Skip to content

Commit 1aa2d7a

Browse files
Merge branch 'master' into fix/issue-1370-yaml-cache
2 parents 8875bf3 + 7ec57f7 commit 1aa2d7a

2 files changed

Lines changed: 72 additions & 0 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,13 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
353353
- **[@w3z315](https://github.com/w3z315)** — Financial support via [GitHub Sponsors](https://github.com/sponsors/julienld). Thank you! ☕
354354
- **[@griffinmartin](https://github.com/griffinmartin)** — Added OpenCode (by Anomaly) as a selectable AI client in the setup wizard, with both stdio and streamable HTTP support.
355355
- **[@hhopke](https://github.com/hhopke)** — Fixed addon API calls to route through HA Core ingress proxy instead of direct container connections, fixing `ha_manage_addon` proxy mode on addon installs.
356+
- **[@tomwilkie](https://github.com/tomwilkie)** — JMESPath middleware exploration (#1147) whose review-time token-measurement data informed the design of #1199 and #1225.
357+
- **[@SealKan](https://github.com/SealKan)**`fields=`/`attribute_keys=` projection on six read-heavy tools (#1225), `ha_call_event` tool (#1239), and dashboards-list helper refactor (#1207).
358+
- **[@KarelTestSpecial](https://github.com/KarelTestSpecial)** — Cached YAML instance to prevent CPU spikes during bulk edits (#1371).
359+
- **[@corgan2222](https://github.com/corgan2222)** — HA brand assets for custom integration (#1317).
360+
- **[@drseanwing](https://github.com/drseanwing)** — Progress emission via FastMCP `Context` in long-running tools (#1124); tool-discovery / categorized-search docs (#1123).
361+
- **[@fnordpig](https://github.com/fnordpig)** — Config subentry support (#1393) and Assist pipeline management tool (#1392).
362+
- **[@paul43210](https://github.com/paul43210)**`array_patch` mode in `ha_manage_addon` for atomic GET-modify-POST (#1063).
356363

357364
---
358365

tests/src/e2e/haos_only/test_manage_addon_modes.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,29 @@
4545

4646
from __future__ import annotations
4747

48+
import asyncio
49+
import logging
50+
import time
4851
from typing import Any
4952

5053
import pytest
5154

5255
from ..utilities.assertions import parse_mcp_result, safe_call_tool
56+
from ..utilities.wait_helpers import _POLLING_TRANSIENT_ERRORS
57+
58+
logger = logging.getLogger(__name__)
5359

5460
pytestmark = [pytest.mark.haos_only]
5561

62+
# Tests that assert strictly on ``status_code`` (with no fall-back error
63+
# branch) need the addon's container to have reached Supervisor's
64+
# ``started`` state — the bake installs every addon with ``start=True``,
65+
# but the container can take tens of seconds to leave its transient boot
66+
# phase, which is enough to flake the strict assertion. Timeout sized
67+
# for cache-cold runners; 2s poll matches sibling lifecycle helpers.
68+
_ADDON_RUNNING_TIMEOUT_S = 120.0
69+
_ADDON_RUNNING_POLL_S = 2.0
70+
5671

5772
# Display names as they appear in build_image.py's ADDONS tuple — slugs
5873
# are looked up dynamically below to survive the SHA-derived slug prefix.
@@ -88,6 +103,53 @@ async def _resolve_slug(mcp_client: Any, display_name: str) -> str:
88103
)
89104

90105

106+
async def _wait_addon_running(
107+
mcp_client: Any,
108+
slug: str,
109+
timeout: float = _ADDON_RUNNING_TIMEOUT_S,
110+
) -> None:
111+
"""Block until ``ha_get_addon(slug=...)`` reports ``state=started``.
112+
113+
Use this before any test that asserts on the HTTP/WS contract of an
114+
addon (rather than tolerating an addon-not-running structured
115+
error). When Supervisor reports the addon as anything other than
116+
``started``, ``ha_manage_addon`` raises ``ToolError`` from its
117+
running-state guard (``tools_addons.py`` "Verify add-on is running");
118+
the JSON-encoded error payload carries the observed transient state.
119+
The bake installs addons with ``start=True``, but their containers
120+
can take tens of seconds to reach ``started`` — long enough to
121+
flake any strict-shape assertion on the proxy path. Mirrors
122+
``_wait_for_state`` in ``test_addon_lifecycle.py`` (same private-
123+
sibling convention as ``_resolve_slug``).
124+
125+
Transient errors from ``ha_get_addon`` are caught via the project's
126+
canonical ``_POLLING_TRANSIENT_ERRORS`` tuple (see
127+
``tests/src/e2e/utilities/wait_helpers.py``) — the same discipline
128+
every other polling helper in the suite uses. Bugs (``TypeError`` /
129+
``AttributeError`` / ``KeyError`` / ``AssertionError``) propagate.
130+
The deadline still fires; transient errors can't mask a wedged
131+
addon forever.
132+
"""
133+
deadline = time.monotonic() + timeout
134+
last_state: str | None = None
135+
while True:
136+
try:
137+
detail_raw = await mcp_client.call_tool("ha_get_addon", {"slug": slug})
138+
detail = parse_mcp_result(detail_raw).get("addon") or {}
139+
last_state = detail.get("state")
140+
except _POLLING_TRANSIENT_ERRORS as e:
141+
logger.debug(f"⚠️ Transient error polling addon {slug!r}: {e}")
142+
last_state = f"<transient: {str(e)[:60]}>"
143+
if last_state == "started":
144+
return
145+
if time.monotonic() >= deadline:
146+
pytest.fail(
147+
f"Addon {slug!r} did not reach state=started within "
148+
f"{timeout:.0f}s (last state: {last_state!r})"
149+
)
150+
await asyncio.sleep(_ADDON_RUNNING_POLL_S)
151+
152+
91153
# ---------------------------------------------------------------------------
92154
# Config mode — options / boot / auto_update / watchdog round-trips
93155
# ---------------------------------------------------------------------------
@@ -234,6 +296,9 @@ async def test_proxy_http_request_headers_pass_through(mcp_client: Any) -> None:
234296
invalid", which proves the value crossed the wire.
235297
"""
236298
slug = await _resolve_slug(mcp_client, NODERED_NAME)
299+
# Strict assertion on ``status_code`` below requires the addon to
300+
# actually answer HTTP; wait it out (see ``_wait_addon_running``).
301+
await _wait_addon_running(mcp_client, slug)
237302
without = await safe_call_tool(
238303
mcp_client,
239304
"ha_manage_addon",

0 commit comments

Comments
 (0)