"""Round-trip tests for AdvancedSettings ↔ AdvancedSettingsFrontend conversion.""" import msgspec import pytest from api.v1.schemas.advanced_settings import AdvancedSettings, AdvancedSettingsFrontend class TestDirectRemoteImagesRoundTrip: def test_default_value_is_true(self) -> None: settings = AdvancedSettings() assert settings.direct_remote_images_enabled is True def test_frontend_default_is_true(self) -> None: frontend = AdvancedSettingsFrontend() assert frontend.direct_remote_images_enabled is True def test_roundtrip_preserves_true(self) -> None: backend = AdvancedSettings(direct_remote_images_enabled=True) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.direct_remote_images_enabled is True restored = frontend.to_backend() assert restored.direct_remote_images_enabled is True def test_roundtrip_preserves_false(self) -> None: backend = AdvancedSettings(direct_remote_images_enabled=False) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.direct_remote_images_enabled is False restored = frontend.to_backend() assert restored.direct_remote_images_enabled is False class TestAudioDBNameSearchFallbackRoundTrip: def test_default_value_is_false(self) -> None: settings = AdvancedSettings() assert settings.audiodb_name_search_fallback is False def test_frontend_default_is_false(self) -> None: frontend = AdvancedSettingsFrontend() assert frontend.audiodb_name_search_fallback is False def test_roundtrip_preserves_true(self) -> None: backend = AdvancedSettings(audiodb_name_search_fallback=True) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.audiodb_name_search_fallback is True restored = frontend.to_backend() assert restored.audiodb_name_search_fallback is True def test_roundtrip_preserves_false(self) -> None: backend = AdvancedSettings(audiodb_name_search_fallback=False) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.audiodb_name_search_fallback is False restored = frontend.to_backend() assert restored.audiodb_name_search_fallback is False class TestCacheTtlAudiodbFoundRoundTrip: def test_default_value(self) -> None: settings = AdvancedSettings() frontend = AdvancedSettingsFrontend.from_backend(settings) assert frontend.cache_ttl_audiodb_found == 168 def test_roundtrip_preserves(self) -> None: backend = AdvancedSettings(cache_ttl_audiodb_found=604800) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.cache_ttl_audiodb_found == 168 restored = frontend.to_backend() assert restored.cache_ttl_audiodb_found == 604800 def test_custom_value_roundtrip(self) -> None: backend = AdvancedSettings(cache_ttl_audiodb_found=36000) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.cache_ttl_audiodb_found == 10 restored = frontend.to_backend() assert restored.cache_ttl_audiodb_found == 36000 class TestCacheTtlAudiodbNotFoundRoundTrip: def test_default_value(self) -> None: settings = AdvancedSettings() frontend = AdvancedSettingsFrontend.from_backend(settings) assert frontend.cache_ttl_audiodb_not_found == 24 def test_roundtrip_preserves(self) -> None: backend = AdvancedSettings(cache_ttl_audiodb_not_found=86400) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.cache_ttl_audiodb_not_found == 24 restored = frontend.to_backend() assert restored.cache_ttl_audiodb_not_found == 86400 def test_custom_value_roundtrip(self) -> None: backend = AdvancedSettings(cache_ttl_audiodb_not_found=7200) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.cache_ttl_audiodb_not_found == 2 restored = frontend.to_backend() assert restored.cache_ttl_audiodb_not_found == 7200 class TestCacheTtlAudiodbLibraryRoundTrip: def test_default_value(self) -> None: settings = AdvancedSettings() frontend = AdvancedSettingsFrontend.from_backend(settings) assert frontend.cache_ttl_audiodb_library == 336 def test_roundtrip_preserves(self) -> None: backend = AdvancedSettings(cache_ttl_audiodb_library=1209600) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.cache_ttl_audiodb_library == 336 restored = frontend.to_backend() assert restored.cache_ttl_audiodb_library == 1209600 class TestCacheTtlRecentlyViewedBytesRoundTrip: def test_default_value(self) -> None: settings = AdvancedSettings() frontend = AdvancedSettingsFrontend.from_backend(settings) assert frontend.cache_ttl_recently_viewed_bytes == 48 def test_roundtrip_preserves(self) -> None: backend = AdvancedSettings(cache_ttl_recently_viewed_bytes=172800) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.cache_ttl_recently_viewed_bytes == 48 restored = frontend.to_backend() assert restored.cache_ttl_recently_viewed_bytes == 172800 class TestAudiodbValidationClamping: def test_cache_ttl_found_below_min(self) -> None: with pytest.raises(msgspec.ValidationError): AdvancedSettings(cache_ttl_audiodb_found=100) def test_cache_ttl_found_above_max(self) -> None: with pytest.raises(msgspec.ValidationError): AdvancedSettings(cache_ttl_audiodb_found=99999999) def test_cache_ttl_not_found_below_min(self) -> None: with pytest.raises(msgspec.ValidationError): AdvancedSettings(cache_ttl_audiodb_not_found=100) def test_cache_ttl_not_found_above_max(self) -> None: with pytest.raises(msgspec.ValidationError): AdvancedSettings(cache_ttl_audiodb_not_found=999999) def test_cache_ttl_library_below_min(self) -> None: with pytest.raises(msgspec.ValidationError): AdvancedSettings(cache_ttl_audiodb_library=100) def test_cache_ttl_library_above_max(self) -> None: with pytest.raises(msgspec.ValidationError): AdvancedSettings(cache_ttl_audiodb_library=99999999) def test_cache_ttl_recently_viewed_bytes_below_min(self) -> None: with pytest.raises(msgspec.ValidationError): AdvancedSettings(cache_ttl_recently_viewed_bytes=100) def test_cache_ttl_recently_viewed_bytes_above_max(self) -> None: with pytest.raises(msgspec.ValidationError): AdvancedSettings(cache_ttl_recently_viewed_bytes=999999) def test_api_key_empty_coerced(self) -> None: settings = AdvancedSettings(audiodb_api_key="") assert settings.audiodb_api_key == "123" def test_api_key_whitespace_coerced(self) -> None: settings = AdvancedSettings(audiodb_api_key=" ") assert settings.audiodb_api_key == "123" class TestAudiodbEnabledRoundTrip: def test_default_is_true(self) -> None: settings = AdvancedSettings() frontend = AdvancedSettingsFrontend.from_backend(settings) assert frontend.audiodb_enabled is True def test_roundtrip_false(self) -> None: backend = AdvancedSettings(audiodb_enabled=False) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.audiodb_enabled is False restored = frontend.to_backend() assert restored.audiodb_enabled is False def test_roundtrip_true(self) -> None: backend = AdvancedSettings(audiodb_enabled=True) frontend = AdvancedSettingsFrontend.from_backend(backend) assert frontend.audiodb_enabled is True restored = frontend.to_backend() assert restored.audiodb_enabled is True class TestSyncSettingsRoundTrip: """Round-trip tests for the sync robustness settings.""" @pytest.mark.parametrize("field,backend_val,frontend_val", [ ("sync_stall_timeout_minutes", 15, 15), ("sync_max_timeout_hours", 6, 6), ("audiodb_prewarm_concurrency", 6, 6), ("audiodb_prewarm_delay", 1.5, 1.5), ("artist_discovery_precache_concurrency", 5, 5), ]) def test_roundtrip_preserves_value(self, field: str, backend_val, frontend_val) -> None: backend = AdvancedSettings(**{field: backend_val}) frontend = AdvancedSettingsFrontend.from_backend(backend) assert getattr(frontend, field) == frontend_val restored = frontend.to_backend() assert getattr(restored, field) == backend_val @pytest.mark.parametrize("field,default_val", [ ("sync_stall_timeout_minutes", 10), ("sync_max_timeout_hours", 8), ("audiodb_prewarm_concurrency", 4), ("audiodb_prewarm_delay", 0.3), ("artist_discovery_precache_concurrency", 5), ("artist_discovery_precache_delay", 0.2), ]) def test_defaults_match(self, field: str, default_val) -> None: backend = AdvancedSettings() frontend = AdvancedSettingsFrontend() assert getattr(backend, field) == default_val assert getattr(frontend, field) == default_val