@@ -52,25 +52,35 @@ def _require_tls_ca_path() -> str:
5252 return ca_path
5353
5454
55- _DIRECT_PORT_READY_TIMEOUT_S = 240.0
55+ _NODERED_HTTP_READY_TIMEOUT_S = 240.0
5656
5757
58- async def _direct_flows_request (mcp : Client , slug : str ) -> dict [str , Any ]:
59- """Call Node-RED's direct port, retrying while its HTTP server binds.
58+ async def _flows_request (
59+ mcp : Client , slug : str , * , port : int | None = None
60+ ) -> dict [str , Any ]:
61+ """Call Node-RED, retrying while its HTTP stack settles after restart.
6062
6163 Supervisor reports ``started`` before the app's nginx binds the mapped
6264 direct port (CONNECTION_FAILED), and nginx binds before Node-RED itself
63- listens on its upstream socket (502/503/504 with the front door open).
64- Retry only those transient shapes until the app answers with a settled
65- status; every other outcome goes back to the caller's assertions
65+ listens on its upstream socket (502/503/504). Core Ingress can expose the
66+ same window after Core's TLS restart, so both routes share this readiness
67+ gate. Retry only those transient shapes until the app answers with a
68+ settled status; every other outcome goes back to the caller's assertions
6669 unchanged.
6770 """
68- deadline = time .monotonic () + _DIRECT_PORT_READY_TIMEOUT_S
71+ deadline = time .monotonic () + _NODERED_HTTP_READY_TIMEOUT_S
72+ request : dict [str , Any ] = {
73+ "slug" : slug ,
74+ "path" : "/flows" ,
75+ "method" : "GET" ,
76+ }
77+ if port is not None :
78+ request ["port" ] = port
6979 while True :
7080 payload = await safe_call_tool (
7181 mcp ,
7282 "ha_manage_app" ,
73- { "slug" : slug , "path" : "/flows" , "method" : "GET" , "port" : 1880 } ,
83+ request ,
7484 )
7585 error = payload .get ("error" )
7686 code = error .get ("code" ) if isinstance (error , dict ) else None
@@ -84,6 +94,16 @@ async def _direct_flows_request(mcp: Client, slug: str) -> dict[str, Any]:
8494 await asyncio .sleep (3 )
8595
8696
97+ async def _direct_flows_request (mcp : Client , slug : str ) -> dict [str , Any ]:
98+ """Call Node-RED's direct port after its HTTP stack is ready."""
99+ return await _flows_request (mcp , slug , port = 1880 )
100+
101+
102+ async def _ingress_flows_request (mcp : Client , slug : str ) -> dict [str , Any ]:
103+ """Call Node-RED through Core Ingress after its HTTP stack is ready."""
104+ return await _flows_request (mcp , slug )
105+
106+
87107async def _set_front_door (
88108 mcp : Client , assertions : MCPAssertions , slug : str , enabled : bool
89109) -> None :
@@ -218,10 +238,7 @@ async def test_manage_app_reproduces_legacy_tls_failure_then_uses_fix(
218238 assert "IP address mismatch" in mismatch , mismatch
219239 assert "127.0.0.1" in mismatch , mismatch
220240
221- fixed_ingress = await assertions .call_tool_success (
222- "ha_manage_app" ,
223- {"slug" : slug , "path" : "/flows" , "method" : "GET" },
224- )
241+ fixed_ingress = await _ingress_flows_request (mcp , slug )
225242 assert fixed_ingress .get ("success" ) is True , fixed_ingress
226243 assert fixed_ingress .get ("status_code" ) == 200 , fixed_ingress
227244 assert isinstance (fixed_ingress .get ("response" ), list ), fixed_ingress
0 commit comments