36feed1efeba

test: pin clean-close string + dialer cooldown spec (v2.9.16)

Cross-polarity: Firefox + Chrome both paired via the new relay. Two clean sessions each, both closed with reason=relay session complete. That is the behavior we want to lock in; the dialer idle-burn (5_000ms redial into a closed socket, 20s ping/pong timeout per cycle) is the bug to fix later. - Extract relaySocketCloseReason(error: Throwable?) from the private logRelaySocketClosed so the success string is directly testable. One-line behavior change in the call site. - New test: clean socket close reason is the literal relay

time
2026-07-31 10:50 UTC · 2 months ago
machine
koh2
parent
877dd0a16304
3 files VERSION+1 −1app/src/main/java/com/peachpasswords/sync/CloudRelayReconnectListener.kt+4 −1app/src/test/java/com/peachpasswords/sync/CloudRelayReconnectListenerTest.kt+24 −0
VERSION+1 −1
@@ -1 +1 @@1-2.9.151+2.9.16\ No newline at end of file

view file

app/src/main/java/com/peachpasswords/sync/CloudRelayReconnectListener.kt+4 −1
@@ -88,6 +88,9 @@8888     error.message?.takeIf { it.isNotBlank() } ?: error.javaClass.simpleName,8989 )9090 91+internal fun relaySocketCloseReason(error: Throwable?): String =92+    error?.let(::relayLogReason) ?: "relay session complete"93+9194 internal data class RelayReconcileInventory(9295     val paired: List<PairedDevice>,9396     val eligible: List<PairedDevice>,@@ -447,7 +450,7 @@447450 )448451 449452 private fun logRelaySocketClosed(error: Throwable?) {450-    val reason = error?.let(::relayLogReason) ?: "relay session complete"453+    val reason = relaySocketCloseReason(error)451454     val outcome = error?.let { classifySyncError(reason) }452455     val message = "cloud-relay: socket closed — reason=$reason"453456     when {

view file

app/src/test/java/com/peachpasswords/sync/CloudRelayReconnectListenerTest.kt+24 −0
@@ -78,6 +78,30 @@7878     }7979 8080     @Test81+    fun `clean socket close reason is the literal "relay session complete" string`() {82+        // Presence of this exact string in logcat is how we grep for a successful83+        // sync close-out. Anything else (a renamed literal, a swallowed exception)84+        // breaks that grep and the avoidable idle-burn diagnosis.85+        assertEquals("relay session complete", relaySocketCloseReason(error = null))86+        assertEquals(87+            "session ended by peer",88+            relaySocketCloseReason(error = RuntimeException("session ended by peer")),89+        )90+    }91+92+    @Test93+    fun `clean-complete dial ramps the dialer cooldown above the reconcile hot loop`() {94+        // After a clean "relay session complete" close, the dialer would otherwise95+        // re-dial at the reconcile cadence (DIAL_RETRY_DELAY_MS=5_000). That is the96+        // idle-burn visible in logcat: a 20s ping/pong timeout per cycle. The fix97+        // must keep the next dial well outside the hot loop. Floor: 2x.98+        assertTrue(99+            "expected clean-complete dialer cooldown >= 10_000 (2x the 5_000 hot loop)",100+            nextRelayRetryDelay(current = 5_000L, succeeded = true, dialer = true) >= 10_000L,101+        )102+    }103+104+    @Test81105     fun `reconcile logs paired and eligible counts when no peer passes the filter`() = runBlocking {82106         val devices = listOf(83107             PairedDevice(

view file