116 lines
5.1 KiB
Python
116 lines
5.1 KiB
Python
"""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()
|