"""Exercise the actual vendored SDK and native bot against a local HTTP API double.""" import asyncio from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import json from pathlib import Path import sys import tempfile import threading import unittest from unittest.mock import patch ROOT = Path(__file__).resolve().parents[1] sys.path[:0] = [str(ROOT), str(ROOT / "vendor")] from gusvoice_bot import AsyncClient, Client, GusVoiceApiError from awg_gus.bot import Bot from awg_gus.core import Delivery, State from test_bot import CHAT, MSG, OWNER, FakeAwg, callback, message class Handler(BaseHTTPRequestHandler): def log_message(self, *args): pass def do_POST(self): body = self.rfile.read(int(self.headers.get("Content-Length", "0"))) method = self.path.rsplit("/", 1)[-1] self.server.received.append((method, self.headers, body)) if self.headers.get("Authorization") != "Bot gvb_test": code, result = 401, {"ok": False, "error_code": 401, "description": "Unauthorized"} elif self.server.fail_next: self.server.fail_next = False code, result = 429, {"ok": False, "error_code": 429, "description": "slow down", "retry_after": 0} else: code = 200 data = json.loads(body) if "application/json" in self.headers.get("Content-Type", "") else {} if method in {"sendMessage", "editMessageText"}: if data and (not isinstance(data.get("chat_id"), str) or data.get("parse_mode") not in {None, "HTML"}): raise AssertionError("Wrong native API contract") payload = {"message_id": MSG, "chat": {"id": CHAT, "type": "dm"}, "text": data.get("text", "")} elif method == "getUpdates": payload = [] elif method == "getWebhookInfo": payload = {"url": ""} else: payload = True result = {"ok": True, "result": payload} raw = json.dumps(result).encode() self.send_response(code) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(raw))) self.end_headers() self.wfile.write(raw) class TransportTests(unittest.IsolatedAsyncioTestCase): def setUp(self): self.server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) self.server.received = [] self.server.fail_next = False self.thread = threading.Thread(target=self.server.serve_forever, daemon=True) self.thread.start() self.url = f"http://127.0.0.1:{self.server.server_port}" def tearDown(self): self.server.shutdown() self.server.server_close() self.thread.join() async def test_real_sdk_native_menu_and_config_upload(self): with tempfile.TemporaryDirectory() as tmp: state = State(Path(tmp) / "state.db", "test") client = AsyncClient("gvb_test", self.url, discover=False) bot = Bot({"owners": [OWNER]}, FakeAwg(), Delivery(client), state) try: await bot.process(message(1, "/menu")) await asyncio.gather(*bot.tasks.values()) with patch("awg_gus.bot.shutil.which", return_value=None): await bot.process(callback(2, "conf:alice")) await asyncio.gather(*bot.tasks.values()) methods = [r[0] for r in self.server.received] self.assertIn("answerCallbackQuery", methods) messages = [r for r in self.server.received if r[0] == "sendMessage"] self.assertEqual(json.loads(messages[0][2])["chat_id"], CHAT) self.assertIn(b'name="files[0]"', messages[-1][2]) self.assertIn(b'filename="alice.conf"', messages[-1][2]) self.assertIn("multipart/form-data", messages[-1][1]["Content-Type"]) finally: state.db.close() async def test_sdk_429_retry_preserves_uuid_and_payload(self): self.server.fail_next = True client = AsyncClient("gvb_test", self.url, discover=False) await client.send_message(CHAT, "hello") self.assertEqual(len(self.server.received), 2) self.assertEqual(self.server.received[0][2], self.server.received[1][2]) async def test_sdk_unauthorized_not_retried(self): client = AsyncClient("gvb_wrong", self.url, discover=False) with self.assertRaises(GusVoiceApiError) as error: await client.send_message(CHAT, "hello") self.assertEqual(error.exception.code, 401) self.assertEqual(len(self.server.received), 1) async def test_delivery_edits_by_uuid_without_arithmetic(self): delivery = Delivery(AsyncClient("gvb_test", self.url, discover=False)) await delivery.show(CHAT, "new screen", [[{"text": "Menu", "callback_data": "home"}]], MSG) method, _, raw = self.server.received[0] self.assertEqual(method, "editMessageText") payload = json.loads(raw) self.assertEqual(payload["message_id"], MSG) self.assertTrue(payload["reply_markup"]["inline_keyboard"]) if __name__ == "__main__": unittest.main()