chore: add tests for redis infra, webhook queue and split-arch entrypoints
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
"""Verifies that the admin sync endpoint hands work to the worker via Redis.
|
||||
|
||||
After the container split, /api/admin/sync no longer runs ``perform_sync``
|
||||
in-process; it enqueues a ``panel_sync`` event onto the webhook queue and
|
||||
returns a fast ack. These tests pin that contract.
|
||||
"""
|
||||
|
||||
import json
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, List
|
||||
from unittest.mock import patch
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
# Importing the facade populates each admin_api_impl submodule's globals with
|
||||
# helpers like ``_require_admin_user_id`` and ``_ok``/``_error``. Without this
|
||||
# side effect, ``sync_module._require_admin_user_id`` does not exist yet.
|
||||
import bot.app.web.subscription_webapp # noqa: F401
|
||||
from bot.app.web.admin_api_impl import sync as sync_module
|
||||
|
||||
|
||||
class _FakeRequest:
|
||||
"""The shape ``admin_sync_route`` reads from the aiohttp request."""
|
||||
|
||||
def __init__(self, settings: SimpleNamespace, admin_telegram_id: int = 42) -> None:
|
||||
self.app = {"settings": settings}
|
||||
self._store = {"admin_telegram_id": admin_telegram_id}
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
return self._store.get(key, default)
|
||||
|
||||
|
||||
def _make_settings() -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
ADMIN_IDS=[42],
|
||||
REDIS_KEY_PREFIX="shop",
|
||||
WEBHOOK_QUEUE_NAME="webhook-events",
|
||||
REDIS_URL="redis://r:6379/0",
|
||||
)
|
||||
|
||||
|
||||
def _parse(response: web.Response) -> dict:
|
||||
return json.loads(response.body.decode())
|
||||
|
||||
|
||||
def _patch_admin_auth(monkeypatch_target: Any) -> None:
|
||||
"""``_require_admin_user_id`` looks up session state we don't have here."""
|
||||
monkeypatch_target.side_effect = lambda request: int(request.get("admin_telegram_id"))
|
||||
|
||||
|
||||
class AdminSyncQueueTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_returns_queued_when_redis_accepts_event(self):
|
||||
recorded: List[dict] = []
|
||||
|
||||
async def fake_enqueue(settings, provider, payload, *, event_id=None):
|
||||
recorded.append(
|
||||
{
|
||||
"settings": settings,
|
||||
"provider": provider,
|
||||
"payload": payload,
|
||||
"event_id": event_id,
|
||||
}
|
||||
)
|
||||
return True
|
||||
|
||||
with (
|
||||
patch.object(sync_module, "enqueue_webhook_event", fake_enqueue),
|
||||
patch.object(sync_module, "_require_admin_user_id") as auth,
|
||||
):
|
||||
_patch_admin_auth(auth)
|
||||
response = await sync_module.admin_sync_route(_FakeRequest(_make_settings()))
|
||||
|
||||
self.assertEqual(response.status, 200)
|
||||
body = _parse(response)
|
||||
self.assertTrue(body["ok"])
|
||||
self.assertEqual(body["result"], {"status": "queued"})
|
||||
|
||||
self.assertEqual(len(recorded), 1)
|
||||
entry = recorded[0]
|
||||
self.assertEqual(entry["provider"], "panel_sync")
|
||||
self.assertEqual(entry["payload"], {"requested_by": 42})
|
||||
# event_id=None lets Redis enqueue every admin request (no dedupe key).
|
||||
self.assertIsNone(entry["event_id"])
|
||||
|
||||
async def test_returns_503_when_queue_is_unavailable(self):
|
||||
async def fake_enqueue(settings, provider, payload, *, event_id=None):
|
||||
return False
|
||||
|
||||
with (
|
||||
patch.object(sync_module, "enqueue_webhook_event", fake_enqueue),
|
||||
patch.object(sync_module, "_require_admin_user_id") as auth,
|
||||
):
|
||||
_patch_admin_auth(auth)
|
||||
response = await sync_module.admin_sync_route(_FakeRequest(_make_settings()))
|
||||
|
||||
self.assertEqual(response.status, 503)
|
||||
body = _parse(response)
|
||||
self.assertFalse(body["ok"])
|
||||
self.assertEqual(body["error"], "queue_unavailable")
|
||||
|
||||
async def test_non_admin_is_rejected_before_enqueueing(self):
|
||||
async def fake_enqueue(*args, **kwargs):
|
||||
raise AssertionError("enqueue must not be called for non-admin requests")
|
||||
|
||||
def deny(_request):
|
||||
raise web.HTTPForbidden(
|
||||
text=json.dumps({"ok": False, "error": "forbidden"}),
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(sync_module, "enqueue_webhook_event", fake_enqueue),
|
||||
patch.object(sync_module, "_require_admin_user_id", side_effect=deny),
|
||||
):
|
||||
with self.assertRaises(web.HTTPForbidden):
|
||||
await sync_module.admin_sync_route(_FakeRequest(_make_settings()))
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user