diff --git a/app/src/test/kotlin/io/flamenet/messenger/app/SecondDeviceCallTest.kt b/app/src/test/kotlin/io/flamenet/messenger/app/SecondDeviceCallTest.kt new file mode 100644 index 0000000..f0c8692 --- /dev/null +++ b/app/src/test/kotlin/io/flamenet/messenger/app/SecondDeviceCallTest.kt @@ -0,0 +1,62 @@ +package io.flamenet.messenger.app + +import java.io.File +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/** + * One account on two devices: a ring on the second must not end a call that + * was answered on the first. + * + * `CallManager` needs WebRTC, which the JVM test runtime does not have, so this + * reads the source the way the iPhone's `RingTimesOutTests` does. That iPhone + * test reads this file as well, but only when the two checkouts sit side by + * side under the directory names it expects, which a CI build directory never + * has. This copy is the one that actually runs on every Android pipeline. + */ +class SecondDeviceCallTest { + private val source: String by lazy { + listOf( + "src/main/kotlin/io/flamenet/messenger/app/CallManager.kt", + "app/src/main/kotlin/io/flamenet/messenger/app/CallManager.kt", + ).map(::File).first { it.exists() }.readText() + } + + /** The function starting at [marker], up to the next one. */ + private fun body(marker: String): String { + val start = source.indexOf(marker) + assertTrue(start >= 0, "no $marker in CallManager.kt") + val rest = source.substring(start + marker.length) + val end = rest.indexOf("private fun ").let { if (it < 0) rest.length else it } + return rest.substring(0, end) + } + + @Test + fun `the ring guard releases the ring without telling the caller`() { + val guard = body("private fun armRingGuard()") + assertTrue(guard.contains("notifyPeer = false"), "the guard must not message the caller") + // Bounded to the guard: the connect guard below it still, correctly, + // hangs up with notifyPeer = true. + assertFalse( + guard.contains("notifyPeer = true"), + "a timeout sent from a second device hangs up a call answered on the first", + ) + } + + @Test + fun `an end hangs up only its own call, and not once that call has connected`() { + val start = source.indexOf("Content.CallSignal.END ->") + assertTrue(start >= 0, "no end branch in onSignal") + val branch = source.substring(start, minOf(source.length, start + 2000)) + assertTrue( + branch.contains("incoming.callId != current?.callId"), + "an end for another call must not hang this one up", + ) + assertTrue(branch.contains("setOf(\"declined\", \"busy\", \"timeout\")")) + assertTrue( + branch.contains("state == State.CONNECTING || state == State.ACTIVE"), + "only a call that has connected may ignore them; a ringing one must still stop", + ) + } +}