kin: BATON-SEC-003-backend_dev
This commit is contained in:
parent
8629f3e40b
commit
3a2ec11cc7
13 changed files with 593 additions and 125 deletions
|
|
@ -5,6 +5,10 @@ Acceptance criteria:
|
|||
1. No asyncio task for the aggregator is created at lifespan startup.
|
||||
2. POST /api/signal calls telegram.send_message directly (no aggregator intermediary).
|
||||
3. SignalAggregator class in telegram.py is preserved with '# v2.0 feature' marker.
|
||||
|
||||
UUID notes: all UUIDs satisfy the UUID v4 pattern.
|
||||
BATON-SEC-003: POST /api/signal now requires Authorization: Bearer <api_key>.
|
||||
Tests that send signals register first and use the returned api_key.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -15,6 +19,7 @@ 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")
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
|
@ -25,6 +30,20 @@ from tests.conftest import make_app_client
|
|||
|
||||
_BACKEND_DIR = Path(__file__).parent.parent / "backend"
|
||||
|
||||
# Valid UUID v4 constants
|
||||
_UUID_S1 = "a0100001-0000-4000-8000-000000000001"
|
||||
_UUID_S2 = "a0100002-0000-4000-8000-000000000002"
|
||||
_UUID_S3 = "a0100003-0000-4000-8000-000000000003"
|
||||
_UUID_S4 = "a0100004-0000-4000-8000-000000000004"
|
||||
_UUID_S5 = "a0100005-0000-4000-8000-000000000005"
|
||||
|
||||
|
||||
async def _register(client, uuid: str, name: str) -> str:
|
||||
"""Register user and return api_key."""
|
||||
r = await client.post("/api/register", json={"uuid": uuid, "name": name})
|
||||
assert r.status_code == 200
|
||||
return r.json()["api_key"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Criterion 1 — No asyncio task for aggregator created at startup (static)
|
||||
|
|
@ -72,11 +91,12 @@ def test_aggregator_instantiation_commented_out_in_main():
|
|||
async def test_signal_calls_telegram_send_message_directly():
|
||||
"""POST /api/signal must call telegram.send_message directly, not via aggregator (ADR-004)."""
|
||||
async with make_app_client() as client:
|
||||
await client.post("/api/register", json={"uuid": "adr-uuid-s1", "name": "Tester"})
|
||||
api_key = await _register(client, _UUID_S1, "Tester")
|
||||
with patch("backend.telegram.send_message", new_callable=AsyncMock) as mock_send:
|
||||
resp = await client.post(
|
||||
"/api/signal",
|
||||
json={"user_id": "adr-uuid-s1", "timestamp": 1742478000000},
|
||||
json={"user_id": _UUID_S1, "timestamp": 1742478000000},
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
mock_send.assert_called_once()
|
||||
|
|
@ -86,11 +106,12 @@ async def test_signal_calls_telegram_send_message_directly():
|
|||
async def test_signal_message_contains_registered_username():
|
||||
"""Message passed to send_message must include the registered user's name."""
|
||||
async with make_app_client() as client:
|
||||
await client.post("/api/register", json={"uuid": "adr-uuid-s2", "name": "Alice"})
|
||||
api_key = await _register(client, _UUID_S2, "Alice")
|
||||
with patch("backend.telegram.send_message", new_callable=AsyncMock) as mock_send:
|
||||
await client.post(
|
||||
"/api/signal",
|
||||
json={"user_id": "adr-uuid-s2", "timestamp": 1742478000000},
|
||||
json={"user_id": _UUID_S2, "timestamp": 1742478000000},
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
)
|
||||
text = mock_send.call_args[0][0]
|
||||
assert "Alice" in text
|
||||
|
|
@ -100,11 +121,12 @@ async def test_signal_message_contains_registered_username():
|
|||
async def test_signal_message_without_geo_contains_bez_geolocatsii():
|
||||
"""When geo is None, message must contain 'Без геолокации'."""
|
||||
async with make_app_client() as client:
|
||||
await client.post("/api/register", json={"uuid": "adr-uuid-s3", "name": "Bob"})
|
||||
api_key = await _register(client, _UUID_S3, "Bob")
|
||||
with patch("backend.telegram.send_message", new_callable=AsyncMock) as mock_send:
|
||||
await client.post(
|
||||
"/api/signal",
|
||||
json={"user_id": "adr-uuid-s3", "timestamp": 1742478000000, "geo": None},
|
||||
json={"user_id": _UUID_S3, "timestamp": 1742478000000, "geo": None},
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
)
|
||||
text = mock_send.call_args[0][0]
|
||||
assert "Без геолокации" in text
|
||||
|
|
@ -114,15 +136,16 @@ async def test_signal_message_without_geo_contains_bez_geolocatsii():
|
|||
async def test_signal_message_with_geo_contains_coordinates():
|
||||
"""When geo is provided, message must contain lat and lon values."""
|
||||
async with make_app_client() as client:
|
||||
await client.post("/api/register", json={"uuid": "adr-uuid-s4", "name": "Charlie"})
|
||||
api_key = await _register(client, _UUID_S4, "Charlie")
|
||||
with patch("backend.telegram.send_message", new_callable=AsyncMock) as mock_send:
|
||||
await client.post(
|
||||
"/api/signal",
|
||||
json={
|
||||
"user_id": "adr-uuid-s4",
|
||||
"user_id": _UUID_S4,
|
||||
"timestamp": 1742478000000,
|
||||
"geo": {"lat": 55.7558, "lon": 37.6173, "accuracy": 15.0},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
)
|
||||
text = mock_send.call_args[0][0]
|
||||
assert "55.7558" in text
|
||||
|
|
@ -133,29 +156,17 @@ async def test_signal_message_with_geo_contains_coordinates():
|
|||
async def test_signal_message_contains_utc_marker():
|
||||
"""Message passed to send_message must contain 'UTC' timestamp marker."""
|
||||
async with make_app_client() as client:
|
||||
await client.post("/api/register", json={"uuid": "adr-uuid-s5", "name": "Dave"})
|
||||
api_key = await _register(client, _UUID_S5, "Dave")
|
||||
with patch("backend.telegram.send_message", new_callable=AsyncMock) as mock_send:
|
||||
await client.post(
|
||||
"/api/signal",
|
||||
json={"user_id": "adr-uuid-s5", "timestamp": 1742478000000},
|
||||
json={"user_id": _UUID_S5, "timestamp": 1742478000000},
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
)
|
||||
text = mock_send.call_args[0][0]
|
||||
assert "UTC" in text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_signal_unknown_user_message_uses_uuid_prefix():
|
||||
"""When user is not registered, message uses first 8 chars of uuid as name."""
|
||||
async with make_app_client() as client:
|
||||
with patch("backend.telegram.send_message", new_callable=AsyncMock) as mock_send:
|
||||
await client.post(
|
||||
"/api/signal",
|
||||
json={"user_id": "unknown-uuid-xyz", "timestamp": 1742478000000},
|
||||
)
|
||||
text = mock_send.call_args[0][0]
|
||||
assert "unknown-" in text # "unknown-uuid-xyz"[:8] == "unknown-"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Criterion 3 — SignalAggregator preserved with '# v2.0 feature' marker (static)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue