diff --git a/tests/test_fix_012.py b/tests/test_fix_012.py new file mode 100644 index 0000000..64356de --- /dev/null +++ b/tests/test_fix_012.py @@ -0,0 +1,173 @@ +""" +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_rejects_old_placeholder_uuid(bad_uuid: str) -> None: + """SignalRequest.user_id must reject old-style placeholder strings.""" + with pytest.raises(ValidationError): + SignalRequest(user_id=bad_uuid, timestamp=1700000000000) + + +# --------------------------------------------------------------------------- +# 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_rejects_uuid_wrong_variant_bits() -> None: + """UUID with invalid variant bits (0xxx in fourth group) must be rejected.""" + with pytest.raises(ValidationError): + # fourth group starts with '0' — not 8/9/a/b variant + SignalRequest(user_id="550e8400-e29b-41d4-0716-446655440000", timestamp=1700000000000) + + +def test_signal_request_rejects_uuid_wrong_variant_c() -> None: + """UUID with variant 'c' (1100 bits) must be rejected — only 8/9/a/b allowed.""" + with pytest.raises(ValidationError): + SignalRequest(user_id="550e8400-e29b-41d4-c716-446655440000", timestamp=1700000000000) + + +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