From 4b6e196509d334cf9f24d6ab8d47f5943f525e23 Mon Sep 17 00:00:00 2001 From: Joshua Date: Mon, 28 Sep 2026 23:27:24 -0400 Subject: [PATCH] Record a timezone-only admin update as an explicit choice (task 45243478) update_settings() now persists timezone_configured=True when the changes supply timezone without timezone_configured, so Home Assistant's reported zone can no longer replace an admin-chosen zone (release review RR-001). An explicit timezone_configured is kept as supplied; the automatic adoption path is unchanged and still leaves the marker false. Co-Authored-By: Claude Opus 5.5 --- .../nestquest/core/settings_store.py | 9 +++++ tests/test_api_household_clock.py | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/custom_components/nestquest/core/settings_store.py b/custom_components/nestquest/core/settings_store.py index 586dbbc..ac6cda8 100644 --- a/custom_components/nestquest/core/settings_store.py +++ b/custom_components/nestquest/core/settings_store.py @@ -184,6 +184,11 @@ async def update_settings( in one atomic upsert; the updated settings are returned. The whole read → merge → write span runs inside the per-database settings lock, so concurrent updates queue instead of interleaving. + + Supplying ``timezone`` without ``timezone_configured`` records the + zone as the admin's explicit choice (``timezone_configured`` is + persisted ``True`` in the same write); an explicitly supplied + ``timezone_configured`` is kept exactly as given. """ if not isinstance(changes, Mapping): raise ValueError( @@ -199,6 +204,10 @@ async def update_settings( raise ValueError( f"unknown settings field(s): {listed}; known fields: {known}" ) + if "timezone" in changes and "timezone_configured" not in changes: + # An admin-supplied zone is an explicit choice, so Home + # Assistant's reported zone must no longer replace it. + changes = {**changes, "timezone_configured": True} async with _settings_lock(database): current = await load_settings(database) return await _write_merged(database, current, changes) diff --git a/tests/test_api_household_clock.py b/tests/test_api_household_clock.py index 7a158f9..1a94307 100644 --- a/tests/test_api_household_clock.py +++ b/tests/test_api_household_clock.py @@ -445,6 +445,44 @@ async def test_admin_chosen_zone_wins_over_reported_zone( assert stored["timezone_configured"] is True +# --- a timezone-only admin update is an explicit choice (task 45243478) ------ + + +@pytest.mark.parametrize("chosen", ["America/Los_Angeles", ""]) +async def test_timezone_only_admin_update_is_an_explicit_choice( + household: SimpleNamespace, chosen: str +) -> None: + """``PATCH {"timezone": ...}`` alone marks the zone admin-chosen.""" + await _adopt_new_york(household) + response = await household.client.patch( + "/api/v1/admin/settings", + json={"timezone": chosen}, + headers=_admin_headers(), + ) + assert response.status_code == 200 + assert response.json()["timezone_configured"] is True + # A conflicting report is no longer adopted. + await _panel_pack_bag(household, "America/Chicago") + stored = await _stored_settings(household) + assert stored["timezone"] == chosen + assert stored["timezone_configured"] is True + + +async def test_explicit_timezone_configured_false_is_respected( + household: SimpleNamespace, +) -> None: + """A supplied ``timezone_configured`` is persisted exactly as given.""" + response = await household.client.patch( + "/api/v1/admin/settings", + json={"timezone": "America/Chicago", "timezone_configured": False}, + headers=_admin_headers(), + ) + assert response.status_code == 200 + assert response.json()["timezone_configured"] is False + await _panel_pack_bag(household, HOUSEHOLD_ZONE) + assert (await _stored_settings(household))["timezone"] == HOUSEHOLD_ZONE + + async def test_invalid_reported_zone_is_ignored( household: SimpleNamespace, ) -> None: