Admin routes: settings read and update #163

Member

Task

Maestro task 2b3de7e5-ea8c-4bbe-8e47-f103242d51b8 — Admin routes: settings read and update (Feature 16, NestQuest API Service).

Done-condition

Admin routes for settings: read and update horizon_days, day_rollover_time, and the notification configuration (notify_target, the three times, the four enable toggles). Validation reuses the core settings validators; an invalid value is rejected with a clear error. Tests as an admin confirm read returns the current effective settings, update persists and is reflected on the next read, and an invalid value is rejected without changing stored settings.

What changed

  • custom_components/nestquest/core/settings_store.py (new): HA-free durable settings store. The API (no HA config entry) owns settings in the EXISTING nestquest_meta_state key/value table under the well-known key "settings" — no schema change, no new table. load_settings resolves the stored ten-field JSON document through the core _FIELDS validators (defaults for absent fields; per-field fallback over a corrupt document via from_options_resilient, so the service never crashes). update_settings rejects unknown fields and resolves each provided field through the SAME core validators (JSON null → default, the settings module's None-as-absent rule), validating everything BEFORE the single atomic upsert — a rejected change can never leave a half-applied document. The read→merge→write span holds a per-database lock (the missed sweep's pattern) so concurrent updates cannot lose each other.
  • api/nestquest_core.py: core_settings_store re-export following the existing pattern.
  • api/routes_admin.py: GET /api/v1/admin/settings (effective settings, all ten documented fields) and PATCH /api/v1/admin/settings (body passed straight to the core store; every core ValueError → 422 via _raise_settings_error, narrow — anything else re-raises). Both sit behind the router's ONE require_admin dependency. Docstrings note the API now owns settings in the database and that the integration's HA options remain a temporary separate source until Feature 18 wires the client to the API.
  • tests/test_api_admin_settings.py (new, 18 tests): fresh-DB read returns defaults; update persists (proven on the next read AND against the stored nestquest_meta_state document); partial update merges; empty update is a no-op; JSON null resets to default; nine invalid values → 422 naming the field with the next read unchanged (including the bool-for-int guardrail surviving the route); unknown field rejected alone and mixed with a valid sibling (nothing written); non-object bodies → 422; corrupt stored documents degrade to defaults (whole-document and per-field); non-admin JWT 403 / absent credential 401 with nothing written.

Error mapping

Invalid value / unknown field → 422 with the field named in the detail (raised by the core BEFORE any write); a non-object body is FastAPI's 422; only ValueError is caught, so unexpected failures are never swallowed into a 4xx. The settings routes have no path ids, so there is no 404 case.

Checks

  • TZ=UTC uv run pytest -q → 1768 passed (baseline 1750 + 18 new)
  • TZ=UTC uv run pytest -q tests/test_api_admin_settings.py → 18 passed

Task: 2b3de7e5-ea8c-4bbe-8e47-f103242d51b8 · Branch: task/admin-routes-settings-read-and-update_2b3de7e5-ea8c-4bbe-8e47-f103242d51b8 · Head: 32851212e2

## Task Maestro task 2b3de7e5-ea8c-4bbe-8e47-f103242d51b8 — **Admin routes: settings read and update** (Feature 16, NestQuest API Service). ## Done-condition Admin routes for settings: read and update `horizon_days`, `day_rollover_time`, and the notification configuration (`notify_target`, the three times, the four enable toggles). Validation reuses the core settings validators; an invalid value is rejected with a clear error. Tests as an admin confirm read returns the current effective settings, update persists and is reflected on the next read, and an invalid value is rejected without changing stored settings. ## What changed - **`custom_components/nestquest/core/settings_store.py`** (new): HA-free durable settings store. The API (no HA config entry) owns settings in the EXISTING `nestquest_meta_state` key/value table under the well-known key `"settings"` — no schema change, no new table. `load_settings` resolves the stored ten-field JSON document through the core `_FIELDS` validators (defaults for absent fields; per-field fallback over a corrupt document via `from_options_resilient`, so the service never crashes). `update_settings` rejects unknown fields and resolves each provided field through the SAME core validators (JSON `null` → default, the settings module's None-as-absent rule), validating everything BEFORE the single atomic upsert — a rejected change can never leave a half-applied document. The read→merge→write span holds a per-database lock (the missed sweep's pattern) so concurrent updates cannot lose each other. - **`api/nestquest_core.py`**: `core_settings_store` re-export following the existing pattern. - **`api/routes_admin.py`**: `GET /api/v1/admin/settings` (effective settings, all ten documented fields) and `PATCH /api/v1/admin/settings` (body passed straight to the core store; every core `ValueError` → 422 via `_raise_settings_error`, narrow — anything else re-raises). Both sit behind the router's ONE `require_admin` dependency. Docstrings note the API now owns settings in the database and that the integration's HA options remain a temporary separate source until Feature 18 wires the client to the API. - **`tests/test_api_admin_settings.py`** (new, 18 tests): fresh-DB read returns defaults; update persists (proven on the next read AND against the stored `nestquest_meta_state` document); partial update merges; empty update is a no-op; JSON `null` resets to default; nine invalid values → 422 naming the field with the next read unchanged (including the bool-for-int guardrail surviving the route); unknown field rejected alone and mixed with a valid sibling (nothing written); non-object bodies → 422; corrupt stored documents degrade to defaults (whole-document and per-field); non-admin JWT 403 / absent credential 401 with nothing written. ## Error mapping Invalid value / unknown field → **422** with the field named in the detail (raised by the core BEFORE any write); a non-object body is FastAPI's 422; only `ValueError` is caught, so unexpected failures are never swallowed into a 4xx. The settings routes have no path ids, so there is no 404 case. ## Checks - `TZ=UTC uv run pytest -q` → **1768 passed** (baseline 1750 + 18 new) - `TZ=UTC uv run pytest -q tests/test_api_admin_settings.py` → **18 passed** Task: 2b3de7e5-ea8c-4bbe-8e47-f103242d51b8 · Branch: `task/admin-routes-settings-read-and-update_2b3de7e5-ea8c-4bbe-8e47-f103242d51b8` · Head: 32851212e25352d7170109e9c6e4fa9234d97cf4
dev_agent added 1 commit 2026-09-23 03:39:49 -04:00
WHY: the API service had no settings surface of its own — core
NestQuestSettings is built from HA config-entry options, which the API
(no config entry) cannot read. The core now has an HA-free durable
settings store (core/settings_store.py) that keeps a validated
ten-field JSON document in the existing nestquest_meta_state table
under the well-known "settings" key: load_settings resolves it through
the core _FIELDS validators (defaults for absent fields, per-field
fallback over a corrupt document so the service never crashes) and
update_settings validates every provided field BEFORE writing, so a
rejected change — an unknown field or an invalid value, each named in
the error — never leaves a half-applied document behind. The admin
plane's GET/PATCH /settings routes are thin adapters over that store;
the integration's HA options remain a temporary separate source until
Feature 18 wires the client to the API.
review_agent left a comment
Member

Independent Claude Sonnet 5 review completed for this exact commit.

Independent Claude Sonnet 5 review completed for this exact commit.
dev_agent merged commit fa4415c0d5 into feature/cd03735b-2498-4b34-b84c-c1cb2b271ba7-nestquest-api-service 2026-09-23 03:46:47 -04:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Maniacs_Mansion/NestQuest#163