Reject non-integer weekday values in presence pattern requests

The create and edit pattern models typed weekday values as lax int, so
JSON true, "1" and 1.0 were coerced to weekday 1. Type them StrictInt
so each is a 422 naming the pattern field, and add create and edit
regression cases for bool, string, float and out-of-range weekdays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 19:55:43 -04:00
co-authored by Claude Opus 5.5
parent 39417615a2
commit b91ecbd5f6
2 changed files with 16 additions and 4 deletions
+5 -4
View File
@@ -623,15 +623,16 @@ class AdminPresencePatternCreateRequest(BaseModel):
covers that week (Monday=0); every week of the cycle must be
covered, which the PRESENCE MODEL enforces at construction, not
this shape — as it does the non-blank ``name`` and the
``home``/``away`` ``kind``. ``cycle_length_weeks`` is a strict
JSON integer: ``2.0`` or ``"2"`` is a 422, not a silent coercion.
``home``/``away`` ``kind``. ``cycle_length_weeks`` and the weekday
values are strict JSON integers: ``2.0``, ``"2"`` or ``true`` is a
422, not a silent coercion.
"""
name: str
kind: str
cycle_length_weeks: StrictInt
anchor_date: str
pattern: dict[int, list[int]]
pattern: dict[int, list[StrictInt]]
class AdminPresencePatternEditRequest(BaseModel):
@@ -646,7 +647,7 @@ class AdminPresencePatternEditRequest(BaseModel):
kind: str | None = None
cycle_length_weeks: StrictInt | None = None
anchor_date: str | None = None
pattern: dict[int, list[int]] | None = None
pattern: dict[int, list[StrictInt]] | None = None
class AdminPresencePatternResponse(BaseModel):
+11
View File
@@ -690,6 +690,12 @@ async def test_pattern_patch_unknown_id_is_404(
({"anchor_date": None}, "anchor_date"),
({"pattern": {}}, "pattern"),
({"pattern": None}, "pattern"),
({"pattern": {"0": [7]}}, "pattern"), # weekday out of 0..6
# Strict JSON integer weekdays: no bool, string or float coercion.
({"pattern": {"0": [True]}}, "pattern"),
({"pattern": {"0": [False]}}, "pattern"),
({"pattern": {"0": ["1"]}}, "pattern"),
({"pattern": {"0": [1.0]}}, "pattern"),
],
)
async def test_rejected_pattern_patch_is_422_and_changes_nothing(
@@ -882,6 +888,11 @@ async def test_pattern_and_override_for_unknown_child_are_404(
(_pattern_body(anchor_date="2026-13-40"), "anchor_date"),
(_pattern_body(anchor_date="05/01/2026"), "anchor_date"),
(_pattern_body(pattern={"0": [7]}), "pattern"), # weekday out of 0..6
# Strict JSON integer weekdays: no bool, string or float coercion.
(_pattern_body(pattern={"0": [True]}), "pattern"),
(_pattern_body(pattern={"0": [False]}), "pattern"),
(_pattern_body(pattern={"0": ["1"]}), "pattern"),
(_pattern_body(pattern={"0": [1.0]}), "pattern"),
(_pattern_body(pattern={"nope": [0]}), "pattern"), # non-int week
(_pattern_body(pattern={"0": "0,2,4"}), "pattern"), # not a list
(_pattern_body(kind="sometimes"), "kind"),