test_chat_management.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. from __future__ import annotations
  2. from unittest.mock import AsyncMock
  3. import pytest
  4. from pyrogram.enums import ChatMemberStatus
  5. from pyrogram.errors import ChatNotModified
  6. def chat_overview() -> dict[str, object]:
  7. return {
  8. "chat_id": -100,
  9. "title": "旧标题",
  10. "description": "旧描述",
  11. "type": "supergroup",
  12. "accessible": True,
  13. "bot_status": "administrator",
  14. "bot_privileges": ["can_change_info"],
  15. "permissions": {},
  16. }
  17. async def test_profile_update_does_not_refresh_after_success(app_modules, monkeypatch):
  18. service = app_modules.load("wbb.services.chat_management")
  19. ensure_permission = AsyncMock()
  20. sync_managed_chat = AsyncMock(return_value=chat_overview())
  21. set_chat_title = AsyncMock()
  22. set_chat_description = AsyncMock()
  23. monkeypatch.setattr(service, "ensure_permission", ensure_permission)
  24. monkeypatch.setattr(service, "sync_managed_chat", sync_managed_chat)
  25. monkeypatch.setattr(service.app, "set_chat_title", set_chat_title, raising=False)
  26. monkeypatch.setattr(
  27. service.app, "set_chat_description", set_chat_description, raising=False
  28. )
  29. result = await service.update_chat_profile(
  30. -100, title="新标题", description="新描述"
  31. )
  32. ensure_permission.assert_awaited_once_with(
  33. -100, "can_change_info", actor_id=None
  34. )
  35. sync_managed_chat.assert_awaited_once_with(-100)
  36. set_chat_title.assert_awaited_once_with(-100, "新标题")
  37. set_chat_description.assert_awaited_once_with(-100, "新描述")
  38. assert result["title"] == "新标题"
  39. assert result["description"] == "新描述"
  40. async def test_profile_update_skips_values_already_applied(app_modules, monkeypatch):
  41. service = app_modules.load("wbb.services.chat_management")
  42. monkeypatch.setattr(service, "ensure_permission", AsyncMock())
  43. monkeypatch.setattr(
  44. service, "sync_managed_chat", AsyncMock(return_value=chat_overview())
  45. )
  46. set_chat_title = AsyncMock()
  47. set_chat_description = AsyncMock()
  48. monkeypatch.setattr(service.app, "set_chat_title", set_chat_title, raising=False)
  49. monkeypatch.setattr(
  50. service.app, "set_chat_description", set_chat_description, raising=False
  51. )
  52. result = await service.update_chat_profile(
  53. -100, title=" 旧标题 ", description="旧描述"
  54. )
  55. set_chat_title.assert_not_awaited()
  56. set_chat_description.assert_not_awaited()
  57. assert result["title"] == "旧标题"
  58. assert result["description"] == "旧描述"
  59. async def test_profile_update_accepts_chat_not_modified(app_modules, monkeypatch):
  60. service = app_modules.load("wbb.services.chat_management")
  61. monkeypatch.setattr(service, "ensure_permission", AsyncMock())
  62. monkeypatch.setattr(
  63. service, "sync_managed_chat", AsyncMock(return_value=chat_overview())
  64. )
  65. set_chat_title = AsyncMock(side_effect=ChatNotModified())
  66. monkeypatch.setattr(service.app, "set_chat_title", set_chat_title, raising=False)
  67. result = await service.update_chat_profile(-100, title="新标题")
  68. assert result["title"] == "新标题"
  69. async def test_profile_update_wraps_real_telegram_failure(app_modules, monkeypatch):
  70. service = app_modules.load("wbb.services.chat_management")
  71. monkeypatch.setattr(service, "ensure_permission", AsyncMock())
  72. monkeypatch.setattr(
  73. service, "sync_managed_chat", AsyncMock(return_value=chat_overview())
  74. )
  75. monkeypatch.setattr(
  76. service.app,
  77. "set_chat_title",
  78. AsyncMock(side_effect=RuntimeError("telegram unavailable")),
  79. raising=False,
  80. )
  81. with pytest.raises(service.ChatManagementError) as caught:
  82. await service.update_chat_profile(-100, title="新标题")
  83. assert caught.value.code == "chat_profile_update_failed"
  84. assert caught.value.status == 502
  85. assert str(caught.value) == "Telegram 未能更新群标题,请稍后重试。"
  86. async def test_automation_settings_store_configurable_risk_rules(
  87. app_modules, monkeypatch
  88. ):
  89. service = app_modules.load("wbb.services.chat_management")
  90. monkeypatch.setattr(service, "ensure_permission", AsyncMock())
  91. result = await service.apply_automation_settings(
  92. -100,
  93. {
  94. "risk_rules": [
  95. {
  96. "rule_id": "ads",
  97. "name": "广告内容",
  98. "enabled": True,
  99. "keywords": ["广告", "做爱"],
  100. "match_images": True,
  101. "match_links": True,
  102. "actions": ["delete", "kick"],
  103. "duration_seconds": 7200,
  104. },
  105. {
  106. "rule_id": "disabled-links",
  107. "name": "停用链接规则",
  108. "enabled": False,
  109. "keywords": ["未生效"],
  110. "match_links": True,
  111. "actions": ["delete"],
  112. "duration_seconds": 3600,
  113. },
  114. ],
  115. "identity_monitor": {"enabled": True, "notify_in_chat": True},
  116. },
  117. )
  118. assert result["risk_rules"][0] == {
  119. "rule_id": "ads",
  120. "name": "广告内容",
  121. "enabled": True,
  122. "keywords": ["做爱", "广告"],
  123. "match_images": True,
  124. "match_links": True,
  125. "actions": ["delete", "kick"],
  126. "duration_seconds": 7200,
  127. }
  128. assert len(result["risk_rules"]) == 2
  129. assert result["identity_monitor"] == {"enabled": True, "notify_in_chat": True}
  130. assert await service.get_blacklisted_words(-100) == ["做爱", "广告"]
  131. async def test_automation_settings_reject_conflicting_member_actions(
  132. app_modules, monkeypatch
  133. ):
  134. service = app_modules.load("wbb.services.chat_management")
  135. monkeypatch.setattr(service, "ensure_permission", AsyncMock())
  136. with pytest.raises(service.ChatManagementError) as caught:
  137. await service.apply_automation_settings(
  138. -100,
  139. {
  140. "risk_rules": [
  141. {
  142. "rule_id": "ads",
  143. "name": "广告内容",
  144. "enabled": True,
  145. "keywords": ["广告"],
  146. "actions": ["warn", "ban"],
  147. }
  148. ]
  149. },
  150. )
  151. assert caught.value.code == "invalid_risk_rules"
  152. async def test_automation_settings_wrap_legacy_policy_and_blacklist(app_modules):
  153. service = app_modules.load("wbb.services.chat_management")
  154. dbadmin = app_modules.load("wbb.utils.dbadmin")
  155. dbfunctions = app_modules.load("wbb.utils.dbfunctions")
  156. await dbadmin.update_managed_chat_settings(
  157. -100,
  158. {
  159. "risk_control": {
  160. "enabled": True,
  161. "keywords": ["广告"],
  162. "match_links": True,
  163. "actions": ["delete", "ban"],
  164. }
  165. },
  166. )
  167. await dbfunctions.save_blacklist_filter(-100, "推广")
  168. result = await service.get_automation_settings(-100)
  169. assert "risk_control" not in result
  170. assert result["risk_rules"] == [
  171. {
  172. "rule_id": "legacy-risk-control",
  173. "name": "原风控策略",
  174. "enabled": True,
  175. "keywords": ["广告", "推广"],
  176. "match_images": False,
  177. "match_links": True,
  178. "actions": ["delete", "ban"],
  179. "duration_seconds": 3600,
  180. }
  181. ]
  182. async def test_member_search_accepts_display_name_from_recent_activity(
  183. app_modules, monkeypatch
  184. ):
  185. service = app_modules.load("wbb.services.chat_management")
  186. dbadmin = app_modules.load("wbb.utils.dbadmin")
  187. await dbadmin.upsert_recent_chat_member(
  188. chat_id=-100,
  189. user_id=42,
  190. username=None,
  191. first_name="小明",
  192. last_name="同学",
  193. is_bot=False,
  194. )
  195. async def no_telegram_results():
  196. if False:
  197. yield None
  198. monkeypatch.setattr(
  199. service.app,
  200. "get_chat_members",
  201. lambda *_args, **_kwargs: no_telegram_results(),
  202. raising=False,
  203. )
  204. app_modules.app.members[(-100, 42)] = type(
  205. "Member",
  206. (),
  207. {
  208. "status": ChatMemberStatus.MEMBER,
  209. "privileges": None,
  210. "user": type(
  211. "User",
  212. (),
  213. {
  214. "id": 42,
  215. "username": None,
  216. "first_name": "小明",
  217. "last_name": "同学",
  218. "is_bot": False,
  219. "is_deleted": False,
  220. },
  221. )(),
  222. "custom_title": None,
  223. "until_date": None,
  224. },
  225. )()
  226. results = await service.search_chat_members(-100, "小明")
  227. assert [item["user"]["id"] for item in results] == ["42"]