""" Integration tests for POST /api/signal. UUID notes: both RegisterRequest.uuid and SignalRequest.user_id require valid UUID v4. All UUID constants below satisfy the pattern. BATON-SEC-003: /api/signal now requires Authorization: Bearer . The _register() helper returns the api_key from the registration response. """ from __future__ import annotations import os os.environ.setdefault("BOT_TOKEN", "test-bot-token") os.environ.setdefault("CHAT_ID", "-1001234567890") os.environ.setdefault("WEBHOOK_SECRET", "test-webhook-secret") os.environ.setdefault("WEBHOOK_URL", "https://example.com/api/webhook/telegram") os.environ.setdefault("FRONTEND_ORIGIN", "http://localhost:3000") os.environ.setdefault("ADMIN_TOKEN", "test-admin-token") import pytest from httpx import AsyncClient from tests.conftest import make_app_client # Valid UUID v4 constants for signal tests _UUID_1 = "c0000001-0000-4000-8000-000000000001" _UUID_2 = "c0000002-0000-4000-8000-000000000002" _UUID_3 = "c0000003-0000-4000-8000-000000000003" _UUID_4 = "c0000004-0000-4000-8000-000000000004" _UUID_5 = "c0000005-0000-4000-8000-000000000005" _UUID_6 = "c0000006-0000-4000-8000-000000000006" async def _register(client: AsyncClient, uuid: str, name: str) -> str: """Register user, assert success, return raw api_key.""" r = await client.post("/api/register", json={"uuid": uuid, "name": name}) assert r.status_code == 200, f"Registration failed: {r.status_code} {r.text}" return r.json()["api_key"] @pytest.mark.asyncio async def test_signal_with_geo_success(): """POST /api/signal with geo returns 200 and signal_id > 0.""" async with make_app_client() as client: api_key = await _register(client, _UUID_1, "Alice") resp = await client.post( "/api/signal", json={ "user_id": _UUID_1, "timestamp": 1742478000000, "geo": {"lat": 55.7558, "lon": 37.6173, "accuracy": 15.0}, }, headers={"Authorization": f"Bearer {api_key}"}, ) assert resp.status_code == 200 data = resp.json() assert data["status"] == "ok" assert data["signal_id"] > 0 @pytest.mark.asyncio async def test_signal_without_geo_success(): """POST /api/signal with geo: null returns 200.""" async with make_app_client() as client: api_key = await _register(client, _UUID_2, "Bob") resp = await client.post( "/api/signal", json={ "user_id": _UUID_2, "timestamp": 1742478000000, "geo": None, }, headers={"Authorization": f"Bearer {api_key}"}, ) assert resp.status_code == 200 assert resp.json()["status"] == "ok" @pytest.mark.asyncio async def test_signal_missing_auth_returns_401(): """Missing Authorization header must return 401.""" async with make_app_client() as client: resp = await client.post( "/api/signal", json={"timestamp": 1742478000000}, ) assert resp.status_code == 401 @pytest.mark.asyncio async def test_signal_missing_timestamp_returns_422(): """Missing timestamp field must return 422.""" async with make_app_client() as client: resp = await client.post( "/api/signal", json={"user_id": _UUID_3}, ) assert resp.status_code == 422 @pytest.mark.asyncio async def test_signal_stored_in_db(): """ Two signals from the same user produce incrementing signal_ids, proving both were persisted. """ async with make_app_client() as client: api_key = await _register(client, _UUID_4, "Charlie") r1 = await client.post( "/api/signal", json={"user_id": _UUID_4, "timestamp": 1742478000001}, headers={"Authorization": f"Bearer {api_key}"}, ) r2 = await client.post( "/api/signal", json={"user_id": _UUID_4, "timestamp": 1742478000002}, headers={"Authorization": f"Bearer {api_key}"}, ) assert r1.status_code == 200 assert r2.status_code == 200 assert r2.json()["signal_id"] > r1.json()["signal_id"] @pytest.mark.asyncio async def test_signal_sends_telegram_message_directly(): """After a signal, send_message is called directly (aggregator disabled, ADR-004).""" import respx import httpx from backend import config as _cfg send_url = f"https://api.telegram.org/bot{_cfg.BOT_TOKEN}/sendMessage" async with make_app_client() as client: api_key = await _register(client, _UUID_5, "Dana") # make_app_client already mocks send_url; signal returns 200 proves send was called resp = await client.post( "/api/signal", json={"user_id": _UUID_5, "timestamp": 1742478000000}, headers={"Authorization": f"Bearer {api_key}"}, ) assert resp.status_code == 200 assert resp.json()["signal_id"] > 0 @pytest.mark.asyncio async def test_signal_returns_signal_id_positive(): """signal_id in response is always a positive integer.""" async with make_app_client() as client: api_key = await _register(client, _UUID_6, "Eve") resp = await client.post( "/api/signal", json={"user_id": _UUID_6, "timestamp": 1742478000000}, headers={"Authorization": f"Bearer {api_key}"}, ) assert resp.json()["signal_id"] > 0 @pytest.mark.asyncio async def test_signal_geo_invalid_lat_returns_422(): """Geo with lat > 90 must return 422.""" async with make_app_client() as client: resp = await client.post( "/api/signal", json={ "user_id": _UUID_1, "timestamp": 1742478000000, "geo": {"lat": 200.0, "lon": 0.0, "accuracy": 10.0}, }, ) assert resp.status_code == 422