Adopt Home Assistant's time zone for the API household clock

The deployed API host runs UTC and the household `timezone` setting was
never set, so the panel snapshot read UTC: morning quests showed overdue
hours early and `today_iso` rolled over at UTC midnight.

The integration now reports `hass.config.time_zone` on every panel
request (`X-NestQuest-Timezone`).  While the admin has not chosen a zone
(`timezone_configured` is false) the API adopts and persists it, so the
panel, the admin plane, and the missed-sweep scheduler all read the
household clock out of the box.  An explicit admin choice — including an
explicitly chosen empty zone — still wins; an invalid reported zone is
logged and ignored.  No schema change.

Task: 6351b36d-0eef-4e7d-bf5a-96b754cbc883

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 22:05:16 -04:00
co-authored by Claude Opus 5.5
parent 07f2eec3bf
commit 09d6a55e04
9 changed files with 285 additions and 45 deletions
+33 -3
View File
@@ -196,6 +196,27 @@ async def test_complete_instance_posts_body_and_headers() -> None:
assert call["headers"]["Authorization"] == f"Bearer {TOKEN}"
async def test_household_timezone_rides_every_request_header() -> None:
"""Home Assistant's zone is reported so the API reads the household
clock (task 6351b36d); it is sent only when the client has one."""
transport = StubTransport(
StubResponse(200, json.dumps(SNAPSHOT_PAYLOAD)),
StubResponse(200, '{"status": "done"}'),
)
client = NestQuestApiClient(
BASE_URL, TOKEN, transport, household_timezone="America/New_York"
)
await client.get_snapshot()
await client.complete_instance(42, 3)
for call in transport.calls:
assert call["headers"] == {
"Authorization": f"Bearer {TOKEN}",
"X-NestQuest-Timezone": "America/New_York",
}
async def test_base_url_trailing_slash_is_normalized() -> None:
"""A base URL with a trailing slash must not double the path slash."""
transport = StubTransport(StubResponse(200, "{}"))
@@ -392,11 +413,20 @@ def test_client_from_entry_reads_config_and_shared_session(monkeypatch) -> None:
}
)
client = api_client.client_from_entry(object(), entry)
hass = types.SimpleNamespace(
config=types.SimpleNamespace(time_zone="America/New_York")
)
client = api_client.client_from_entry(hass, entry)
assert client._session is shared_session
assert client._base_url == "http://panel.lan:8443"
assert client._panel_token == "entry-token"
assert client._household_timezone == "America/New_York"
#: A minimal hass stand-in carrying the configured time zone.
_HASS_UTC = types.SimpleNamespace(config=types.SimpleNamespace(time_zone="UTC"))
def test_client_from_entry_unconfigured_token_raises_at_construction(
@@ -419,12 +449,12 @@ def test_client_from_entry_unconfigured_token_raises_at_construction(
assert resolve_api_config(entry)[1] == DEFAULT_PANEL_TOKEN == ""
with pytest.raises(ValueError, match="panel_token must be a non-empty"):
api_client.client_from_entry(object(), entry)
api_client.client_from_entry(_HASS_UTC, entry)
# Same for a whitespace-only stored token: whitespace is not a token.
entry_ws = make_config_entry(options={CONF_PANEL_TOKEN: " "})
with pytest.raises(ValueError, match="panel_token must be a non-empty"):
api_client.client_from_entry(object(), entry_ws)
api_client.client_from_entry(_HASS_UTC, entry_ws)
async def test_client_logs_nothing() -> None: