baton/tests/test_fix_012.py

173 lines
6.5 KiB
Python
Raw Permalink Normal View History

"""
Tests for BATON-FIX-012: UUID v4 validation regression guard.
BATON-SEC-005 added UUID v4 pattern validation to RegisterRequest.uuid and
SignalRequest.user_id. Tests in test_db.py / test_baton_005.py / test_telegram.py
previously used placeholder strings ('uuid-001', 'create-uuid-001', 'agg-uuid-001')
that are not valid UUID v4 causing 25 regressions.
This file locks down the behaviour so the same mistake cannot recur silently:
- Old-style placeholder strings are rejected by Pydantic
- All UUID constants used across the fixed test files are valid UUID v4
- RegisterRequest and SignalRequest accept exactly-valid v4 UUIDs
- They reject strings that violate version (bit 3 of field-3 must be 4) or
variant (top bits of field-4 must be 10xx) requirements
"""
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 pydantic import ValidationError
from backend.models import RegisterRequest, SignalRequest
# ---------------------------------------------------------------------------
# UUID constants from fixed test files — all must be valid UUID v4
# ---------------------------------------------------------------------------
# test_db.py constants (_UUID_DB_1 .. _UUID_DB_6)
_DB_UUIDS = [
"d0000001-0000-4000-8000-000000000001",
"d0000002-0000-4000-8000-000000000002",
"d0000003-0000-4000-8000-000000000003",
"d0000004-0000-4000-8000-000000000004",
"d0000005-0000-4000-8000-000000000005",
"d0000006-0000-4000-8000-000000000006",
]
# test_baton_005.py constants (_UUID_ADM_*)
_ADM_UUIDS = [
"e0000000-0000-4000-8000-000000000000",
"e0000001-0000-4000-8000-000000000001",
"e0000002-0000-4000-8000-000000000002",
"e0000003-0000-4000-8000-000000000003",
"e0000004-0000-4000-8000-000000000004",
"e0000005-0000-4000-8000-000000000005",
"e0000006-0000-4000-8000-000000000006",
"e0000007-0000-4000-8000-000000000007",
"e0000008-0000-4000-8000-000000000008",
"e0000009-0000-4000-8000-000000000009",
"e000000a-0000-4000-8000-000000000010",
]
# test_telegram.py constants (aggregator UUIDs)
_AGG_UUIDS = [
"a9900001-0000-4000-8000-000000000001",
"a9900099-0000-4000-8000-000000000099",
] + [f"a990000{i}-0000-4000-8000-00000000000{i}" for i in range(5)]
# ---------------------------------------------------------------------------
# Old-style placeholder UUIDs (pre-fix) must be rejected
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("bad_uuid", [
"uuid-001",
"uuid-002",
"uuid-003",
"uuid-004",
"uuid-005",
"uuid-006",
"create-uuid-001",
"create-uuid-002",
"create-uuid-003",
"pass-uuid-001",
"pass-uuid-002",
"block-uuid-001",
"unblock-uuid-001",
"delete-uuid-001",
"delete-uuid-002",
"regress-admin-uuid-001",
"unauth-uuid-001",
"agg-uuid-001",
"agg-uuid-clr",
])
def test_register_request_rejects_old_placeholder_uuid(bad_uuid: str) -> None:
"""RegisterRequest.uuid must reject all pre-BATON-SEC-005 placeholder strings."""
with pytest.raises(ValidationError):
RegisterRequest(uuid=bad_uuid, name="Test")
@pytest.mark.parametrize("bad_uuid", [
"uuid-001",
"agg-uuid-001",
"create-uuid-001",
])
def test_signal_request_accepts_any_user_id_string(bad_uuid: str) -> None:
"""SignalRequest.user_id is optional (no pattern) — validation is at endpoint level."""
req = SignalRequest(user_id=bad_uuid, timestamp=1700000000000)
assert req.user_id == bad_uuid
# ---------------------------------------------------------------------------
# All UUID constants from the fixed test files are valid UUID v4
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("valid_uuid", _DB_UUIDS)
def test_register_request_accepts_db_uuid_constants(valid_uuid: str) -> None:
"""RegisterRequest accepts all _UUID_DB_* constants from test_db.py."""
req = RegisterRequest(uuid=valid_uuid, name="Test")
assert req.uuid == valid_uuid
@pytest.mark.parametrize("valid_uuid", _ADM_UUIDS)
def test_register_request_accepts_adm_uuid_constants(valid_uuid: str) -> None:
"""RegisterRequest accepts all _UUID_ADM_* constants from test_baton_005.py."""
req = RegisterRequest(uuid=valid_uuid, name="Test")
assert req.uuid == valid_uuid
@pytest.mark.parametrize("valid_uuid", _AGG_UUIDS)
def test_signal_request_accepts_agg_uuid_constants(valid_uuid: str) -> None:
"""SignalRequest accepts all aggregator UUID constants from test_telegram.py."""
req = SignalRequest(user_id=valid_uuid, timestamp=1700000000000)
assert req.user_id == valid_uuid
# ---------------------------------------------------------------------------
# UUID v4 structural requirements — version digit and variant bits
# ---------------------------------------------------------------------------
def test_register_request_rejects_uuid_v1_version_digit() -> None:
"""UUID with version digit = 1 (not 4) must be rejected by RegisterRequest."""
with pytest.raises(ValidationError):
# third group starts with '1' — version 1, not v4
RegisterRequest(uuid="550e8400-e29b-11d4-a716-446655440000", name="Test")
def test_register_request_rejects_uuid_v3_version_digit() -> None:
"""UUID with version digit = 3 must be rejected."""
with pytest.raises(ValidationError):
RegisterRequest(uuid="550e8400-e29b-31d4-a716-446655440000", name="Test")
def test_signal_request_accepts_any_variant_bits() -> None:
"""SignalRequest.user_id is now optional and unvalidated (JWT auth doesn't use it)."""
req = SignalRequest(user_id="550e8400-e29b-41d4-0716-446655440000", timestamp=1700000000000)
assert req.user_id is not None
def test_signal_request_without_user_id() -> None:
"""SignalRequest works without user_id (JWT auth mode)."""
req = SignalRequest(timestamp=1700000000000)
assert req.user_id is None
def test_register_request_accepts_all_valid_v4_variants() -> None:
"""RegisterRequest accepts UUIDs with variant nibbles 8, 9, a, b."""
for variant in ("8", "9", "a", "b"):
uuid = f"550e8400-e29b-41d4-{variant}716-446655440000"
req = RegisterRequest(uuid=uuid, name="Test")
assert req.uuid == uuid