import pytest from pydantic import ValidationError from app.domain import DomainError from app.schemas import CallbackItem, SendRequest from app.service import validate_otp_request def valid_send(**overrides) -> SendRequest: payload = { "idempotency_key": "keycloak:challenge:01JABCDEF", "template_code": "auth_otp", "locale": "ru", "phone_e164": "+79001234567", "substitutions": {"code": "482193", "ttl_min": "1"}, "customer_ref": "01JABCDEF", "message_ttl_sec": 60, } payload.update(overrides) return SendRequest.model_validate(payload) def test_send_request_is_strict() -> None: with pytest.raises(ValidationError): valid_send(extra="forbidden") @pytest.mark.parametrize( ("substitutions", "ttl"), [ ({"code": "12ab", "ttl_min": "1"}, 60), ({"code": "123456", "ttl_min": "2"}, 60), ({"code": "123456", "ttl_min": "1"}, 61), ], ) def test_otp_substitutions_match_ttl(substitutions, ttl) -> None: with pytest.raises(DomainError): validate_otp_request(valid_send(substitutions=substitutions, message_ttl_sec=ttl)) def test_callback_accepts_provider_camel_case() -> None: item = CallbackItem.model_validate( { "channelType": "SMS", "messageUuid": "provider-id", "externalMessageId": "internal-id", "callbackEvent": "delivered", "status": "delivered", "statusTime": "2026-07-22T12:00:00Z", } ) assert item.channel_type == "SMS" assert item.status_time.tzinfo is not None