@@ -33,10 +33,10 @@ import org.junit.Test
3333import org.junit.jupiter.api.assertNull
3434import org.junit.runner.RunWith
3535import org.robolectric.Robolectric
36- import org.robolectric.annotation.Config
3736import org.robolectric.RobolectricTestRunner
3837import org.robolectric.Shadows
3938import org.robolectric.android.controller.ServiceController
39+ import org.robolectric.annotation.Config
4040import org.robolectric.shadows.ShadowVoiceInteractionService
4141
4242@OptIn(ExperimentalCoroutinesApi ::class )
@@ -356,4 +356,60 @@ class AssistVoiceInteractionServiceTest {
356356 // Both detections should have sent a broadcast, even though the second was debounced
357357 assertEquals(2 , wakeWordBroadcasts.size)
358358 }
359+
360+ @Test
361+ fun `Given service not ready when wake word detected then do not show session` () = runTest {
362+ val shadow = Shadows .shadowOf(service) as ShadowVoiceInteractionService
363+ coEvery { assistConfigManager.getSelectedWakeWordModel() } returns microWakeWordModelConfigs[0 ]
364+
365+ // Do NOT call onReady() - service is not ready
366+
367+ val intent = Intent ().apply {
368+ action = " io.homeassistant.companion.android.START_LISTENING"
369+ }
370+ service.onStartCommand(intent, 0 , 1 )
371+ advanceUntilIdle()
372+
373+ // Simulate wake word detection while service is not ready
374+ onWakeWordDetectedSlot.captured.invoke(microWakeWordModelConfigs[0 ])
375+ advanceUntilIdle()
376+
377+ // showSession should NOT have been called
378+ assertNull(shadow.lastSessionBundle)
379+ // But the listener should still have been stopped to release the microphone
380+ coVerify { wakeWordListener.stop() }
381+ }
382+
383+ @Test
384+ fun `Given service shut down after ready when wake word detected then do not show session` () = runTest {
385+ val shadow = Shadows .shadowOf(service) as ShadowVoiceInteractionService
386+ coEvery { assistConfigManager.getSelectedWakeWordModel() } returns microWakeWordModelConfigs[0 ]
387+
388+ // Make service ready and start listening so the wake word callback is captured
389+ service.onReady()
390+ advanceUntilIdle()
391+
392+ val intent = Intent ().apply {
393+ action = " io.homeassistant.companion.android.START_LISTENING"
394+ }
395+ service.onStartCommand(intent, 0 , 1 )
396+ advanceUntilIdle()
397+
398+ // Simulate a shutdown that races with the wake-word coroutine: when
399+ // stop() is called inside the launched coroutine, the service shuts down
400+ // concurrently (setting isServiceReady = false). Because launchAssist()
401+ // runs synchronously after stop() returns, only the isServiceReady guard
402+ // prevents showSession() from being called.
403+ coEvery { wakeWordListener.stop() } coAnswers {
404+ service.onShutdown()
405+ }
406+
407+ // Invoke the wake word callback to queue the coroutine, then advance
408+ onWakeWordDetectedSlot.captured.invoke(microWakeWordModelConfigs[0 ])
409+ advanceUntilIdle()
410+
411+ // showSession should NOT have been called because the service is no longer ready
412+ assertNull(shadow.lastSessionBundle)
413+ coVerify { wakeWordListener.stop() }
414+ }
359415}
0 commit comments