import pytest from app.db import DeliveryStatus from app.domain import ( DomainError, delivery_transition, normalize_phone, render_template, request_fingerprint, sms_parts, ) def test_phone_is_canonical_and_masked() -> None: e164, digits, masked = normalize_phone("+79001234567") assert e164 == "+79001234567" assert digits == "79001234567" assert masked == "+7******4567" @pytest.mark.parametrize("phone", ["79001234567", "+012345678", "+7900", "+7999999999999999"]) def test_invalid_phone_is_rejected(phone: str) -> None: with pytest.raises(DomainError) as error: normalize_phone(phone) assert error.value.code == "sms_request_invalid" def test_strict_template_render() -> None: result = render_template( "Код входа: {code}. Действителен {ttl_min} мин.", ["code", "ttl_min"], {"code": "482193", "ttl_min": 1}, 1, ) assert result == "Код входа: 482193. Действителен 1 мин." @pytest.mark.parametrize( "substitutions", [ {"code": "482193"}, {"code": "482193", "ttl_min": 1, "extra": "forbidden"}, ], ) def test_template_rejects_placeholder_mismatch(substitutions) -> None: with pytest.raises(DomainError): render_template( "Код: {code}; TTL: {ttl_min}", ["code", "ttl_min"], substitutions, 1, ) def test_template_rejects_format_expressions() -> None: with pytest.raises(DomainError): render_template("{code!r}", ["code"], {"code": "123456"}, 1) def test_sms_parts_supports_gsm_and_unicode() -> None: assert sms_parts("A" * 160) == 1 assert sms_parts("A" * 161) == 2 assert sms_parts("Я" * 70) == 1 assert sms_parts("Я" * 71) == 2 def test_fingerprint_is_canonical() -> None: first = request_fingerprint({"b": 2, "a": {"y": 2, "x": 1}}) second = request_fingerprint({"a": {"x": 1, "y": 2}, "b": 2}) assert first == second @pytest.mark.parametrize( ("current", "incoming", "expected"), [ (DeliveryStatus.UNKNOWN, "sent", DeliveryStatus.SENT), (DeliveryStatus.SENT, "delivered", DeliveryStatus.DELIVERED), (DeliveryStatus.DELIVERED, "sent", DeliveryStatus.DELIVERED), (DeliveryStatus.UNDELIVERED, "sent", DeliveryStatus.UNDELIVERED), (DeliveryStatus.DELIVERED, "unsent", DeliveryStatus.DELIVERED), (DeliveryStatus.UNKNOWN, "bogus", None), ], ) def test_delivery_status_is_monotonic(current, incoming, expected) -> None: assert delivery_transition(current, incoming) == expected