mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-09 07:16:04 +00:00
feat(kook): add eba adapter
This commit is contained in:
275
tests/unit_tests/platform/test_kook_eba_adapter.py
Normal file
275
tests/unit_tests/platform/test_kook_eba_adapter.py
Normal file
@@ -0,0 +1,275 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from langbot.pkg.platform.adapters.kook.adapter import KookAdapter
|
||||
from langbot.pkg.platform.adapters.kook.event_converter import KookEventConverter
|
||||
from langbot.pkg.platform.adapters.kook.message_converter import KookMessageConverter
|
||||
from langbot.pkg.platform.adapters.kook.platform_api import PLATFORM_API_MAP, get_gateway
|
||||
from langbot_plugin.api.definition.abstract.platform.event_logger import AbstractEventLogger
|
||||
from langbot_plugin.api.entities.builtin.platform import entities as platform_entities
|
||||
from langbot_plugin.api.entities.builtin.platform import events as platform_events
|
||||
from langbot_plugin.api.entities.builtin.platform import message as platform_message
|
||||
|
||||
|
||||
class DummyLogger(AbstractEventLogger):
|
||||
async def info(self, text, images=None, message_session_id=None, no_throw=True):
|
||||
pass
|
||||
|
||||
async def debug(self, text, images=None, message_session_id=None, no_throw=True):
|
||||
pass
|
||||
|
||||
async def warning(self, text, images=None, message_session_id=None, no_throw=True):
|
||||
pass
|
||||
|
||||
async def error(self, text, images=None, message_session_id=None, no_throw=True):
|
||||
pass
|
||||
|
||||
|
||||
def make_adapter() -> KookAdapter:
|
||||
return KookAdapter({'token': 'fake', 'enable-stream-reply': False}, DummyLogger())
|
||||
|
||||
|
||||
def manifest() -> dict:
|
||||
manifest_path = (
|
||||
pathlib.Path(__file__).parents[3]
|
||||
/ 'src'
|
||||
/ 'langbot'
|
||||
/ 'pkg'
|
||||
/ 'platform'
|
||||
/ 'adapters'
|
||||
/ 'kook'
|
||||
/ 'manifest.yaml'
|
||||
)
|
||||
return yaml.safe_load(manifest_path.read_text())
|
||||
|
||||
|
||||
def fake_kook_message(**overrides):
|
||||
event = {
|
||||
'channel_type': 'GROUP',
|
||||
'type': 9,
|
||||
'author_id': 'u1',
|
||||
'target_id': 'c1',
|
||||
'msg_id': 'm1',
|
||||
'msg_timestamp': 1_775_000_000_000,
|
||||
'content': 'hi (met)u2(met) and (met)all(met)',
|
||||
'extra': {
|
||||
'channel_name': 'general',
|
||||
'guild_id': 'g1',
|
||||
'guild_name': 'Guild',
|
||||
'author': {
|
||||
'id': 'u1',
|
||||
'username': 'alice',
|
||||
'nickname': 'Alice',
|
||||
'avatar': 'https://example/avatar.png',
|
||||
},
|
||||
'mention': ['u2'],
|
||||
'mention_all': True,
|
||||
},
|
||||
}
|
||||
event.update(overrides)
|
||||
return event
|
||||
|
||||
|
||||
def test_kook_supported_events_match_manifest():
|
||||
assert make_adapter().get_supported_events() == manifest()['spec']['supported_events']
|
||||
|
||||
|
||||
def test_kook_supported_apis_match_manifest():
|
||||
supported_apis = make_adapter().get_supported_apis()
|
||||
manifest_apis = manifest()['spec']['supported_apis']
|
||||
|
||||
assert supported_apis == manifest_apis['required'] + manifest_apis['optional']
|
||||
|
||||
|
||||
def test_kook_platform_api_map_matches_manifest():
|
||||
manifest_actions = {item['action'] for item in manifest()['spec']['platform_specific_apis']}
|
||||
|
||||
assert set(PLATFORM_API_MAP) == manifest_actions
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kook_adapter_dispatches_most_specific_eba_listener():
|
||||
adapter = make_adapter()
|
||||
calls: list[str] = []
|
||||
|
||||
async def event_listener(event, adapter):
|
||||
calls.append('event')
|
||||
|
||||
async def eba_listener(event, adapter):
|
||||
calls.append('eba')
|
||||
|
||||
async def message_listener(event, adapter):
|
||||
calls.append('message')
|
||||
|
||||
adapter.register_listener(platform_events.Event, event_listener)
|
||||
adapter.register_listener(platform_events.EBAEvent, eba_listener)
|
||||
adapter.register_listener(platform_events.MessageReceivedEvent, message_listener)
|
||||
|
||||
event = platform_events.MessageReceivedEvent(
|
||||
message_id='m1',
|
||||
message_chain=platform_message.MessageChain([platform_message.Plain(text='hello')]),
|
||||
sender=platform_entities.User(id='u1'),
|
||||
chat_id='c1',
|
||||
)
|
||||
|
||||
await adapter._dispatch_eba_event(event)
|
||||
|
||||
assert calls == ['message']
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kook_message_converter_maps_target_text_mentions_and_source():
|
||||
chain = await KookMessageConverter.target2yiri(fake_kook_message(), bot_account_id='bot')
|
||||
|
||||
assert isinstance(chain[0], platform_message.Source)
|
||||
assert chain[0].id == 'm1'
|
||||
assert isinstance(chain[1], platform_message.Plain)
|
||||
assert chain[1].text == 'hi '
|
||||
assert isinstance(chain[2], platform_message.At)
|
||||
assert chain[2].target == 'u2'
|
||||
assert isinstance(chain[4], platform_message.AtAll)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kook_message_converter_maps_media_components():
|
||||
image = await KookMessageConverter.target2yiri(fake_kook_message(type=2, content='https://example/image.png'))
|
||||
assert isinstance(image[1], platform_message.Image)
|
||||
assert image[1].url == 'https://example/image.png'
|
||||
|
||||
file_chain = await KookMessageConverter.target2yiri(
|
||||
fake_kook_message(type=4, content='https://example/file.bin', extra={'attachments': {'name': 'file.bin'}})
|
||||
)
|
||||
assert isinstance(file_chain[1], platform_message.File)
|
||||
assert file_chain[1].name == 'file.bin'
|
||||
|
||||
voice = await KookMessageConverter.target2yiri(fake_kook_message(type=8, content='https://example/voice.mp3'))
|
||||
assert isinstance(voice[1], platform_message.Voice)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kook_message_converter_maps_common_components_to_target():
|
||||
content, msg_type = await KookMessageConverter.yiri2target(
|
||||
platform_message.MessageChain(
|
||||
[
|
||||
platform_message.Plain(text='hi '),
|
||||
platform_message.At(target='u1'),
|
||||
platform_message.Plain(text=' all '),
|
||||
platform_message.AtAll(),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
assert content == 'hi (met)u1(met) all (met)all(met)'
|
||||
assert msg_type == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kook_event_converter_maps_group_private_and_platform_specific_events():
|
||||
group_event = await KookEventConverter.target2yiri(fake_kook_message(), bot_account_id='bot')
|
||||
assert isinstance(group_event, platform_events.MessageReceivedEvent)
|
||||
assert group_event.type == 'message.received'
|
||||
assert group_event.adapter_name == 'kook'
|
||||
assert group_event.chat_type == platform_entities.ChatType.GROUP
|
||||
assert group_event.chat_id == 'c1'
|
||||
assert group_event.group.id == 'c1'
|
||||
assert group_event.sender.id == 'u1'
|
||||
|
||||
private_event = await KookEventConverter.target2yiri(
|
||||
fake_kook_message(channel_type='PERSON', target_id='u1', extra={'code': 'chat-code'}),
|
||||
bot_account_id='bot',
|
||||
)
|
||||
assert private_event.chat_type == platform_entities.ChatType.PRIVATE
|
||||
assert private_event.chat_id == 'chat-code'
|
||||
assert private_event.group is None
|
||||
|
||||
specific = await KookEventConverter.target2yiri({'type': 255, 'target_id': 'raw'}, bot_account_id='bot')
|
||||
assert isinstance(specific, platform_events.PlatformSpecificEvent)
|
||||
assert specific.action == '255'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kook_event_converter_maps_legacy_events():
|
||||
legacy_group = await KookEventConverter.target2legacy(fake_kook_message(), bot_account_id='bot')
|
||||
assert isinstance(legacy_group, platform_events.GroupMessage)
|
||||
assert legacy_group.sender.group.id == 'c1'
|
||||
|
||||
legacy_private = await KookEventConverter.target2legacy(
|
||||
fake_kook_message(channel_type='PERSON', target_id='u1'),
|
||||
bot_account_id='bot',
|
||||
)
|
||||
assert isinstance(legacy_private, platform_events.FriendMessage)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kook_send_and_reply_pass_expected_payloads():
|
||||
adapter = make_adapter()
|
||||
adapter._request = AsyncMock(return_value={'code': 0, 'data': {'msg_id': 'sent'}})
|
||||
|
||||
result = await adapter.send_message(
|
||||
'group',
|
||||
'c1',
|
||||
platform_message.MessageChain([platform_message.Plain(text='hello')]),
|
||||
)
|
||||
|
||||
assert result.message_id == 'sent'
|
||||
adapter._request.assert_awaited_with(
|
||||
'POST',
|
||||
'/message/create',
|
||||
json={'target_id': 'c1', 'content': 'hello', 'type': 1},
|
||||
)
|
||||
|
||||
source = await KookEventConverter.target2yiri(fake_kook_message(), bot_account_id='bot')
|
||||
await adapter.reply_message(source, platform_message.MessageChain([platform_message.Plain(text='reply')]), True)
|
||||
|
||||
assert adapter._request.await_args_list[-1].args == ('POST', '/message/create')
|
||||
payload = adapter._request.await_args_list[-1].kwargs['json']
|
||||
assert payload['reply_msg_id'] == 'm1'
|
||||
assert payload['quote'] == 'm1'
|
||||
assert payload['content'] == 'reply'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kook_get_gateway_redacts_token_in_platform_api_result():
|
||||
adapter = make_adapter()
|
||||
adapter._request = AsyncMock(
|
||||
return_value={
|
||||
'code': 0,
|
||||
'data': {
|
||||
'url': 'wss://example.invalid/gateway?compress=1&token=secret-token',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
result = await get_gateway(adapter, {'compress': 1})
|
||||
|
||||
assert result['data']['url'] == 'wss://example.invalid/gateway?compress=1&token=%3Credacted%3E'
|
||||
assert 'secret-token' not in result['data']['url']
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kook_handle_event_dispatches_eba_and_legacy_then_caches():
|
||||
adapter = make_adapter()
|
||||
adapter.bot_account_id = 'bot'
|
||||
calls: list[str] = []
|
||||
|
||||
async def legacy_listener(event, adapter):
|
||||
calls.append(type(event).__name__)
|
||||
|
||||
async def eba_listener(event, adapter):
|
||||
calls.append(event.type)
|
||||
|
||||
adapter.register_listener(platform_events.GroupMessage, legacy_listener)
|
||||
adapter.register_listener(platform_events.MessageReceivedEvent, eba_listener)
|
||||
|
||||
await adapter._handle_event(fake_kook_message(), 7)
|
||||
|
||||
assert calls == ['GroupMessage', 'message.received']
|
||||
assert adapter.current_sn == 7
|
||||
assert 'm1' in adapter._message_cache
|
||||
assert 'u1' in adapter._user_cache
|
||||
assert 'c1' in adapter._group_cache
|
||||
Reference in New Issue
Block a user