"""The request models turn payloads that used to 500 into clean validation errors, and PATCH models distinguish an absent field from an explicit null. Pure-logic (no DB) — it exercises the Pydantic models the route modules define, not the endpoints.""" import pytest from pydantic import ValidationError from app.routes.channels import AttachTagIn, ChannelUpdateIn from app.routes.downloads import AdminSetQuotaIn from app.routes._common import ProgressIn from app.routes.setup import SetupAdminIn from app.routes.tags import TagUpdateIn # --- The three ROADMAP-named 500-producers are now 422s (ValidationError at the boundary). --- def test_channel_priority_rejects_non_numeric(): # Was `int(payload["priority"])` → 500 on garbage. Now a validation error. with pytest.raises(ValidationError): ChannelUpdateIn(priority="not-a-number") def test_channel_priority_accepts_int_and_numeric_string(): assert ChannelUpdateIn(priority=5).priority == 5 assert ChannelUpdateIn(priority="5").priority == 5 # lax numeric-string coercion, as before def test_channel_update_fields_all_optional(): m = ChannelUpdateIn() assert m.priority is None and m.hidden is None and m.deep_requested is None def test_attach_tag_rejects_non_int_id(): # Was a str tag_id into db.get(Tag, ...) — a DB cast 500. Now rejected up front. with pytest.raises(ValidationError): AttachTagIn(tag_id="abc") def test_setup_admin_password_must_be_a_string(): # Was a non-string password reaching len()/argon2 → 500. `str` rejects it. with pytest.raises(ValidationError): SetupAdminIn(email="a@b.c", password=12345) def test_admin_quota_rejects_non_numeric_limit(): # Was `int(payload["max_bytes"])` → 500. Now a validation error. with pytest.raises(ValidationError): AdminSetQuotaIn(max_bytes="lots") def test_progress_accepts_fractional_seconds(): # The player may post a raw currentTime (a float). The field must accept it (truncated in the # handler) — a plain `int` field would 422 on the progress hot path. assert ProgressIn(position_seconds=12.7).position_seconds == 12.7 assert int(ProgressIn(position_seconds=12.7).position_seconds) == 12 with pytest.raises(ValidationError): ProgressIn(position_seconds="not-a-number") # --- PATCH models: model_fields_set separates "absent" from "explicit null". --- def test_tag_update_absent_vs_null_color(): absent = TagUpdateIn() assert "color" not in absent.model_fields_set # untouched on PATCH cleared = TagUpdateIn(color=None) assert "color" in cleared.model_fields_set # an explicit null CLEARS it assert cleared.color is None