test_channel_api.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. from __future__ import annotations
  2. import json
  3. from datetime import UTC, datetime, timedelta
  4. from types import SimpleNamespace
  5. from unittest.mock import AsyncMock
  6. from aiohttp import CookieJar
  7. from aiohttp.test_utils import TestClient, TestServer
  8. from pyrogram.enums import ChatMemberStatus, ChatType
  9. async def test_gif_upload_uses_telegram_returned_media_type(app_modules, monkeypatch):
  10. api = app_modules.load("wbb.admin.api")
  11. monkeypatch.setattr(api, "MESSAGE_DUMP_CHAT", -100999)
  12. monkeypatch.setattr(
  13. api.telegram_app,
  14. "send_animation",
  15. AsyncMock(return_value=SimpleNamespace(
  16. animation=None, document=SimpleNamespace(file_id="gif-as-document")
  17. )),
  18. raising=False,
  19. )
  20. class Part:
  21. name = "file"
  22. filename = "sample.gif"
  23. headers = {"Content-Type": "image/gif"}
  24. def __init__(self):
  25. self.sent = False
  26. async def read_chunk(self, _size):
  27. if self.sent:
  28. return b""
  29. self.sent = True
  30. return b"GIF89a"
  31. class Reader:
  32. async def next(self):
  33. return Part()
  34. class Request(dict):
  35. async def multipart(self):
  36. return Reader()
  37. response = await api.AdminApi().upload_media(Request())
  38. result = json.loads(response.text)["data"]
  39. assert result["type"] == "document"
  40. assert result["file_id"] == "gif-as-document"
  41. assert result["mime_type"] == "image/gif"
  42. async def test_channel_api_onboarding_schedule_and_role_gate(app_modules, monkeypatch):
  43. chat_id = -100123
  44. app_modules.app.chats[chat_id] = SimpleNamespace(
  45. id=chat_id, type=ChatType.CHANNEL, title="运营频道", username="ops_channel",
  46. description="简介", permissions=None, photo=None,
  47. )
  48. app_modules.app.members[(chat_id, 999)] = SimpleNamespace(
  49. status=ChatMemberStatus.ADMINISTRATOR,
  50. privileges=SimpleNamespace(
  51. can_manage_chat=True, can_post_messages=True, can_pin_messages=True,
  52. can_change_info=True, can_edit_messages=True, can_delete_messages=True,
  53. can_promote_members=True,
  54. ),
  55. )
  56. api = app_modules.load("wbb.admin.api")
  57. application = api.build_admin_application()
  58. await application["admin_api"].initialize()
  59. client = TestClient(TestServer(application), cookie_jar=CookieJar(unsafe=True))
  60. await client.start_server()
  61. try:
  62. login = await client.post(
  63. "/api/admin/v1/auth/login",
  64. json={"username": "admin", "password": "qwe0.123456"},
  65. )
  66. token = (await login.json())["data"]["csrf_token"]
  67. changed = await client.put(
  68. "/api/admin/v1/auth/password",
  69. headers={"X-CSRF-Token": token},
  70. json={"current_password": "qwe0.123456", "new_password": "changed-pass-123"},
  71. )
  72. token = (await changed.json())["data"]["csrf_token"]
  73. headers = {"X-CSRF-Token": token}
  74. connected = await client.post(
  75. "/api/admin/v1/channels", headers=headers,
  76. json={"chat_id": str(chat_id), "confirm": True},
  77. )
  78. assert connected.status == 201
  79. assert (await connected.json())["data"]["type"] == "channel"
  80. listed = await client.get("/api/admin/v1/channels")
  81. assert [item["chat_id"] for item in (await listed.json())["data"]["items"]] == [str(chat_id)]
  82. groups = await client.get("/api/admin/v1/chats")
  83. assert (await groups.json())["data"]["total"] == 0
  84. async def get_chat_members(_chat_id, *, query, limit, filter):
  85. assert query == "Keven" and limit == 20
  86. yield SimpleNamespace(
  87. status=ChatMemberStatus.MEMBER, privileges=None, custom_title=None, until_date=None,
  88. user=SimpleNamespace(
  89. id=42, username="keven", first_name="Keven", last_name=None,
  90. is_bot=False, is_deleted=False,
  91. ),
  92. )
  93. monkeypatch.setattr(app_modules.app, "get_chat_members", get_chat_members, raising=False)
  94. candidates = await client.get(
  95. f"/api/admin/v1/channels/{chat_id}/admins/candidates?query=Keven&limit=20"
  96. )
  97. assert candidates.status == 200
  98. assert (await candidates.json())["data"]["items"][0]["user"]["id"] == "42"
  99. scheduled = await client.post(
  100. f"/api/admin/v1/channels/{chat_id}/posts", headers=headers,
  101. json={
  102. "text": "稍后发布", "publish_at": (datetime.now(UTC) + timedelta(hours=1)).isoformat(),
  103. "confirm": True,
  104. },
  105. )
  106. assert scheduled.status == 201
  107. post = (await scheduled.json())["data"]
  108. assert post["status"] == "scheduled" and post["message_id"] is None
  109. updated = await client.patch(
  110. f"/api/admin/v1/channels/{chat_id}/posts/{post['post_id']}", headers=headers,
  111. json={"text": "更新的帖子", "confirm": True},
  112. )
  113. assert updated.status == 200
  114. assert (await updated.json())["data"]["text"] == "更新的帖子"
  115. canceled = await client.delete(
  116. f"/api/admin/v1/channels/{chat_id}/posts/{post['post_id']}", headers=headers,
  117. json={"confirm": True},
  118. )
  119. assert (await canceled.json())["data"]["status"] == "canceled"
  120. app_modules.wbb.BOT_PERMISSIONS = {"channel.profile"}
  121. denied = await client.get(f"/api/admin/v1/channels/{chat_id}/posts")
  122. assert denied.status == 403
  123. assert (await denied.json())["error"]["code"] == "bot_role_permission_denied"
  124. denied_candidates = await client.get(
  125. f"/api/admin/v1/channels/{chat_id}/admins/candidates"
  126. )
  127. assert denied_candidates.status == 403
  128. finally:
  129. await client.close()