# 本文件为测试文件,含有大量的MonkeyPatch和Mock代码,请忽略TypeChecker的报错 import importlib.util import sys from dataclasses import dataclass from types import ModuleType from pathlib import Path import pytest def _install_stub_modules(monkeypatch): def _stub_module(name: str) -> ModuleType: module = ModuleType(name) monkeypatch.setitem(sys.modules, name, module) return module # src.common.logger logger_mod = _stub_module("src.common.logger") class _Logger: def __init__(self): self.info_calls = [] self.debug_calls = [] self.warning_calls = [] self.error_calls = [] self.critical_calls = [] def info(self, *args, **kwargs): self.info_calls.append(args) def debug(self, *args, **kwargs): self.debug_calls.append(args) def warning(self, *args, **kwargs): self.warning_calls.append(args) def error(self, *args, **kwargs): self.error_calls.append(args) def critical(self, *args, **kwargs): self.critical_calls.append(args) def get_logger(_name: str): return _Logger() logger_mod.get_logger = get_logger # src.common.data_models.image_data_model data_model_mod = _stub_module("src.common.data_models.image_data_model") @dataclass class MaiEmoji: full_path: Path | None = None file_name: str = "" description: str | None = None emotion: list[str] | None = None file_hash: str | None = None is_deleted: bool = False query_count: int = 0 register_time: object | None = None image_format: str | None = None @staticmethod def from_db_instance(_record): return MaiEmoji() def to_db_instance(self): return Images() async def calculate_hash_format(self): return True @staticmethod def read_image_bytes(_path): return b"" data_model_mod.MaiEmoji = MaiEmoji # src.common.database.database_model db_model_mod = _stub_module("src.common.database.database_model") class Images: id = 0 is_registered = False is_banned = False register_time = None query_count = 0 last_used_time = None full_path = "" image_hash = "" image_type = None class ImageType: EMOJI = "EMOJI" db_model_mod.Images = Images db_model_mod.ImageType = ImageType # src.common.database.database db_mod = _stub_module("src.common.database.database") class _DummySession: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): class _Result: def scalars(self): return self def all(self): return [] def first(self): return None return _Result() def add(self, _record): pass def delete(self, _record): pass def flush(self): pass def get_db_session(): return _DummySession() db_mod.get_db_session = get_db_session # src.common.utils.utils_image image_utils_mod = _stub_module("src.common.utils.utils_image") class ImageUtils: @staticmethod def gif_2_static_image(_image_bytes): return b"" @staticmethod def image_bytes_to_base64(_image_bytes): return "" image_utils_mod.ImageUtils = ImageUtils # src.prompt.prompt_manager prompt_manager_mod = _stub_module("src.prompt.prompt_manager") class _Prompt: def add_context(self, _key, _value): pass class _PromptManager: def get_prompt(self, _name): return _Prompt() async def render_prompt(self, _prompt): return "" prompt_manager_mod.prompt_manager = _PromptManager() # src.config.config config_mod = _stub_module("src.config.config") class _EmojiConfig: max_reg_num = 20 content_filtration = False filtration_prompt = "" steal_emoji = False do_replace = False check_interval = 1 class _BotConfig: nickname = "bot" class _ModelTaskConfig: vlm = None utils = None class _ModelConfig: model_task_config = _ModelTaskConfig() class _GlobalConfig: emoji = _EmojiConfig() bot = _BotConfig() config_mod.global_config = _GlobalConfig() config_mod.model_config = _ModelConfig() # src.llm_models.utils_model llm_mod = _stub_module("src.llm_models.utils_model") class LLMRequest: def __init__(self, *args, **kwargs): pass async def generate_response_async(self, *args, **kwargs): return "", None async def generate_response_for_image(self, *args, **kwargs): return "", None llm_mod.LLMRequest = LLMRequest # third-party stubs rich_traceback_mod = _stub_module("rich.traceback") def install(*_args, **_kwargs): pass rich_traceback_mod.install = install sqlmodel_mod = _stub_module("sqlmodel") def select(_model): return object() sqlmodel_mod.select = select levenshtein_mod = _stub_module("Levenshtein") def distance(a, b): return abs(len(str(a)) - len(str(b))) levenshtein_mod.distance = distance def import_emoji_manager_new(monkeypatch): _install_stub_modules(monkeypatch) file_path = Path(__file__).resolve().parents[2] / "src" / "chat" / "emoji_system" / "emoji_manager.py" spec = importlib.util.spec_from_file_location("emoji_manager", file_path) module = importlib.util.module_from_spec(spec) monkeypatch.setitem(sys.modules, "emoji_manager_new", module) spec.loader.exec_module(module) return module def _messages(calls): return [" ".join(map(str, args)) for args in calls] @pytest.mark.asyncio async def test_replace_an_emoji_by_llm_decision_no_delete(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager.emojis = [emoji_manager_new.MaiEmoji()] async def _generate_response_async(*_args, **_kwargs): return "不删除", None monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) result = await manager.replace_an_emoji_by_llm(emoji_manager_new.MaiEmoji()) assert result is False assert any("不删除任何表情包" in m for m in _messages(logger.info_calls)) @pytest.mark.asyncio async def test_replace_an_emoji_by_llm_decision_parse_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager.emojis = [emoji_manager_new.MaiEmoji()] async def _generate_response_async(*_args, **_kwargs): return "删除编号1", None def _bad_search(*_args, **_kwargs): raise RuntimeError("search failed") monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) monkeypatch.setattr(emoji_manager_new.re, "search", _bad_search) result = await manager.replace_an_emoji_by_llm(emoji_manager_new.MaiEmoji()) assert result is False assert any("解析决策结果时出错" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_replace_an_emoji_by_llm_decision_missing_number(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager.emojis = [emoji_manager_new.MaiEmoji()] async def _generate_response_async(*_args, **_kwargs): return "删除编号ABC", None monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) result = await manager.replace_an_emoji_by_llm(emoji_manager_new.MaiEmoji()) assert result is False assert any("未能解析删除编号" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_replace_an_emoji_by_llm_decision_index_out_of_range(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager.emojis = [emoji_manager_new.MaiEmoji()] async def _generate_response_async(*_args, **_kwargs): return "删除编号3", None monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) result = await manager.replace_an_emoji_by_llm(emoji_manager_new.MaiEmoji()) assert result is False assert any("无效的表情包编号" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_replace_an_emoji_by_llm_delete_failed(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager.emojis = [emoji_manager_new.MaiEmoji()] async def _generate_response_async(*_args, **_kwargs): return "删除编号1", None def _delete(_emoji): return False monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) monkeypatch.setattr(manager, "delete_emoji", _delete) result = await manager.replace_an_emoji_by_llm(emoji_manager_new.MaiEmoji()) assert result is False assert any("删除表情包失败" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_replace_an_emoji_by_llm_register_failed(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager.emojis = [emoji_manager_new.MaiEmoji()] async def _generate_response_async(*_args, **_kwargs): return "删除编号1", None def _delete(_emoji): return True def _register(_emoji): return False monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) monkeypatch.setattr(manager, "delete_emoji", _delete) monkeypatch.setattr(manager, "register_emoji_to_db", _register) result = await manager.replace_an_emoji_by_llm(emoji_manager_new.MaiEmoji()) assert result is False assert any("注册新表情包失败" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_replace_an_emoji_by_llm_success(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() old_emoji = emoji_manager_new.MaiEmoji() old_emoji.description = "old" manager.emojis = [old_emoji] async def _generate_response_async(*_args, **_kwargs): return "删除编号1", None def _delete(_emoji): return True def _register(_emoji): return True monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) monkeypatch.setattr(manager, "delete_emoji", _delete) monkeypatch.setattr(manager, "register_emoji_to_db", _register) new_emoji = emoji_manager_new.MaiEmoji() new_emoji.description = "new" result = await manager.replace_an_emoji_by_llm(new_emoji) assert result is True assert new_emoji in manager.emojis assert old_emoji not in manager.emojis assert any("成功替换并注册新表情包" in m for m in _messages(logger.info_calls)) def test_load_emojis_from_db_empty(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger class _Result: def scalars(self): return self def all(self): return [] class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): return _Result() def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) manager = emoji_manager_new.EmojiManager() manager.load_emojis_from_db() assert manager.emojis == [] assert manager._emoji_num == 0 assert any("成功加载" in m for m in _messages(logger.info_calls)) def test_load_emojis_from_db_partial_bad_records(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger class _Record: def __init__(self, record_id, full_path): self.id = record_id self.full_path = full_path records = [_Record(1, "bad"), _Record(2, "ok")] class _Result: def scalars(self): return self def all(self): return records class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): return _Result() def _get_db_session(): return _Session() def _from_db_instance(record): if record.id == 1: raise ValueError("bad record") emoji = emoji_manager_new.MaiEmoji() emoji.file_name = "ok" return emoji monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) monkeypatch.setattr(emoji_manager_new.MaiEmoji, "from_db_instance", staticmethod(_from_db_instance)) manager = emoji_manager_new.EmojiManager() manager.load_emojis_from_db() assert len(manager.emojis) == 1 assert manager.emojis[0].file_name == "ok" assert manager._emoji_num == 1 assert any("加载表情包记录时出错" in m for m in _messages(logger.error_calls)) assert any("成功加载" in m for m in _messages(logger.info_calls)) def test_load_emojis_from_db_execute_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): raise RuntimeError("execute failed") def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) manager = emoji_manager_new.EmojiManager() manager.emojis = [emoji_manager_new.MaiEmoji()] manager._emoji_num = 1 with pytest.raises(RuntimeError): manager.load_emojis_from_db() assert manager.emojis == [] assert manager._emoji_num == 0 assert any("不可恢复错误" in m for m in _messages(logger.critical_calls)) def test_load_emojis_from_db_get_db_session_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger def _get_db_session(): raise RuntimeError("get_db_session failed") monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) manager = emoji_manager_new.EmojiManager() manager.emojis = [emoji_manager_new.MaiEmoji()] manager._emoji_num = 1 with pytest.raises(RuntimeError): manager.load_emojis_from_db() assert manager.emojis == [] assert manager._emoji_num == 0 assert any("不可恢复错误" in m for m in _messages(logger.critical_calls)) def test_load_emojis_from_db_scalars_all_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger class _Result: def scalars(self): return self def all(self): raise RuntimeError("all failed") class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): return _Result() def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) manager = emoji_manager_new.EmojiManager() manager.emojis = [emoji_manager_new.MaiEmoji()] manager._emoji_num = 1 with pytest.raises(RuntimeError): manager.load_emojis_from_db() assert manager.emojis == [] assert manager._emoji_num == 0 assert any("不可恢复错误" in m for m in _messages(logger.critical_calls)) def test_register_emoji_to_db_invalid_object(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() result = manager.register_emoji_to_db(None) assert result is False assert any("无效的表情包对象" in m for m in _messages(logger.error_calls)) def test_register_emoji_to_db_wrong_type(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() result = manager.register_emoji_to_db(object()) assert result is False assert any("无效的表情包对象" in m for m in _messages(logger.error_calls)) def test_register_emoji_to_db_file_missing(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() emoji = emoji_manager_new.MaiEmoji() emoji.full_path = Path("/missing/file.png") result = manager.register_emoji_to_db(emoji) assert result is False assert any("表情包文件不存在" in m for m in _messages(logger.error_calls)) def test_register_emoji_to_db_move_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _DummyPath: def __init__(self): self._name = "a.png" self._exists = True def exists(self): return self._exists def replace(self, _target): raise RuntimeError("move failed") @property def name(self): return self._name emoji = emoji_manager_new.MaiEmoji() emoji.full_path = _DummyPath() emoji.file_name = "a.png" result = manager.register_emoji_to_db(emoji) assert result is False assert any("移动表情包文件时出错" in m for m in _messages(logger.error_calls)) def test_register_emoji_to_db_db_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _DummyPath: def __init__(self): self._name = "a.png" self._exists = True self._replaced = False def exists(self): return self._exists def replace(self, _target): self._replaced = True @property def name(self): return self._name emoji = emoji_manager_new.MaiEmoji() emoji.full_path = _DummyPath() emoji.file_name = "a.png" class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def add(self, _record): raise RuntimeError("db add failed") def flush(self): pass def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) result = manager.register_emoji_to_db(emoji) assert result is False assert any("注册到数据库时出错" in m for m in _messages(logger.error_calls)) def test_register_emoji_to_db_success(monkeypatch, tmp_path): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _DummyPath: def __init__(self, name): self._name = name self._exists = True self._replaced = False self._target = None def exists(self): return self._exists def replace(self, target): self._replaced = True self._target = target @property def name(self): return self._name emoji = emoji_manager_new.MaiEmoji() emoji.full_path = _DummyPath("a.png") emoji.file_name = "a.png" class _Record: def __init__(self): self.id = 123 self.is_registered = False self.is_banned = False self.register_time = None class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def add(self, _record): pass def flush(self): pass def _get_db_session(): return _Session() def _to_db_instance(self): return _Record() monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) monkeypatch.setattr(emoji_manager_new.MaiEmoji, "to_db_instance", _to_db_instance, raising=False) result = manager.register_emoji_to_db(emoji) assert result is True assert any("成功注册表情包到数据库" in m for m in _messages(logger.info_calls)) def test_delete_emoji_file_missing_and_db_record_missing(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _DummyPath: def __init__(self): self._name = "missing.png" def unlink(self): raise FileNotFoundError("missing") def exists(self): return False @property def name(self): return self._name class _Select: def filter_by(self, **_kwargs): return self def limit(self, _num): return self def _select(_model): return _Select() class _Result: def scalars(self): return self def first(self): return None class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): return _Result() def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "select", _select) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) emoji = emoji_manager_new.MaiEmoji() emoji.full_path = _DummyPath() emoji.file_name = "missing.png" emoji.file_hash = "hash-missing" result = manager.delete_emoji(emoji) assert result is True assert any("不存在" in m for m in _messages(logger.warning_calls)) assert any("未找到表情包记录" in m for m in _messages(logger.warning_calls)) def test_delete_emoji_file_delete_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _DummyPath: def __init__(self): self._name = "boom.png" def unlink(self): raise RuntimeError("unlink failed") @property def name(self): return self._name emoji = emoji_manager_new.MaiEmoji() emoji.full_path = _DummyPath() emoji.file_name = "boom.png" emoji.file_hash = "hash-boom" result = manager.delete_emoji(emoji) assert result is False assert any("删除表情包文件时出错" in m for m in _messages(logger.error_calls)) def test_delete_emoji_db_error_file_still_exists(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _DummyPath: def __init__(self): self._name = "keep.png" def unlink(self): return None def exists(self): return True @property def name(self): return self._name class _Select: def filter_by(self, **_kwargs): return self def limit(self, _num): return self def _select(_model): return _Select() class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): raise RuntimeError("db delete failed") def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "select", _select) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) emoji = emoji_manager_new.MaiEmoji() emoji.full_path = _DummyPath() emoji.file_name = "keep.png" emoji.file_hash = "hash-keep" result = manager.delete_emoji(emoji) assert result is False assert any("删除数据库记录时出错" in m for m in _messages(logger.error_calls)) assert any("数据库记录删除失败,但文件仍存在" in m for m in _messages(logger.warning_calls)) def test_delete_emoji_success(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _DummyPath: def __init__(self): self._name = "ok.png" self._deleted = False def unlink(self): self._deleted = True def exists(self): return not self._deleted @property def name(self): return self._name class _Select: def filter_by(self, **_kwargs): return self def limit(self, _num): return self def _select(_model): return _Select() class _Record: pass class _Result: def scalars(self): return self def first(self): return _Record() class _Session: def __init__(self): self.deleted = False def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): return _Result() def delete(self, _record): self.deleted = True def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "select", _select) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) emoji = emoji_manager_new.MaiEmoji() emoji.full_path = _DummyPath() emoji.file_name = "ok.png" emoji.file_hash = "hash-ok" result = manager.delete_emoji(emoji) assert result is True assert any("成功删除表情包文件" in m for m in _messages(logger.info_calls)) assert any("成功删除数据库中的表情包记录" in m for m in _messages(logger.info_calls)) def test_update_emoji_usage_success(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _Select: def filter_by(self, **_kwargs): return self def limit(self, _num): return self def _select(_model): return _Select() class _Record: def __init__(self): self.query_count = 2 self.last_used_time = None record = _Record() class _Result: def scalars(self): return self def first(self): return record class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): return _Result() def add(self, _record): self.added = True def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "select", _select) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash-ok" result = manager.update_emoji_usage(emoji) assert result is True assert emoji.query_count == 1 assert record.query_count == 1 assert any("成功记录表情包使用" in m for m in _messages(logger.info_calls)) def test_update_emoji_usage_missing_record(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _Select: def filter_by(self, **_kwargs): return self def limit(self, _num): return self def _select(_model): return _Select() class _Result: def scalars(self): return self def first(self): return None class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): return _Result() def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "select", _select) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash-missing" result = manager.update_emoji_usage(emoji) assert result is False assert any("未找到表情包记录" in m for m in _messages(logger.error_calls)) def test_update_emoji_usage_execute_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _Select: def filter_by(self, **_kwargs): return self def limit(self, _num): return self def _select(_model): return _Select() class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): raise RuntimeError("execute failed") def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "select", _select) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash-execute" result = manager.update_emoji_usage(emoji) assert result is False assert any("记录使用时出错" in m for m in _messages(logger.error_calls)) def test_update_emoji_usage_get_db_session_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() def _get_db_session(): raise RuntimeError("get_db_session failed") monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash-session" result = manager.update_emoji_usage(emoji) assert result is False assert any("记录使用时出错" in m for m in _messages(logger.error_calls)) def test_update_emoji_success(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _Select: def filter_by(self, **_kwargs): return self def limit(self, _num): return self def _select(_model): return _Select() class _Record: def __init__(self): self.description = None self.emotion = None class _Result: def scalars(self): return self def first(self): return _Record() class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): return _Result() def add(self, _record): self.added = True def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "select", _select) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash-update" emoji.description = "new-desc" emoji.emotion = ["a", "b"] result = manager.update_emoji(emoji) assert result is True assert any("成功更新表情包信息" in m for m in _messages(logger.info_calls)) def test_update_emoji_missing_record(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _Select: def filter_by(self, **_kwargs): return self def limit(self, _num): return self def _select(_model): return _Select() class _Result: def scalars(self): return self def first(self): return None class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): return _Result() def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "select", _select) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash-missing" result = manager.update_emoji(emoji) assert result is False assert any("未找到表情包记录" in m for m in _messages(logger.error_calls)) def test_update_emoji_execute_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() class _Select: def filter_by(self, **_kwargs): return self def limit(self, _num): return self def _select(_model): return _Select() class _Session: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def exec(self, _statement): raise RuntimeError("execute failed") def _get_db_session(): return _Session() monkeypatch.setattr(emoji_manager_new, "select", _select) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash-execute" result = manager.update_emoji(emoji) assert result is False assert any("更新数据库记录时出错" in m for m in _messages(logger.error_calls)) def test_update_emoji_get_db_session_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() def _get_db_session(): raise RuntimeError("get_db_session failed") monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash-session" result = manager.update_emoji(emoji) assert result is False assert any("更新数据库记录时出错" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_get_emoji_for_emotion_empty_list(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager.emojis = [] result = await manager.get_emoji_for_emotion("开心") assert result is None assert any("表情包列表为空" in m for m in _messages(logger.warning_calls)) @pytest.mark.asyncio async def test_get_emoji_for_emotion_no_matches(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager.emojis = [emoji_manager_new.MaiEmoji()] def _calc(_label): return [] monkeypatch.setattr(manager, "_calculate_emotion_similarity_list", _calc) result = await manager.get_emoji_for_emotion("无匹配") assert result is None assert any("未找到匹配的表情包" in m for m in _messages(logger.info_calls)) @pytest.mark.asyncio async def test_get_emoji_for_emotion_success_updates_usage(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() emoji1 = emoji_manager_new.MaiEmoji() emoji1.file_name = "e1.png" emoji1.emotion = ["开心"] emoji2 = emoji_manager_new.MaiEmoji() emoji2.file_name = "e2.png" emoji2.emotion = ["难过"] manager.emojis = [emoji1, emoji2] def _calc(_label): return [(emoji1, 0.9), (emoji2, 0.2)] monkeypatch.setattr(manager, "_calculate_emotion_similarity_list", _calc) monkeypatch.setattr(emoji_manager_new.random, "choice", lambda items: items[0]) called = {"emoji": None} def _update(emoji): called["emoji"] = emoji return True monkeypatch.setattr(manager, "update_emoji_usage", _update) result = await manager.get_emoji_for_emotion("开心") assert result is emoji1 assert called["emoji"] is emoji1 assert any("选中表情包" in m for m in _messages(logger.info_calls)) @pytest.mark.asyncio async def test_get_emoji_for_emotion_similarity_error_propagates(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) manager = emoji_manager_new.EmojiManager() manager.emojis = [emoji_manager_new.MaiEmoji()] def _calc(_label): raise RuntimeError("calc failed") monkeypatch.setattr(manager, "_calculate_emotion_similarity_list", _calc) with pytest.raises(RuntimeError): await manager.get_emoji_for_emotion("异常") @pytest.mark.asyncio async def test_build_emoji_description_calls_hash_and_sets_description(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger called = {"hash": False, "vlm": False} async def _calc(self): called["hash"] = True return True def _read_bytes(_path): return b"" async def _vlm_response(*_args, **_kwargs): called["vlm"] = True return "desc", None monkeypatch.setattr(emoji_manager_new.MaiEmoji, "calculate_hash_format", _calc, raising=False) monkeypatch.setattr(emoji_manager_new.MaiEmoji, "read_image_bytes", staticmethod(_read_bytes), raising=False) monkeypatch.setattr( emoji_manager_new.emoji_manager_vlm, "generate_response_for_image", _vlm_response, ) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = None emoji.image_format = "png" emoji.full_path = Path("/tmp/a.png") result, updated = await emoji_manager_new.EmojiManager().build_emoji_description(emoji) assert result is True assert updated.description == "desc" assert called["hash"] is True assert called["vlm"] is True assert any("成功为表情包构建描述" in m for m in _messages(logger.info_calls)) @pytest.mark.asyncio async def test_build_emoji_description_gif_conversion_error(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger def _read_bytes(_path): return b"" def _gif_to_static(_bytes): raise RuntimeError("gif fail") monkeypatch.setattr(emoji_manager_new.MaiEmoji, "read_image_bytes", staticmethod(_read_bytes), raising=False) monkeypatch.setattr(emoji_manager_new.ImageUtils, "gif_2_static_image", staticmethod(_gif_to_static)) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash" emoji.image_format = "gif" emoji.full_path = Path("/tmp/a.gif") result, updated = await emoji_manager_new.EmojiManager().build_emoji_description(emoji) assert result is False assert updated.description is None assert any("转换 GIF 图片时出错" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_build_emoji_description_content_filtration_reject(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger emoji_manager_new.global_config.emoji.content_filtration = True emoji_manager_new.global_config.emoji.filtration_prompt = "rule" def _read_bytes(_path): return b"" call_count = {"n": 0} async def _vlm_response(*_args, **_kwargs): call_count["n"] += 1 if call_count["n"] == 2: return "否", None return "desc", None monkeypatch.setattr(emoji_manager_new.MaiEmoji, "read_image_bytes", staticmethod(_read_bytes), raising=False) monkeypatch.setattr( emoji_manager_new.emoji_manager_vlm, "generate_response_for_image", _vlm_response, ) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash" emoji.image_format = "png" emoji.full_path = Path("/tmp/a.png") result, updated = await emoji_manager_new.EmojiManager().build_emoji_description(emoji) assert result is False assert updated.description is None assert any("表情包内容不符合要求" in m for m in _messages(logger.warning_calls)) @pytest.mark.asyncio async def test_build_emoji_description_content_filtration_pass(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger emoji_manager_new.global_config.emoji.content_filtration = True emoji_manager_new.global_config.emoji.filtration_prompt = "rule" def _read_bytes(_path): return b"" async def _vlm_response(prompt, *_args, **_kwargs): if "rule" in str(prompt): return "是", None return "desc", None monkeypatch.setattr(emoji_manager_new.MaiEmoji, "read_image_bytes", staticmethod(_read_bytes), raising=False) monkeypatch.setattr( emoji_manager_new.emoji_manager_vlm, "generate_response_for_image", _vlm_response, ) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash" emoji.image_format = "png" emoji.full_path = Path("/tmp/a.png") result, updated = await emoji_manager_new.EmojiManager().build_emoji_description(emoji) assert result is True assert updated.description == "desc" assert any("成功为表情包构建描述" in m for m in _messages(logger.info_calls)) @pytest.mark.asyncio async def test_build_emoji_description_vlm_exception_propagates(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) def _read_bytes(_path): return b"" async def _vlm_response(*_args, **_kwargs): raise RuntimeError("vlm failed") monkeypatch.setattr(emoji_manager_new.MaiEmoji, "read_image_bytes", staticmethod(_read_bytes), raising=False) monkeypatch.setattr( emoji_manager_new.emoji_manager_vlm, "generate_response_for_image", _vlm_response, ) emoji = emoji_manager_new.MaiEmoji() emoji.file_hash = "hash" emoji.image_format = "png" emoji.full_path = Path("/tmp/a.png") with pytest.raises(RuntimeError): await emoji_manager_new.EmojiManager().build_emoji_description(emoji) @pytest.mark.asyncio async def test_build_emoji_emotion_description_missing(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger emoji = emoji_manager_new.MaiEmoji() emoji.description = None result, updated = await emoji_manager_new.EmojiManager().build_emoji_emotion(emoji) assert result is False assert updated.emotion is None assert any("表情包描述为空" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_build_emoji_emotion_llm_exception_propagates(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) async def _generate_response_async(*_args, **_kwargs): raise RuntimeError("llm failed") monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) emoji = emoji_manager_new.MaiEmoji() emoji.description = "desc" with pytest.raises(RuntimeError): await emoji_manager_new.EmojiManager().build_emoji_emotion(emoji) @pytest.mark.asyncio async def test_build_emoji_emotion_empty_result(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger async def _generate_response_async(*_args, **_kwargs): return " , , ", None monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) emoji = emoji_manager_new.MaiEmoji() emoji.description = "desc" result, updated = await emoji_manager_new.EmojiManager().build_emoji_emotion(emoji) assert result is True assert updated.emotion == [] assert any("成功为表情包构建情感标签" in m for m in _messages(logger.info_calls)) @pytest.mark.asyncio async def test_build_emoji_emotion_more_than_five_random_sample(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger async def _generate_response_async(*_args, **_kwargs): return "a,b,c,d,e,f", None monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) monkeypatch.setattr(emoji_manager_new.random, "sample", lambda items, _k: items[:3]) emoji = emoji_manager_new.MaiEmoji() emoji.description = "desc" result, updated = await emoji_manager_new.EmojiManager().build_emoji_emotion(emoji) assert result is True assert updated.emotion == ["a", "b", "c"] assert any("成功为表情包构建情感标签" in m for m in _messages(logger.info_calls)) @pytest.mark.asyncio async def test_build_emoji_emotion_three_items_random_sample(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger async def _generate_response_async(*_args, **_kwargs): return "a,b,c", None monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) monkeypatch.setattr(emoji_manager_new.random, "sample", lambda items, _k: items[:2]) emoji = emoji_manager_new.MaiEmoji() emoji.description = "desc" result, updated = await emoji_manager_new.EmojiManager().build_emoji_emotion(emoji) assert result is True assert updated.emotion == ["a", "b"] assert any("成功为表情包构建情感标签" in m for m in _messages(logger.info_calls)) def test_check_emoji_file_integrity_no_issues(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger class _DummyPath: def __init__(self, name): self._name = name self._exists = True def exists(self): return self._exists @property def name(self): return self._name manager = emoji_manager_new.EmojiManager() emoji = emoji_manager_new.MaiEmoji() emoji.file_name = "ok.png" emoji.full_path = _DummyPath("ok.png") emoji.is_deleted = False emoji.description = "desc" manager.emojis = [emoji] manager._emoji_num = 1 called = {"count": 0} def _delete(_emoji): called["count"] += 1 return True monkeypatch.setattr(manager, "delete_emoji", _delete) manager.check_emoji_file_integrity() assert manager.emojis == [emoji] assert manager._emoji_num == 1 assert called["count"] == 0 assert logger.warning_calls == [] assert any("完整性检查完成" in m for m in _messages(logger.info_calls)) def test_check_emoji_file_integrity_removes_invalid_records(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger class _DummyPath: def __init__(self, name, exists=True): self._name = name self._exists = exists def exists(self): return self._exists @property def name(self): return self._name manager = emoji_manager_new.EmojiManager() missing_file = emoji_manager_new.MaiEmoji() missing_file.file_name = "missing.png" missing_file.full_path = _DummyPath("missing.png", exists=False) missing_file.description = "desc" deleted_flag = emoji_manager_new.MaiEmoji() deleted_flag.file_name = "deleted.png" deleted_flag.full_path = _DummyPath("deleted.png", exists=True) deleted_flag.is_deleted = True deleted_flag.description = "desc" missing_desc = emoji_manager_new.MaiEmoji() missing_desc.file_name = "nodesc.png" missing_desc.full_path = _DummyPath("nodesc.png", exists=True) missing_desc.description = None manager.emojis = [missing_file, deleted_flag, missing_desc] manager._emoji_num = 3 deleted = [] def _delete(emoji): deleted.append(emoji.file_name) return True monkeypatch.setattr(manager, "delete_emoji", _delete) manager.check_emoji_file_integrity() assert manager.emojis == [] assert manager._emoji_num == 0 assert set(deleted) == {"missing.png", "deleted.png", "nodesc.png"} messages = _messages(logger.warning_calls) assert any("文件缺失" in m for m in messages) assert any("标记为已删除" in m for m in messages) assert any("缺失描述" in m for m in messages) assert any("成功删除缺失文件的表情包记录" in m for m in _messages(logger.info_calls)) assert any("删除了 3 条记录" in m for m in _messages(logger.info_calls)) def test_check_emoji_file_integrity_delete_failed(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger class _DummyPath: def __init__(self, name): self._name = name self._exists = False def exists(self): return self._exists @property def name(self): return self._name manager = emoji_manager_new.EmojiManager() emoji = emoji_manager_new.MaiEmoji() emoji.file_name = "bad.png" emoji.full_path = _DummyPath("bad.png") emoji.description = "desc" manager.emojis = [emoji] manager._emoji_num = 1 def _delete(_emoji): return False monkeypatch.setattr(manager, "delete_emoji", _delete) manager.check_emoji_file_integrity() assert manager.emojis == [emoji] assert manager._emoji_num == 1 assert any("表情包文件缺失" in m for m in _messages(logger.warning_calls)) assert any("删除缺失文件的表情包记录失败" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_build_emoji_emotion_two_items_no_sample(monkeypatch): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger async def _generate_response_async(*_args, **_kwargs): return "a, b", None monkeypatch.setattr( emoji_manager_new.emoji_manager_emotion_judge_llm, "generate_response_async", _generate_response_async, ) emoji = emoji_manager_new.MaiEmoji() emoji.description = "desc" result, updated = await emoji_manager_new.EmojiManager().build_emoji_emotion(emoji) assert result is True assert updated.emotion == ["a", "b"] assert any("成功为表情包构建情感标签" in m for m in _messages(logger.info_calls)) @pytest.mark.asyncio async def test_register_emoji_by_filename_file_missing(monkeypatch, tmp_path): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() missing_file = tmp_path / "missing.png" result = await manager.register_emoji_by_filename(missing_file) assert result is False assert any("表情包文件不存在" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_register_emoji_by_filename_create_object_error(monkeypatch, tmp_path): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() file_path = tmp_path / "ok.png" file_path.write_bytes(b"") class _BadEmoji: def __init__(self, *args, **kwargs): raise RuntimeError("create failed") monkeypatch.setattr(emoji_manager_new, "MaiEmoji", _BadEmoji) result = await manager.register_emoji_by_filename(file_path) assert result is False assert any("创建表情包对象时出错" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_register_emoji_by_filename_hash_format_failed(monkeypatch, tmp_path): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() file_path = tmp_path / "hash.png" file_path.write_bytes(b"") class _Emoji(emoji_manager_new.MaiEmoji): async def calculate_hash_format(self): return False monkeypatch.setattr(emoji_manager_new, "MaiEmoji", _Emoji) result = await manager.register_emoji_by_filename(file_path) assert result is False assert any("计算表情包哈希值和格式失败" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_register_emoji_by_filename_duplicate_hash(monkeypatch, tmp_path): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() file_path = tmp_path / "dup.png" file_path.write_bytes(b"") class _Emoji(emoji_manager_new.MaiEmoji): async def calculate_hash_format(self): self.file_hash = "hash-dup" self.full_path = file_path return True monkeypatch.setattr(emoji_manager_new, "MaiEmoji", _Emoji) existing = emoji_manager_new.MaiEmoji() existing.file_name = "exist.png" monkeypatch.setattr(manager, "get_emoji_by_hash", lambda _h: existing) result = await manager.register_emoji_by_filename(file_path) assert result is False assert any("表情包已存在" in m for m in _messages(logger.warning_calls)) @pytest.mark.asyncio async def test_register_emoji_by_filename_build_description_failed(monkeypatch, tmp_path): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() file_path = tmp_path / "desc.png" file_path.write_bytes(b"") class _Emoji(emoji_manager_new.MaiEmoji): async def calculate_hash_format(self): self.file_hash = "hash-desc" self.full_path = file_path return True monkeypatch.setattr(emoji_manager_new, "MaiEmoji", _Emoji) async def _build_desc(_e): return False, _e monkeypatch.setattr(manager, "build_emoji_description", _build_desc) result = await manager.register_emoji_by_filename(file_path) assert result is False assert any("构建表情包描述失败" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_register_emoji_by_filename_build_emotion_failed(monkeypatch, tmp_path): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() file_path = tmp_path / "emo.png" file_path.write_bytes(b"") class _Emoji(emoji_manager_new.MaiEmoji): async def calculate_hash_format(self): self.file_hash = "hash-emo" self.full_path = file_path return True monkeypatch.setattr(emoji_manager_new, "MaiEmoji", _Emoji) async def _build_desc(_e): return True, _e async def _build_emo(_e): return False, _e monkeypatch.setattr(manager, "build_emoji_description", _build_desc) monkeypatch.setattr(manager, "build_emoji_emotion", _build_emo) result = await manager.register_emoji_by_filename(file_path) assert result is False assert any("构建表情包情感标签失败" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_register_emoji_by_filename_capacity_replace_failed(monkeypatch, tmp_path): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager._emoji_num = 1 emoji_manager_new.global_config.emoji.max_reg_num = 1 file_path = tmp_path / "full.png" file_path.write_bytes(b"") class _Emoji(emoji_manager_new.MaiEmoji): async def calculate_hash_format(self): self.file_hash = "hash-full" self.full_path = file_path return True monkeypatch.setattr(emoji_manager_new, "MaiEmoji", _Emoji) async def _build_desc(_e): return True, _e async def _build_emo(_e): return True, _e async def _replace(_e): return False monkeypatch.setattr(manager, "build_emoji_description", _build_desc) monkeypatch.setattr(manager, "build_emoji_emotion", _build_emo) monkeypatch.setattr(manager, "replace_an_emoji_by_llm", _replace) result = await manager.register_emoji_by_filename(file_path) assert result is False assert any("数量已达上限" in m for m in _messages(logger.warning_calls)) assert any("替换表情包失败" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_register_emoji_by_filename_capacity_replace_success(monkeypatch, tmp_path): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager._emoji_num = 1 emoji_manager_new.global_config.emoji.max_reg_num = 1 file_path = tmp_path / "full-ok.png" file_path.write_bytes(b"") class _Emoji(emoji_manager_new.MaiEmoji): async def calculate_hash_format(self): self.file_hash = "hash-full-ok" self.full_path = file_path return True monkeypatch.setattr(emoji_manager_new, "MaiEmoji", _Emoji) async def _build_desc(_e): return True, _e async def _build_emo(_e): return True, _e async def _replace(_e): return True monkeypatch.setattr(manager, "build_emoji_description", _build_desc) monkeypatch.setattr(manager, "build_emoji_emotion", _build_emo) monkeypatch.setattr(manager, "replace_an_emoji_by_llm", _replace) result = await manager.register_emoji_by_filename(file_path) assert result is True assert any("数量已达上限" in m for m in _messages(logger.warning_calls)) @pytest.mark.asyncio async def test_register_emoji_by_filename_register_db_failed(monkeypatch, tmp_path): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager._emoji_num = 0 emoji_manager_new.global_config.emoji.max_reg_num = 10 file_path = tmp_path / "db-fail.png" file_path.write_bytes(b"") class _Emoji(emoji_manager_new.MaiEmoji): async def calculate_hash_format(self): self.file_hash = "hash-db-fail" self.full_path = file_path return True monkeypatch.setattr(emoji_manager_new, "MaiEmoji", _Emoji) async def _build_desc(_e): return True, _e async def _build_emo(_e): return True, _e monkeypatch.setattr(manager, "build_emoji_description", _build_desc) monkeypatch.setattr(manager, "build_emoji_emotion", _build_emo) monkeypatch.setattr(manager, "register_emoji_to_db", lambda _e: False) result = await manager.register_emoji_by_filename(file_path) assert result is False assert any("注册表情包到数据库失败" in m for m in _messages(logger.error_calls)) @pytest.mark.asyncio async def test_register_emoji_by_filename_register_db_success(monkeypatch, tmp_path): emoji_manager_new = import_emoji_manager_new(monkeypatch) logger = emoji_manager_new.logger manager = emoji_manager_new.EmojiManager() manager._emoji_num = 0 emoji_manager_new.global_config.emoji.max_reg_num = 10 file_path = tmp_path / "db-ok.png" file_path.write_bytes(b"") class _Emoji(emoji_manager_new.MaiEmoji): async def calculate_hash_format(self): self.file_hash = "hash-db-ok" self.full_path = file_path self.file_name = "db-ok.png" return True monkeypatch.setattr(emoji_manager_new, "MaiEmoji", _Emoji) async def _build_desc(_e): return True, _e async def _build_emo(_e): return True, _e monkeypatch.setattr(manager, "build_emoji_description", _build_desc) monkeypatch.setattr(manager, "build_emoji_emotion", _build_emo) monkeypatch.setattr(manager, "register_emoji_to_db", lambda _e: True) result = await manager.register_emoji_by_filename(file_path) assert result is True assert manager._emoji_num == 1 assert len(manager.emojis) == 1 assert any("成功注册新表情包" in m for m in _messages(logger.info_calls))