Add standalone GusVoice AWG bot and Linux installer
This commit is contained in:
commit
38c6a1a682
23 files changed
+2924
No files matched your search
@@ -0,0 +1,115 @@
|
||||
"""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()
|
||||
Reference in new issue
Block a user