mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-18 10:00:40 +08:00
feat: 添加测试用例,覆盖各个客户端的实现,包括 CapabilityProxy、DBClient、LLMClient、MemoryClient 和 PlatformClient
This commit is contained in:
304
tests_v4/test_capability_proxy.py
Normal file
304
tests_v4/test_capability_proxy.py
Normal file
@@ -0,0 +1,304 @@
|
||||
"""
|
||||
Tests for clients/_proxy.py - CapabilityProxy implementation.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from astrbot_sdk.clients._proxy import CapabilityProxy
|
||||
from astrbot_sdk.errors import AstrBotError
|
||||
|
||||
|
||||
@dataclass
|
||||
class MockCapabilityDescriptor:
|
||||
"""Mock capability descriptor for testing."""
|
||||
name: str
|
||||
supports_stream: bool | None = None
|
||||
|
||||
|
||||
class MockPeer:
|
||||
"""Mock peer for testing CapabilityProxy."""
|
||||
|
||||
def __init__(self):
|
||||
self.remote_capability_map: dict[str, MockCapabilityDescriptor] = {}
|
||||
self.invoke = AsyncMock(return_value={"result": "ok"})
|
||||
self.invoke_stream = AsyncMock()
|
||||
|
||||
|
||||
class TestCapabilityProxyInit:
|
||||
"""Tests for CapabilityProxy initialization."""
|
||||
|
||||
def test_init_with_peer(self):
|
||||
"""CapabilityProxy should store peer reference."""
|
||||
peer = MagicMock()
|
||||
proxy = CapabilityProxy(peer)
|
||||
assert proxy._peer is peer
|
||||
|
||||
|
||||
class TestCapabilityProxyGetDescriptor:
|
||||
"""Tests for CapabilityProxy._get_descriptor() method."""
|
||||
|
||||
def test_get_descriptor_returns_descriptor(self):
|
||||
"""_get_descriptor should return descriptor if found."""
|
||||
peer = MagicMock()
|
||||
peer.remote_capability_map = {
|
||||
"db.get": MockCapabilityDescriptor(name="db.get")
|
||||
}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
result = proxy._get_descriptor("db.get")
|
||||
assert result is not None
|
||||
assert result.name == "db.get"
|
||||
|
||||
def test_get_descriptor_returns_none_for_missing(self):
|
||||
"""_get_descriptor should return None if not found."""
|
||||
peer = MagicMock()
|
||||
peer.remote_capability_map = {}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
result = proxy._get_descriptor("nonexistent")
|
||||
assert result is None
|
||||
|
||||
def test_get_descriptor_with_empty_map(self):
|
||||
"""_get_descriptor should work with empty capability map."""
|
||||
peer = MagicMock()
|
||||
peer.remote_capability_map = {}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
result = proxy._get_descriptor("anything")
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestCapabilityProxyEnsureAvailable:
|
||||
"""Tests for CapabilityProxy._ensure_available() method."""
|
||||
|
||||
def test_ensure_available_passes_when_descriptor_exists(self):
|
||||
"""_ensure_available should pass when descriptor exists."""
|
||||
peer = MagicMock()
|
||||
peer.remote_capability_map = {
|
||||
"test.cap": MockCapabilityDescriptor(name="test.cap")
|
||||
}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
# Should not raise
|
||||
proxy._ensure_available("test.cap", stream=False)
|
||||
|
||||
def test_ensure_available_raises_capability_not_found(self):
|
||||
"""_ensure_available should raise capability_not_found when missing."""
|
||||
peer = MagicMock()
|
||||
peer.remote_capability_map = {"other.cap": MockCapabilityDescriptor(name="other.cap")}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
with pytest.raises(AstrBotError) as exc_info:
|
||||
proxy._ensure_available("missing.cap", stream=False)
|
||||
|
||||
assert exc_info.value.code == "capability_not_found"
|
||||
assert "missing.cap" in exc_info.value.message
|
||||
|
||||
def test_ensure_available_passes_when_map_empty(self):
|
||||
"""_ensure_available should pass (return None) when capability map is empty."""
|
||||
peer = MagicMock()
|
||||
peer.remote_capability_map = {}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
# Should not raise when map is empty
|
||||
proxy._ensure_available("any.cap", stream=False)
|
||||
|
||||
def test_ensure_available_raises_for_stream_not_supported(self):
|
||||
"""_ensure_available should raise when stream requested but not supported."""
|
||||
peer = MagicMock()
|
||||
peer.remote_capability_map = {
|
||||
"test.cap": MockCapabilityDescriptor(name="test.cap", supports_stream=False)
|
||||
}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
with pytest.raises(AstrBotError) as exc_info:
|
||||
proxy._ensure_available("test.cap", stream=True)
|
||||
|
||||
assert exc_info.value.code == "invalid_input"
|
||||
assert "不支持 stream=true" in exc_info.value.message
|
||||
|
||||
def test_ensure_available_passes_for_stream_supported(self):
|
||||
"""_ensure_available should pass when stream is supported."""
|
||||
peer = MagicMock()
|
||||
peer.remote_capability_map = {
|
||||
"test.cap": MockCapabilityDescriptor(name="test.cap", supports_stream=True)
|
||||
}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
# Should not raise
|
||||
proxy._ensure_available("test.cap", stream=True)
|
||||
|
||||
def test_ensure_available_handles_none_supports_stream(self):
|
||||
"""_ensure_available should treat None supports_stream as not supporting stream."""
|
||||
peer = MagicMock()
|
||||
peer.remote_capability_map = {
|
||||
"test.cap": MockCapabilityDescriptor(name="test.cap", supports_stream=None)
|
||||
}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
# Should not raise for non-stream
|
||||
proxy._ensure_available("test.cap", stream=False)
|
||||
|
||||
# Should raise for stream=True when supports_stream is None
|
||||
with pytest.raises(AstrBotError) as exc_info:
|
||||
proxy._ensure_available("test.cap", stream=True)
|
||||
assert exc_info.value.code == "invalid_input"
|
||||
|
||||
|
||||
class TestCapabilityProxyCall:
|
||||
"""Tests for CapabilityProxy.call() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_call_invokes_peer(self):
|
||||
"""call() should invoke peer with correct parameters."""
|
||||
peer = MockPeer()
|
||||
peer.remote_capability_map = {
|
||||
"db.get": MockCapabilityDescriptor(name="db.get")
|
||||
}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
result = await proxy.call("db.get", {"key": "test"})
|
||||
|
||||
peer.invoke.assert_called_once_with("db.get", {"key": "test"}, stream=False)
|
||||
assert result == {"result": "ok"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_call_without_capability_map(self):
|
||||
"""call() should work when capability map is empty."""
|
||||
peer = MockPeer()
|
||||
peer.remote_capability_map = {}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
result = await proxy.call("any.cap", {})
|
||||
|
||||
peer.invoke.assert_called_once_with("any.cap", {}, stream=False)
|
||||
assert result == {"result": "ok"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_call_raises_for_missing_capability(self):
|
||||
"""call() should raise for missing capability when map is not empty."""
|
||||
peer = MockPeer()
|
||||
peer.remote_capability_map = {
|
||||
"other.cap": MockCapabilityDescriptor(name="other.cap")
|
||||
}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
with pytest.raises(AstrBotError) as exc_info:
|
||||
await proxy.call("missing.cap", {})
|
||||
|
||||
assert exc_info.value.code == "capability_not_found"
|
||||
|
||||
|
||||
class MockAsyncIterator:
|
||||
"""Mock async iterator for testing stream responses."""
|
||||
|
||||
def __init__(self, items):
|
||||
self._items = list(items)
|
||||
self._index = 0
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
if self._index >= len(self._items):
|
||||
raise StopAsyncIteration
|
||||
item = self._items[self._index]
|
||||
self._index += 1
|
||||
return item
|
||||
|
||||
|
||||
@dataclass
|
||||
class MockEvent:
|
||||
"""Mock stream event for testing."""
|
||||
phase: str
|
||||
data: dict
|
||||
|
||||
|
||||
class TestCapabilityProxyStream:
|
||||
"""Tests for CapabilityProxy.stream() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_yields_delta_data(self):
|
||||
"""stream() should yield data from delta events."""
|
||||
peer = MockPeer()
|
||||
|
||||
# invoke_stream is an async method that returns AsyncIterator
|
||||
events = [
|
||||
MockEvent(phase="delta", data={"text": "chunk1"}),
|
||||
MockEvent(phase="delta", data={"text": "chunk2"}),
|
||||
MockEvent(phase="complete", data={"done": True}),
|
||||
]
|
||||
peer.invoke_stream = AsyncMock(return_value=MockAsyncIterator(events))
|
||||
peer.remote_capability_map = {
|
||||
"llm.stream": MockCapabilityDescriptor(name="llm.stream", supports_stream=True)
|
||||
}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
chunks = []
|
||||
async for data in proxy.stream("llm.stream", {"prompt": "hi"}):
|
||||
chunks.append(data)
|
||||
|
||||
assert len(chunks) == 2
|
||||
assert chunks[0] == {"text": "chunk1"}
|
||||
assert chunks[1] == {"text": "chunk2"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_filters_non_delta_events(self):
|
||||
"""stream() should only yield delta events."""
|
||||
peer = MockPeer()
|
||||
|
||||
events = [
|
||||
MockEvent(phase="start", data={"session": "abc"}),
|
||||
MockEvent(phase="delta", data={"text": "hello"}),
|
||||
MockEvent(phase="complete", data={}),
|
||||
MockEvent(phase="delta", data={"text": "world"}),
|
||||
]
|
||||
peer.invoke_stream = AsyncMock(return_value=MockAsyncIterator(events))
|
||||
peer.remote_capability_map = {
|
||||
"test.stream": MockCapabilityDescriptor(name="test.stream", supports_stream=True)
|
||||
}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
chunks = []
|
||||
async for data in proxy.stream("test.stream", {}):
|
||||
chunks.append(data)
|
||||
|
||||
# Only delta events should be yielded
|
||||
assert len(chunks) == 2
|
||||
assert chunks[0] == {"text": "hello"}
|
||||
assert chunks[1] == {"text": "world"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_raises_for_non_streaming_capability(self):
|
||||
"""stream() should raise when capability doesn't support streaming."""
|
||||
peer = MockPeer()
|
||||
peer.remote_capability_map = {
|
||||
"db.get": MockCapabilityDescriptor(name="db.get", supports_stream=False)
|
||||
}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
with pytest.raises(AstrBotError) as exc_info:
|
||||
async for _ in proxy.stream("db.get", {}):
|
||||
pass
|
||||
|
||||
assert exc_info.value.code == "invalid_input"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_works_without_capability_map(self):
|
||||
"""stream() should work when capability map is empty."""
|
||||
peer = MockPeer()
|
||||
|
||||
events = [MockEvent(phase="delta", data={"text": "ok"})]
|
||||
peer.invoke_stream = AsyncMock(return_value=MockAsyncIterator(events))
|
||||
peer.remote_capability_map = {}
|
||||
proxy = CapabilityProxy(peer)
|
||||
|
||||
chunks = []
|
||||
async for data in proxy.stream("any.stream", {}):
|
||||
chunks.append(data)
|
||||
|
||||
assert chunks == [{"text": "ok"}]
|
||||
69
tests_v4/test_clients_module.py
Normal file
69
tests_v4/test_clients_module.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Tests for clients/__init__.py - Module exports.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestClientsModuleExports:
|
||||
"""Tests for clients module exports."""
|
||||
|
||||
def test_exports_db_client(self):
|
||||
"""clients module should export DBClient."""
|
||||
from astrbot_sdk.clients import DBClient
|
||||
|
||||
assert DBClient is not None
|
||||
|
||||
def test_exports_llm_client(self):
|
||||
"""clients module should export LLMClient."""
|
||||
from astrbot_sdk.clients import LLMClient
|
||||
|
||||
assert LLMClient is not None
|
||||
|
||||
def test_exports_llm_response(self):
|
||||
"""clients module should export LLMResponse."""
|
||||
from astrbot_sdk.clients import LLMResponse
|
||||
|
||||
assert LLMResponse is not None
|
||||
|
||||
def test_exports_chat_message(self):
|
||||
"""clients module should export ChatMessage."""
|
||||
from astrbot_sdk.clients import ChatMessage
|
||||
|
||||
assert ChatMessage is not None
|
||||
|
||||
def test_exports_memory_client(self):
|
||||
"""clients module should export MemoryClient."""
|
||||
from astrbot_sdk.clients import MemoryClient
|
||||
|
||||
assert MemoryClient is not None
|
||||
|
||||
def test_exports_platform_client(self):
|
||||
"""clients module should export PlatformClient."""
|
||||
from astrbot_sdk.clients import PlatformClient
|
||||
|
||||
assert PlatformClient is not None
|
||||
|
||||
def test_all_exports_defined(self):
|
||||
"""__all__ should contain all expected exports."""
|
||||
from astrbot_sdk.clients import __all__
|
||||
|
||||
assert "DBClient" in __all__
|
||||
assert "LLMClient" in __all__
|
||||
assert "LLMResponse" in __all__
|
||||
assert "ChatMessage" in __all__
|
||||
assert "MemoryClient" in __all__
|
||||
assert "PlatformClient" in __all__
|
||||
|
||||
def test_does_not_export_capability_proxy(self):
|
||||
"""CapabilityProxy should not be in public exports."""
|
||||
from astrbot_sdk.clients import __all__
|
||||
|
||||
assert "CapabilityProxy" not in __all__
|
||||
|
||||
def test_capability_proxy_importable_from_private(self):
|
||||
"""CapabilityProxy should be importable from _proxy."""
|
||||
from astrbot_sdk.clients._proxy import CapabilityProxy
|
||||
|
||||
assert CapabilityProxy is not None
|
||||
213
tests_v4/test_db_client.py
Normal file
213
tests_v4/test_db_client.py
Normal file
@@ -0,0 +1,213 @@
|
||||
"""
|
||||
Tests for clients/db.py - DBClient implementation.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from astrbot_sdk.clients.db import DBClient
|
||||
from astrbot_sdk.clients._proxy import CapabilityProxy
|
||||
|
||||
|
||||
class TestDBClientInit:
|
||||
"""Tests for DBClient initialization."""
|
||||
|
||||
def test_init_with_proxy(self):
|
||||
"""DBClient should store proxy reference."""
|
||||
proxy = MagicMock(spec=CapabilityProxy)
|
||||
client = DBClient(proxy)
|
||||
assert client._proxy is proxy
|
||||
|
||||
|
||||
class TestDBClientGet:
|
||||
"""Tests for DBClient.get() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_returns_dict_value(self):
|
||||
"""get() should return dict value from proxy response."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"value": {"data": "test"}})
|
||||
|
||||
client = DBClient(proxy)
|
||||
result = await client.get("my_key")
|
||||
|
||||
proxy.call.assert_called_once_with("db.get", {"key": "my_key"})
|
||||
assert result == {"data": "test"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_returns_none_for_missing_key(self):
|
||||
"""get() should return None when value is not found."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"value": None})
|
||||
|
||||
client = DBClient(proxy)
|
||||
result = await client.get("missing_key")
|
||||
|
||||
assert result is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_returns_none_for_non_dict_value(self):
|
||||
"""get() should return None when value is not a dict."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"value": "not a dict"})
|
||||
|
||||
client = DBClient(proxy)
|
||||
result = await client.get("my_key")
|
||||
|
||||
assert result is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_returns_none_when_value_key_missing(self):
|
||||
"""get() should return None when 'value' key is missing in response."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = DBClient(proxy)
|
||||
result = await client.get("my_key")
|
||||
|
||||
assert result is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_with_empty_key(self):
|
||||
"""get() should work with empty string key."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"value": {"empty": True}})
|
||||
|
||||
client = DBClient(proxy)
|
||||
result = await client.get("")
|
||||
|
||||
proxy.call.assert_called_once_with("db.get", {"key": ""})
|
||||
assert result == {"empty": True}
|
||||
|
||||
|
||||
class TestDBClientSet:
|
||||
"""Tests for DBClient.set() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_calls_proxy_with_key_and_value(self):
|
||||
"""set() should call proxy with correct parameters."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = DBClient(proxy)
|
||||
await client.set("test_key", {"name": "value"})
|
||||
|
||||
proxy.call.assert_called_once_with(
|
||||
"db.set",
|
||||
{"key": "test_key", "value": {"name": "value"}},
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_with_empty_dict(self):
|
||||
"""set() should work with empty dict value."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = DBClient(proxy)
|
||||
await client.set("empty", {})
|
||||
|
||||
proxy.call.assert_called_once_with("db.set", {"key": "empty", "value": {}})
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_with_nested_dict(self):
|
||||
"""set() should work with nested dict."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = DBClient(proxy)
|
||||
await client.set("nested", {"level1": {"level2": {"level3": "deep"}}})
|
||||
|
||||
proxy.call.assert_called_once_with(
|
||||
"db.set",
|
||||
{"key": "nested", "value": {"level1": {"level2": {"level3": "deep"}}}},
|
||||
)
|
||||
|
||||
|
||||
class TestDBClientDelete:
|
||||
"""Tests for DBClient.delete() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_calls_proxy_with_key(self):
|
||||
"""delete() should call proxy with correct key."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = DBClient(proxy)
|
||||
await client.delete("to_delete")
|
||||
|
||||
proxy.call.assert_called_once_with("db.delete", {"key": "to_delete"})
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_with_empty_key(self):
|
||||
"""delete() should work with empty string key."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = DBClient(proxy)
|
||||
await client.delete("")
|
||||
|
||||
proxy.call.assert_called_once_with("db.delete", {"key": ""})
|
||||
|
||||
|
||||
class TestDBClientList:
|
||||
"""Tests for DBClient.list() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_returns_keys(self):
|
||||
"""list() should return list of keys."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"keys": ["key1", "key2", "key3"]})
|
||||
|
||||
client = DBClient(proxy)
|
||||
result = await client.list()
|
||||
|
||||
proxy.call.assert_called_once_with("db.list", {"prefix": None})
|
||||
assert result == ["key1", "key2", "key3"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_with_prefix(self):
|
||||
"""list() should pass prefix parameter."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"keys": ["user:1", "user:2"]})
|
||||
|
||||
client = DBClient(proxy)
|
||||
result = await client.list(prefix="user:")
|
||||
|
||||
proxy.call.assert_called_once_with("db.list", {"prefix": "user:"})
|
||||
assert result == ["user:1", "user:2"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_returns_empty_list_when_no_keys(self):
|
||||
"""list() should return empty list when no keys found."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = DBClient(proxy)
|
||||
result = await client.list()
|
||||
|
||||
assert result == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_converts_non_string_items(self):
|
||||
"""list() should convert non-string items to string."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"keys": [123, 456]})
|
||||
|
||||
client = DBClient(proxy)
|
||||
result = await client.list()
|
||||
|
||||
assert result == ["123", "456"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_with_none_prefix(self):
|
||||
"""list() should handle None prefix."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"keys": []})
|
||||
|
||||
client = DBClient(proxy)
|
||||
result = await client.list(prefix=None)
|
||||
|
||||
proxy.call.assert_called_once_with("db.list", {"prefix": None})
|
||||
assert result == []
|
||||
249
tests_v4/test_llm_client.py
Normal file
249
tests_v4/test_llm_client.py
Normal file
@@ -0,0 +1,249 @@
|
||||
"""
|
||||
Tests for clients/llm.py - LLMClient and related models.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from astrbot_sdk.clients.llm import ChatMessage, LLMClient, LLMResponse
|
||||
from astrbot_sdk.clients._proxy import CapabilityProxy
|
||||
|
||||
|
||||
class TestChatMessage:
|
||||
"""Tests for ChatMessage model."""
|
||||
|
||||
def test_create_with_role_and_content(self):
|
||||
"""ChatMessage should have role and content."""
|
||||
msg = ChatMessage(role="user", content="Hello")
|
||||
assert msg.role == "user"
|
||||
assert msg.content == "Hello"
|
||||
|
||||
def test_model_dump(self):
|
||||
"""ChatMessage should serialize correctly."""
|
||||
msg = ChatMessage(role="assistant", content="Hi there")
|
||||
data = msg.model_dump()
|
||||
assert data == {"role": "assistant", "content": "Hi there"}
|
||||
|
||||
|
||||
class TestLLMResponse:
|
||||
"""Tests for LLMResponse model."""
|
||||
|
||||
def test_create_with_text_only(self):
|
||||
"""LLMResponse should work with just text."""
|
||||
response = LLMResponse(text="Hello")
|
||||
assert response.text == "Hello"
|
||||
assert response.usage is None
|
||||
assert response.finish_reason is None
|
||||
assert response.tool_calls == []
|
||||
|
||||
def test_create_with_all_fields(self):
|
||||
"""LLMResponse should accept all fields."""
|
||||
response = LLMResponse(
|
||||
text="Response",
|
||||
usage={"prompt_tokens": 10, "completion_tokens": 5},
|
||||
finish_reason="stop",
|
||||
tool_calls=[{"name": "search", "args": {"query": "test"}}],
|
||||
)
|
||||
assert response.text == "Response"
|
||||
assert response.usage["prompt_tokens"] == 10
|
||||
assert response.finish_reason == "stop"
|
||||
assert len(response.tool_calls) == 1
|
||||
|
||||
def test_model_validate(self):
|
||||
"""LLMResponse should validate from dict."""
|
||||
data = {
|
||||
"text": "Validated",
|
||||
"usage": {"total_tokens": 100},
|
||||
"finish_reason": "length",
|
||||
"tool_calls": [],
|
||||
}
|
||||
response = LLMResponse.model_validate(data)
|
||||
assert response.text == "Validated"
|
||||
assert response.usage["total_tokens"] == 100
|
||||
|
||||
|
||||
class TestLLMClientInit:
|
||||
"""Tests for LLMClient initialization."""
|
||||
|
||||
def test_init_with_proxy(self):
|
||||
"""LLMClient should store proxy reference."""
|
||||
proxy = MagicMock(spec=CapabilityProxy)
|
||||
client = LLMClient(proxy)
|
||||
assert client._proxy is proxy
|
||||
|
||||
|
||||
class TestLLMClientChat:
|
||||
"""Tests for LLMClient.chat() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_with_prompt_only(self):
|
||||
"""chat() should work with just prompt."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"text": "Hello back"})
|
||||
|
||||
client = LLMClient(proxy)
|
||||
result = await client.chat("Hello")
|
||||
|
||||
proxy.call.assert_called_once()
|
||||
call_args = proxy.call.call_args
|
||||
assert call_args[0][0] == "llm.chat"
|
||||
assert call_args[0][1]["prompt"] == "Hello"
|
||||
assert result == "Hello back"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_with_system_prompt(self):
|
||||
"""chat() should pass system prompt."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"text": "Response"})
|
||||
|
||||
client = LLMClient(proxy)
|
||||
result = await client.chat("Hi", system="Be helpful")
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert call_args["system"] == "Be helpful"
|
||||
assert result == "Response"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_with_history(self):
|
||||
"""chat() should pass conversation history."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"text": "OK"})
|
||||
|
||||
client = LLMClient(proxy)
|
||||
history = [
|
||||
ChatMessage(role="user", content="Hello"),
|
||||
ChatMessage(role="assistant", content="Hi"),
|
||||
]
|
||||
await client.chat("How are you?", history=history)
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert len(call_args["history"]) == 2
|
||||
assert call_args["history"][0] == {"role": "user", "content": "Hello"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_with_model_and_temperature(self):
|
||||
"""chat() should pass model and temperature."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"text": "Done"})
|
||||
|
||||
client = LLMClient(proxy)
|
||||
await client.chat("Test", model="gpt-4", temperature=0.5)
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert call_args["model"] == "gpt-4"
|
||||
assert call_args["temperature"] == 0.5
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_returns_empty_string_for_missing_text(self):
|
||||
"""chat() should return empty string if text is missing."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = LLMClient(proxy)
|
||||
result = await client.chat("Hello")
|
||||
|
||||
assert result == ""
|
||||
|
||||
|
||||
class TestLLMClientChatRaw:
|
||||
"""Tests for LLMClient.chat_raw() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_raw_returns_llm_response(self):
|
||||
"""chat_raw() should return LLMResponse object."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(
|
||||
return_value={
|
||||
"text": "Raw response",
|
||||
"usage": {"tokens": 50},
|
||||
"finish_reason": "stop",
|
||||
"tool_calls": [],
|
||||
}
|
||||
)
|
||||
|
||||
client = LLMClient(proxy)
|
||||
result = await client.chat_raw("Test")
|
||||
|
||||
assert isinstance(result, LLMResponse)
|
||||
assert result.text == "Raw response"
|
||||
assert result.usage["tokens"] == 50
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_raw_passes_kwargs(self):
|
||||
"""chat_raw() should pass additional kwargs to proxy."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"text": "OK"})
|
||||
|
||||
client = LLMClient(proxy)
|
||||
await client.chat_raw("Test", custom_param="value", another=123)
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert call_args["custom_param"] == "value"
|
||||
assert call_args["another"] == 123
|
||||
|
||||
|
||||
class TestLLMClientStreamChat:
|
||||
"""Tests for LLMClient.stream_chat() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_chat_yields_text_chunks(self):
|
||||
"""stream_chat() should yield text chunks."""
|
||||
proxy = MagicMock(spec=CapabilityProxy)
|
||||
|
||||
async def mock_stream(name, payload):
|
||||
yield {"text": "Hello"}
|
||||
yield {"text": " "}
|
||||
yield {"text": "World"}
|
||||
|
||||
proxy.stream = mock_stream
|
||||
|
||||
client = LLMClient(proxy)
|
||||
chunks = []
|
||||
async for chunk in client.stream_chat("Test"):
|
||||
chunks.append(chunk)
|
||||
|
||||
assert chunks == ["Hello", " ", "World"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_chat_with_system_and_history(self):
|
||||
"""stream_chat() should pass system and history."""
|
||||
proxy = MagicMock(spec=CapabilityProxy)
|
||||
|
||||
captured_payload = None
|
||||
|
||||
async def mock_stream(name, payload):
|
||||
nonlocal captured_payload
|
||||
captured_payload = payload
|
||||
yield {"text": "Done"}
|
||||
|
||||
proxy.stream = mock_stream
|
||||
|
||||
client = LLMClient(proxy)
|
||||
history = [ChatMessage(role="user", content="Hi")]
|
||||
chunks = []
|
||||
async for chunk in client.stream_chat("Test", system="Be nice", history=history):
|
||||
chunks.append(chunk)
|
||||
|
||||
assert captured_payload["system"] == "Be nice"
|
||||
assert len(captured_payload["history"]) == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_chat_yields_empty_string_for_missing_text(self):
|
||||
"""stream_chat() should yield empty string if text is missing."""
|
||||
proxy = MagicMock(spec=CapabilityProxy)
|
||||
|
||||
async def mock_stream(name, payload):
|
||||
yield {}
|
||||
yield {"other": "data"}
|
||||
|
||||
proxy.stream = mock_stream
|
||||
|
||||
client = LLMClient(proxy)
|
||||
chunks = []
|
||||
async for chunk in client.stream_chat("Test"):
|
||||
chunks.append(chunk)
|
||||
|
||||
assert chunks == ["", ""]
|
||||
185
tests_v4/test_memory_client.py
Normal file
185
tests_v4/test_memory_client.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""
|
||||
Tests for clients/memory.py - MemoryClient implementation.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from astrbot_sdk.clients.memory import MemoryClient
|
||||
from astrbot_sdk.clients._proxy import CapabilityProxy
|
||||
|
||||
|
||||
class TestMemoryClientInit:
|
||||
"""Tests for MemoryClient initialization."""
|
||||
|
||||
def test_init_with_proxy(self):
|
||||
"""MemoryClient should store proxy reference."""
|
||||
proxy = MagicMock(spec=CapabilityProxy)
|
||||
client = MemoryClient(proxy)
|
||||
assert client._proxy is proxy
|
||||
|
||||
|
||||
class TestMemoryClientSearch:
|
||||
"""Tests for MemoryClient.search() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_returns_items(self):
|
||||
"""search() should return list of items."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(
|
||||
return_value={
|
||||
"items": [
|
||||
{"id": "1", "content": "first"},
|
||||
{"id": "2", "content": "second"},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
result = await client.search("test query")
|
||||
|
||||
proxy.call.assert_called_once_with(
|
||||
"memory.search",
|
||||
{"query": "test query"},
|
||||
)
|
||||
assert len(result) == 2
|
||||
assert result[0]["content"] == "first"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_returns_empty_list_for_no_results(self):
|
||||
"""search() should return empty list when no items found."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
result = await client.search("nonexistent")
|
||||
|
||||
assert result == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_with_empty_query(self):
|
||||
"""search() should work with empty query."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"items": []})
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
result = await client.search("")
|
||||
|
||||
proxy.call.assert_called_once_with("memory.search", {"query": ""})
|
||||
assert result == []
|
||||
|
||||
|
||||
class TestMemoryClientSave:
|
||||
"""Tests for MemoryClient.save() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_with_key_and_value(self):
|
||||
"""save() should call proxy with key and value."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
await client.save("my_key", {"data": "value"})
|
||||
|
||||
proxy.call.assert_called_once_with(
|
||||
"memory.save",
|
||||
{"key": "my_key", "value": {"data": "value"}},
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_with_extra_kwargs(self):
|
||||
"""save() should merge extra kwargs into value."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
await client.save("key", {"base": 1}, extra="added", another=2)
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert call_args["value"] == {"base": 1, "extra": "added", "another": 2}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_with_only_kwargs(self):
|
||||
"""save() should work with only kwargs (no value)."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
await client.save("key", name="test", count=5)
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert call_args["value"] == {"name": "test", "count": 5}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_with_none_value(self):
|
||||
"""save() should handle None value with kwargs."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
await client.save("key", None, field="value")
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert call_args["value"] == {"field": "value"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_with_empty_value_and_no_kwargs(self):
|
||||
"""save() should work with empty dict."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
await client.save("key", {})
|
||||
|
||||
proxy.call.assert_called_once_with(
|
||||
"memory.save",
|
||||
{"key": "key", "value": {}},
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_raises_type_error_for_non_dict_value(self):
|
||||
"""save() should raise TypeError for non-dict value."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
|
||||
with pytest.raises(TypeError, match="memory.save 的 value 必须是 dict"):
|
||||
await client.save("key", "not a dict")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_raises_type_error_for_list_value(self):
|
||||
"""save() should raise TypeError for list value."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
|
||||
with pytest.raises(TypeError, match="memory.save 的 value 必须是 dict"):
|
||||
await client.save("key", [1, 2, 3])
|
||||
|
||||
|
||||
class TestMemoryClientDelete:
|
||||
"""Tests for MemoryClient.delete() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_calls_proxy_with_key(self):
|
||||
"""delete() should call proxy with correct key."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
await client.delete("to_delete")
|
||||
|
||||
proxy.call.assert_called_once_with("memory.delete", {"key": "to_delete"})
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_with_empty_key(self):
|
||||
"""delete() should work with empty string key."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = MemoryClient(proxy)
|
||||
await client.delete("")
|
||||
|
||||
proxy.call.assert_called_once_with("memory.delete", {"key": ""})
|
||||
157
tests_v4/test_platform_client.py
Normal file
157
tests_v4/test_platform_client.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
Tests for clients/platform.py - PlatformClient implementation.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from astrbot_sdk.clients.platform import PlatformClient
|
||||
from astrbot_sdk.clients._proxy import CapabilityProxy
|
||||
|
||||
|
||||
class TestPlatformClientInit:
|
||||
"""Tests for PlatformClient initialization."""
|
||||
|
||||
def test_init_with_proxy(self):
|
||||
"""PlatformClient should store proxy reference."""
|
||||
proxy = MagicMock(spec=CapabilityProxy)
|
||||
client = PlatformClient(proxy)
|
||||
assert client._proxy is proxy
|
||||
|
||||
|
||||
class TestPlatformClientSend:
|
||||
"""Tests for PlatformClient.send() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_returns_response(self):
|
||||
"""send() should return response dict."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"message_id": "msg_123", "sent": True})
|
||||
|
||||
client = PlatformClient(proxy)
|
||||
result = await client.send("session-1", "Hello")
|
||||
|
||||
proxy.call.assert_called_once_with(
|
||||
"platform.send",
|
||||
{"session": "session-1", "text": "Hello"},
|
||||
)
|
||||
assert result["message_id"] == "msg_123"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_with_empty_text(self):
|
||||
"""send() should work with empty text."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = PlatformClient(proxy)
|
||||
result = await client.send("session-1", "")
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert call_args["text"] == ""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_with_special_characters(self):
|
||||
"""send() should handle special characters in text."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = PlatformClient(proxy)
|
||||
await client.send("session-1", "Hello\nWorld\t! @#$%")
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert call_args["text"] == "Hello\nWorld\t! @#$%"
|
||||
|
||||
|
||||
class TestPlatformClientSendImage:
|
||||
"""Tests for PlatformClient.send_image() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_image_returns_response(self):
|
||||
"""send_image() should return response dict."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"image_id": "img_456"})
|
||||
|
||||
client = PlatformClient(proxy)
|
||||
result = await client.send_image("session-1", "https://example.com/image.png")
|
||||
|
||||
proxy.call.assert_called_once_with(
|
||||
"platform.send_image",
|
||||
{"session": "session-1", "image_url": "https://example.com/image.png"},
|
||||
)
|
||||
assert result["image_id"] == "img_456"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_image_with_file_url(self):
|
||||
"""send_image() should work with file:// URL."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = PlatformClient(proxy)
|
||||
await client.send_image("session-1", "file:///path/to/image.jpg")
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert call_args["image_url"] == "file:///path/to/image.jpg"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_image_with_base64_url(self):
|
||||
"""send_image() should work with data URL."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = PlatformClient(proxy)
|
||||
await client.send_image("session-1", "data:image/png;base64,abc123")
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert call_args["image_url"] == "data:image/png;base64,abc123"
|
||||
|
||||
|
||||
class TestPlatformClientGetMembers:
|
||||
"""Tests for PlatformClient.get_members() method."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_members_returns_list(self):
|
||||
"""get_members() should return list of members."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(
|
||||
return_value={
|
||||
"members": [
|
||||
{"id": "user1", "name": "Alice"},
|
||||
{"id": "user2", "name": "Bob"},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
client = PlatformClient(proxy)
|
||||
result = await client.get_members("group-1")
|
||||
|
||||
proxy.call.assert_called_once_with(
|
||||
"platform.get_members",
|
||||
{"session": "group-1"},
|
||||
)
|
||||
assert len(result) == 2
|
||||
assert result[0]["name"] == "Alice"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_members_returns_empty_list(self):
|
||||
"""get_members() should return empty list when no members."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={})
|
||||
|
||||
client = PlatformClient(proxy)
|
||||
result = await client.get_members("empty-group")
|
||||
|
||||
assert result == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_members_with_private_session(self):
|
||||
"""get_members() should work with private session."""
|
||||
proxy = AsyncMock(spec=CapabilityProxy)
|
||||
proxy.call = AsyncMock(return_value={"members": [{"id": "single_user"}]})
|
||||
|
||||
client = PlatformClient(proxy)
|
||||
result = await client.get_members("private-123")
|
||||
|
||||
call_args = proxy.call.call_args[0][1]
|
||||
assert call_args["session"] == "private-123"
|
||||
Reference in New Issue
Block a user