From 314b77242ff4bc6e16e7df6dd48ffc2ea5663d74 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 09:11:19 +0100 Subject: [PATCH] Test call route persistence and busy outcome failure isolation --- .../chat/data/CallServiceLifecycleTest.kt | 23 +++++++++++++++++++ .../kotlin/com/utun/chat/RouterHostApp.kt | 1 + .../utun/chat/data/RouterHostEnvironment.kt | 3 ++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/tools/chatgui-android/call-service-tests/src/test/kotlin/com/utun/chat/data/CallServiceLifecycleTest.kt b/tools/chatgui-android/call-service-tests/src/test/kotlin/com/utun/chat/data/CallServiceLifecycleTest.kt index d188d980..4f248d98 100644 --- a/tools/chatgui-android/call-service-tests/src/test/kotlin/com/utun/chat/data/CallServiceLifecycleTest.kt +++ b/tools/chatgui-android/call-service-tests/src/test/kotlin/com/utun/chat/data/CallServiceLifecycleTest.kt @@ -178,6 +178,29 @@ class CallServiceLifecycleTest { assertEquals(attempts, NativeLib.callCommands.size) } + @Test fun failedRoutePreferenceWriteDoesNotEscapeTheControllerScope() { + active(); val previous = context.configProvider.getCallAudioRoute() + context.configProvider.failCallRouteSave = true + CallController.setRoute(AudioRoute.SPEAKER); dispatcher.scheduler.runCurrent() + assertEquals(previous, context.configProvider.getCallAudioRoute()); assertEquals(CallPhase.ACTIVE, state?.phase) + diagnostic("Cannot save preferred route") + } + + @Test fun lateRouteInputAfterEndCannotChangeNextCallPreference() { + active(); CallController.end(); dispatcher.scheduler.runCurrent() + val previous = context.configProvider.getCallAudioRoute() + CallController.setRoute(AudioRoute.SPEAKER); dispatcher.scheduler.runCurrent() + assertEquals(previous, context.configProvider.getCallAudioRoute()); diagnostic("route ignored outside active call") + } + + @Test fun failedBusyOutcomeTranslationDoesNotSkipCallCleanupOrStateExpiry() { + active(); AppLanguage.failText = true + CallController.end(reason = 8); dispatcher.scheduler.runCurrent() + assertEquals(CallPhase.ENDED, state?.phase); assertFalse(power.locks.single().held) + diagnostic("Busy outcome notification failed") + dispatcher.scheduler.advanceTimeBy(2500); dispatcher.scheduler.runCurrent(); assertNull(state) + } + @Test fun normalStartupOwnsOneLockReceiverAndForegroundBeforeAudio() { val service = create(); val id = request(); start(service, id) assertTrue(power.locks.single().held); assertFalse(power.locks.single().counted) diff --git a/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/RouterHostApp.kt b/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/RouterHostApp.kt index f8c34d41..04c196b2 100644 --- a/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/RouterHostApp.kt +++ b/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/RouterHostApp.kt @@ -31,6 +31,7 @@ object R { const val connection_indirect = 143; const val call_incoming_stop_media = 144; const val call_outgoing_stop_media = 145 const val call_incoming_stop_radio = 146; const val call_outgoing_stop_radio = 147 const val call_incoming_stop_stream = 148; const val call_outgoing_stop_stream = 149 + const val call_route_save_failed = 150 const val files_preparing = 201; const val files_transferring = 202; const val files_completed = 203 const val files_cancelled = 204; const val files_failed = 205; const val files_replace_title = 206; const val files_saving = 207 const val stream_resources_busy = 301; const val stream_failed = 302; const val stream_mode_restored = 303 diff --git a/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/data/RouterHostEnvironment.kt b/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/data/RouterHostEnvironment.kt index 9b0cd922..6f2983a4 100644 --- a/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/data/RouterHostEnvironment.kt +++ b/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/data/RouterHostEnvironment.kt @@ -201,8 +201,9 @@ class HostConfigProvider { if (failStreamSave) it.completeExceptionally(IllegalStateException("stream settings save failed")) else it.complete(Unit) } var callRoute = AudioRoute.EARPIECE + var failCallRouteSave = false fun getCallAudioRoute() = callRoute - suspend fun setCallAudioRoute(route: AudioRoute) { callRoute = route } + suspend fun setCallAudioRoute(route: AudioRoute) { check(!failCallRouteSave) { "call route settings unavailable" }; callRoute = route } @JvmField var radioOverlay = false var failOverlayWrite = false fun getRadioOverlay() = radioOverlay