# apps/api/tests/test_aider_events_api.py | 2026-04-20 @ Asia/Taipei import hmac import hashlib import json import pytest from datetime import datetime, timezone, timedelta from unittest.mock import AsyncMock, patch from fastapi import FastAPI from fastapi.testclient import TestClient from src.api.v1.aider_events import router SECRET = "testsecret_for_unittest_do_not_use_in_prod_" + "x" * 20 TAIPEI = timezone(timedelta(hours=8)) def _sign(body: bytes, secret: str = SECRET) -> str: return "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() def _ev(): return { "ts": datetime(2026, 4, 20, 10, 0, tzinfo=TAIPEI).isoformat(), "session_id": "s1", "host": "ogt-mac", "type": "session_start", "payload": { "cwd": "/t/x", "model": "elephant", "aider_args": [], "aider_pid": 1, "cli_version": "0.86", }, } @pytest.fixture def client(monkeypatch): monkeypatch.setenv("AIDER_WEBHOOK_SECRET", SECRET) # Patch the service function async def mock_push(batch): return ["1234-0"] * len(batch.events) with patch("src.api.v1.aider_events.push_aider_batch_to_stream", new_callable=lambda: mock_push): app = FastAPI() app.include_router(router, prefix="/api/v1") yield TestClient(app) def test_accepts_signed_batch(client): body = json.dumps({"events": [_ev()]}).encode() r = client.post( "/api/v1/aider/events", content=body, headers={ "X-Aider-Signature": _sign(body), "Content-Type": "application/json", }, ) assert r.status_code == 202, r.text data = r.json() assert data["accepted"] == 1 def test_rejects_invalid_signature(client): body = json.dumps({"events": [_ev()]}).encode() r = client.post( "/api/v1/aider/events", content=body, headers={ "X-Aider-Signature": "sha256=" + "0" * 64, "Content-Type": "application/json", }, ) assert r.status_code == 401 def test_rejects_missing_signature(client): body = json.dumps({"events": [_ev()]}).encode() r = client.post( "/api/v1/aider/events", content=body, headers={"Content-Type": "application/json"}, ) assert r.status_code == 401 def test_rejects_malformed_event(client): body = json.dumps({"events": [{"bad": "payload"}]}).encode() r = client.post( "/api/v1/aider/events", content=body, headers={ "X-Aider-Signature": _sign(body), "Content-Type": "application/json", }, ) assert r.status_code == 400 def test_rejects_oversize_batch(client): body = json.dumps({"events": [_ev()] * 51}).encode() r = client.post( "/api/v1/aider/events", content=body, headers={ "X-Aider-Signature": _sign(body), "Content-Type": "application/json", }, ) assert r.status_code == 400 def test_accepts_batch_of_50(client): body = json.dumps({"events": [_ev()] * 50}).encode() r = client.post( "/api/v1/aider/events", content=body, headers={ "X-Aider-Signature": _sign(body), "Content-Type": "application/json", }, ) assert r.status_code == 202 assert r.json()["accepted"] == 50