"""Unit tests for profile pydantic schemas (ticket 002). Validation is the api boundary for the JSONB columns: language enum, IANA timezone, notif/dashboard pref shapes, PATCH all-optional semantics. """ from __future__ import annotations import pytest from pydantic import ValidationError from contract_check.api.schemas.profile import ( DashboardPrefs, NotificationPrefs, UserProfile, UserProfilePatch, ) class TestUserProfile: def test_defaults(self) -> None: profile = UserProfile() assert profile.language is None assert profile.timezone is None assert profile.notif_prefs == NotificationPrefs( report_ready=True, security=True, marketing=False ) assert profile.dashboard_prefs == DashboardPrefs( severity_filter="all", per_page=10, density="comfortable" ) def test_valid_language_and_timezone(self) -> None: profile = UserProfile(language="ru", timezone="Europe/Minsk") assert profile.language == "ru" assert profile.timezone == "Europe/Minsk" def test_invalid_language_rejected(self) -> None: with pytest.raises(ValidationError): UserProfile(language="de") def test_invalid_timezone_rejected(self) -> None: with pytest.raises(ValidationError): UserProfile(timezone="Mars/Olympus_Mons") def test_empty_timezone_normalized_to_none(self) -> None: assert UserProfile(timezone="").timezone is None @pytest.mark.parametrize( ("field", "value"), [ ("severity_filter", "critical"), ("per_page", 0), ("per_page", 101), ("density", "spacious"), ], ) def test_dashboard_pref_validation(self, field: str, value: object) -> None: with pytest.raises(ValidationError): DashboardPrefs.model_validate({field: value}) def test_unknown_fields_ignored(self) -> None: profile = UserProfile.model_validate({"language": "en", "hobby": "chess"}) assert profile.language == "en" assert not hasattr(profile, "hobby") class TestUserProfilePatch: def test_empty_patch(self) -> None: patch = UserProfilePatch() assert patch.is_empty() def test_partial_patch(self) -> None: patch = UserProfilePatch(language="be") assert patch.language == "be" assert patch.notif_prefs is None assert not patch.is_empty() def test_invalid_timezone_rejected(self) -> None: with pytest.raises(ValidationError): UserProfilePatch(timezone="not/a-zone") def test_nested_prefs_validated(self) -> None: with pytest.raises(ValidationError): UserProfilePatch(notif_prefs={"marketing": "maybe"})