Files
AstrBot/tests/test_kook/test_kook_client.py
shuiping233 1199b704a8 feat: implements support for KOOK role mentions (#7626)
* feat: 实现kook适配器响应`@`角色(role)的能力

* refactor: kook适配器处理role时,`At`组件保留`@`角色的名称而不是id

* fix: kook适配器处理role时,role_id的判断问题

* refactor: 移除kook适配器中的一个# type: ignore

* fix: 修复kook适配器 role mention转换成`At`组件时保留不是角色名称的bug;

* unittest: 给kook适配器添加带有role mention的事件消息的单测,并添加消息组件转换判断单测

* unittest: 部分重构test_kook_event.py和test_kook_types.py 单测

* unittest: 添加kook适配器的 `user/me` `user/view` 接口响应数据验证单测

* fix: 修复kook适配器接收频道权限更新消息会报错的bug

* fix:  不额外处理kook的道具消息

* fix: 使用async with self._http_client.get

* refactor: kook适配器转换文本内容为消息组件时,只strip mention之间的空格

* fix: 修复 role_mention_counter 计数不正确的问题

* fix: 修复kook适配器发送卡片失败的问题;区分两类kook 数据类的to_dict to_json行为

* chore: 添加注释

* refactor: 重构kook适配器的角色缓存功能,使其无锁,性能更好且具备良好的重试机制

* refactor: kook适配器的channel_id 改为 guild_id

* feat: kook适配器响应频道角色更新事件时不再清空整个角色id缓存,而是只清理特定频道的角色id缓存

* unittest: 添加kook适配器的update_role事件的数据类验证单测

* refactor: 补上了一些打印的日志消息文本

refactor: 补上了一些打印的日志消息文本

refactor: 补上了一些打印的日志消息文本

* refactor: 修复kook适配器潜在可能的类型问题

* refactor: `clean_roles_cache`重命名为`clear_guild_roles_cache`
2026-04-19 14:18:16 +08:00

157 lines
4.8 KiB
Python

import asyncio
from dataclasses import dataclass, field
import json
from pathlib import Path
from typing import Any
from unittest.mock import AsyncMock
import pytest
from astrbot.core.message.components import (
At,
AtAll,
BaseMessageComponent,
Plain,
Record,
)
from astrbot.core.platform.sources.kook.kook_client import KookClient
from astrbot.core.platform.sources.kook.kook_config import KookConfig
from astrbot.core.platform.sources.kook.kook_types import (
KookMessageEventData,
KookWebsocketEvent,
)
from tests.test_kook.shared import (
KookEventDataPath,
mock_http_client,
mock_kook_roles_record,
)
TEST_BOT_ID = 1234567891
TEST_BOT_USERNAME = "test_username"
TEST_BOT_NICKNAME = "test_nickname"
def mock_kook_client(config: KookConfig, event_callback):
class MockKookClient:
def __init__(self, config, callback):
self.bot_id = TEST_BOT_ID
self.bot_nickname = TEST_BOT_NICKNAME
self.bot_username = TEST_BOT_USERNAME
self.http_client = mock_http_client()
self.connect = AsyncMock()
self.close = AsyncMock()
async def __aenter__(self):
return self
async def __aexit__(self, *args):
pass
return MockKookClient(config, event_callback)
def get_json_field(content: dict, json_field_path: list[str | int]) -> Any:
expend_value = content
for key in json_field_path:
expend_value = expend_value[key]
return expend_value
@dataclass
class JsonFieldPaths:
message_str: list[int | str] = field(default_factory=list)
@pytest.mark.asyncio
@pytest.mark.parametrize(
"expected_json_data_path, expected_message_str, expected_message_components",
[
(
KookEventDataPath.GROUP_MESSAGE_WITH_MENTION,
["d", "extra", "kmarkdown", "raw_content"],
[
# 这里默认机器人一定属于某个角色id
At(qq=TEST_BOT_ID, name="some_role"),
Plain(text="/help"),
At(qq=3351526782, name="some_username"),
AtAll(qq="all", name=""),
],
),
(
KookEventDataPath.GROUP_MESSAGE,
["d", "extra", "kmarkdown", "raw_content"],
[Plain(text="done!")],
),
(
KookEventDataPath.MESSAGE_WITH_CARD_1,
"[audio]",
[
Plain(text="[audio]"),
Record(
file="https://img.kookapp.cn/attachments/2026-03/03/69a6841c3125d.wav",
url="",
text=None,
path=None,
),
],
),
(
KookEventDataPath.MESSAGE_WITH_CARD_2,
["d", "extra", "kmarkdown", "raw_content"],
[
Plain(text="(met)"),
Plain(text="all(met) #hello \\*\\*world\\*\\* [audio]\n😆"),
Record(
file="https://img.kookapp.cn/attachments/2026-03/03/69a6841c3125d.wav",
url="",
text=None,
path=None,
),
],
),
(
KookEventDataPath.PRIVATE_MESSAGE,
["d", "extra", "kmarkdown", "raw_content"],
[Plain(text="/help")],
),
],
)
async def test_kook_event_warp_message(
expected_json_data_path: Path,
expected_message_str: list[int | str] | str,
expected_message_components: list[BaseMessageComponent],
):
monkeypatch = pytest.MonkeyPatch()
monkeypatch.setattr(
"astrbot.core.platform.sources.kook.kook_adapter.KookClient", mock_kook_client
)
monkeypatch.setattr(
"astrbot.core.platform.sources.kook.kook_adapter.KookRolesRecord",
mock_kook_roles_record,
)
from astrbot.core.platform.sources.kook.kook_adapter import KookPlatformAdapter
adapter = KookPlatformAdapter({}, {}, asyncio.Queue())
raw_event_str = expected_json_data_path.read_text(encoding="utf-8")
raw_event = json.loads(raw_event_str)
event = KookWebsocketEvent.from_json(
raw_event_str,
)
assert isinstance(event.data, KookMessageEventData)
astrbotMessage = await adapter.convert_message(event.data)
assert astrbotMessage.self_id == TEST_BOT_ID
assert astrbotMessage.sender.user_id == raw_event["d"]["author_id"]
assert (
astrbotMessage.sender.nickname == raw_event["d"]["extra"]["author"]["username"]
)
assert astrbotMessage.raw_message == raw_event["d"]
assert astrbotMessage.message_id == raw_event["d"]["msg_id"]
assert astrbotMessage.message == expected_message_components
if isinstance(expected_message_str, str):
assert astrbotMessage.message_str == expected_message_str
else:
assert get_json_field(raw_event, expected_message_str)