1+ --- Test for Hue bridge SSE connection lifecycle.
2+ --- Migrated to use connection_scenario 2.0.
3+ ---
4+ --- This test uses a hybrid approach combining helpers with dynamic queueing:
5+ --- - Uses SSE event builder helpers (light_event()) for cleaner event construction
6+ --- - Uses dynamic queue_http_response() and queue_sse_event() for complex lifecycle timing
7+ --- - Cannot use pre-defined expectations because each test needs different response sequences
8+ --- - The double-refresh pattern (both .added and .init inject refresh) requires dynamic handling
9+
110local test = require " integration_test"
211local capabilities = require " st.capabilities"
312local mock_devices_api = require " integration_test.mock_devices_api"
4- local mock_lan_socket = require " integration_test.mock_lan_socket"
513local hue_test_helpers = require " test.hue_test_helpers"
614
715local LIGHT_RID = " 22222222-2222-2222-2222-222222222222"
@@ -12,18 +20,16 @@ local NEW_DEVICE_RID = "66666666-6666-6666-6666-666666666666"
1220local NEW_LIGHT_RID = " 77777777-7777-7777-7777-777777777777"
1321local NEW_LIGHT_NAME = " New Hue Light"
1422
15- local mock_bridge , mock_light , get_bridge_server , test_init , get_sse_connection =
16- hue_test_helpers .build_paired_bridge_and_light (
17- LIGHT_RID ,
18- {
23+ local mock_bridge , mock_light , get_bridge_server , base_test_init , get_sse_connection =
24+ hue_test_helpers .HueDeviceBuilder . new ()
25+ : with_bridge ()
26+ : with_light ( LIGHT_RID , {
1927 on = { on = true },
2028 dimming = { brightness = 80 },
2129 hue_device_id = HUE_DEVICE_ID ,
22- },
23- " white-and-color-ambiance.yml" ,
24- nil ,
25- { enable_sse = true }
26- )
30+ })
31+ :enable_sse ()
32+ :start ()
2733
2834local function expect_switch_and_level_emit ()
2935 test .socket .capability :__expect_send (
5157-- full SSE connect sequence below -- independently, same as any other Hue test file.)
5258test .set_test_init_function (function ()
5359 expect_switch_and_level_emit () -- from .added's injected refresh
54- test_init () -- registers .init's levelRange emit
60+ base_test_init () -- registers .init's levelRange emit
5561 expect_switch_and_level_emit () -- from .init's injected refresh
5662end )
5763
58- --- That injected refresh's own connect() and the SSE EventSource's own connect() both race for
59- --- this address right after test_init returns, and *which one* claims the single "sse"
60- --- reservation test_init made isn't reliably ordered (it can flip depending on unrelated timing,
61- --- e.g. table/coroutine hashing) -- so this doesn't assume an order; it peeks at whichever
62- --- connection actually claimed the "sse" label's first sent bytes (a non-consuming read, unlike
63- --- `assert_http_request_received`) to tell the two apart, then returns them correctly identified.
64- ---
65- --- @return integration_test.LanMockServer sse the real SSE stream
66- --- @return integration_test.LanMockServer rest the bridge ' s persistent REST connection
67- local function identify_sse_and_rest_connections ()
68- test .wait_for_events ()
69- local sse_entry = mock_lan_socket .tcp_registry .get_labeled (hue_test_helpers .BRIDGE_IP , 443 , " sse" )
70- local sent_so_far = table.concat (sse_entry .sent_log )
71- if sent_so_far :match (" ^GET /eventstream/clip/v2 " ) then
72- return get_sse_connection (), get_bridge_server ()
73- end
74- return get_bridge_server (), get_sse_connection ()
75- end
76-
7764--- Answers the REST calls `LightLifecycleHandlers.added`'s injected refresh makes (see above) --
7865--- the same zigbee-connectivity-then-light-state sequence test_hue_light_refresh.lua exercises
7966--- directly. All of the bridge's REST calls share one persistent connection (one PhilipsHueApi
8067--- instance, one worker thread processing requests serially), so this unconditional first
8168--- request has to be drained before anything else can get its response -- otherwise it blocks
8269--- every later REST call (including the SSE onopen's own connectivity poll) behind it.
8370---
84- --- @param rest integration_test.LanMockServer the bridge ' s REST connection (see
85- --- identify_sse_and_rest_connections)
86- local function answer_initial_light_refresh (rest )
71+ --- @param rest integration_test.connection_scenario.Connection the bridge ' s REST connection
72+ --- @param light_on boolean | nil whether the light should be on (default : true )
73+ --- @param light_brightness number | nil the light brightness (default : 80)
74+ local function answer_initial_light_refresh (rest , light_on , light_brightness )
75+ light_on = light_on == nil and true or light_on
76+ light_brightness = light_brightness or 80
77+
8778 rest :queue_http_response (200 , {}, {
8879 errors = {},
8980 data = { { services = { { rtype = " zigbee_connectivity" , rid = ZIGBEE_RID } } } },
@@ -94,15 +85,15 @@ local function answer_initial_light_refresh(rest)
9485 })
9586 rest :queue_http_response (200 , {}, {
9687 errors = {},
97- data = { { id = LIGHT_RID , on = { on = true }, dimming = { brightness = 80 } } },
88+ data = { { id = LIGHT_RID , on = { on = light_on }, dimming = { brightness = light_brightness } } },
9889 })
9990 test .wait_for_events ()
10091 rest :assert_http_request_received (" GET" , " /clip/v2/resource/device/" .. HUE_DEVICE_ID )
10192 rest :assert_http_request_received (" GET" , " /clip/v2/resource/zigbee_connectivity/" .. ZIGBEE_RID )
10293 rest :assert_http_request_received (" GET" , " /clip/v2/resource/light/" .. LIGHT_RID )
10394end
10495
105- --- Drives one full SSE connect: identifies which connection is which (see above) , drains the
96+ --- Drives one full SSE connect: gets the connections , drains the
10697--- unconditional initial light refresh, then the EventSource handshake, then the
10798--- connectivity-status poll `onopen` makes before it settles (which also finishes flushing the
10899--- light's own `init` lifecycle -- its levelRange emit shares scheduler turns with all of this,
@@ -111,10 +102,13 @@ end
111102--- bridge/light/EventSource per test (see above), so there's no persistent connection to share
112103--- across tests the way there might be within a single production run.
113104---
114- --- @return integration_test.LanMockServer sse
115- --- @return integration_test.LanMockServer rest
105+ --- @return integration_test.connection_scenario.Connection sse
106+ --- @return integration_test.connection_scenario.Connection rest
116107local function connect_sse ()
117- local sse , rest = identify_sse_and_rest_connections ()
108+ test .wait_for_events ()
109+ local sse = get_sse_connection ()
110+ local rest = get_bridge_server ()
111+
118112 answer_initial_light_refresh (rest ) -- LightLifecycleHandlers.added's injected refresh
119113 answer_initial_light_refresh (rest ) -- LightLifecycleHandlers.init's injected refresh
120114
@@ -169,11 +163,9 @@ test.register_coroutine_test(
169163 function ()
170164 local sse = connect_sse ()
171165
166+ -- Use helper to build the SSE event
172167 sse :queue_sse_event ({
173- {
174- type = " update" ,
175- data = { { type = " light" , id = LIGHT_RID , on = { on = false }, dimming = { brightness = 42 } } },
176- },
168+ hue_test_helpers .light_event (LIGHT_RID , false , 42 )
177169 })
178170 test .socket .capability :__expect_send (
179171 mock_light :generate_test_message (" main" , capabilities .switch .switch .off ())
0 commit comments