package com.peachpasswords.sync import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test /** * The fail-closed relay gate, mirroring the browser's `sync-settings.test.ts`. * Everything here is the pure decision; the store around it is only * SharedPreferences and a Context this suite has no reason to stand up. */ class RelayGateTest { private val selfHosted = "wss://relay.example.com" @Test fun `public dials peach's relay`() { assertEquals( RelayDecision.Dial(Rendezvous.DEFAULT_RELAY_URL), relayInForce(SyncMode.PUBLIC, Rendezvous.DEFAULT_RELAY_URL), ) } @Test fun `private with an address dials it`() { assertEquals( RelayDecision.Dial(selfHosted), relayInForce(SyncMode.PRIVATE, selfHosted), ) } @Test fun `private with no address refuses instead of falling back to public`() { assertTrue(isPrivateRelayUnconfigured(SyncMode.PRIVATE, Rendezvous.DEFAULT_RELAY_URL)) assertEquals( RelayDecision.Refuse(SyncOutcomeReason.PRIVATE_RELAY_UNSET), relayInForce(SyncMode.PRIVATE, Rendezvous.DEFAULT_RELAY_URL), ) } @Test fun `off refuses with sync_disabled`() { assertEquals( RelayDecision.Refuse(SyncOutcomeReason.SYNC_DISABLED), relayInForce(SyncMode.OFF, selfHosted), ) assertEquals("sync_disabled", SyncOutcomeReason.SYNC_DISABLED.wire()) assertEquals("private_relay_unset", SyncOutcomeReason.PRIVATE_RELAY_UNSET.wire()) } @Test fun `saving peach's own url lands on public, not a broken private`() { assertEquals(SyncMode.PUBLIC, modeForRelayUrl(Rendezvous.DEFAULT_RELAY_URL)) assertEquals(SyncMode.PRIVATE, modeForRelayUrl(selfHosted)) // Adoption hands the source's relay through this same path, so a // receiver that joined over the public relay must not be relabelled // private and stranded in the unconfigured state. assertFalse( isPrivateRelayUnconfigured( modeForRelayUrl(Rendezvous.DEFAULT_RELAY_URL), Rendezvous.DEFAULT_RELAY_URL, ), ) } /** * The one guarantee behind mesh propagation: [RelayAdoptionHost] and the * reconnect loop can only obtain a URL out of [RelayDecision.Dial], so an * unconfigured private relay leaves nothing to put in an invitation and * nothing to hand a peer. */ @Test fun `no relay is available to push to peers while private is unconfigured`() { val decision = relayInForce(SyncMode.PRIVATE, Rendezvous.DEFAULT_RELAY_URL) assertNull((decision as? RelayDecision.Dial)?.relayUrl) assertNull((relayInForce(SyncMode.OFF, selfHosted) as? RelayDecision.Dial)?.relayUrl) } @Test fun `a stored mode the app no longer understands is not a crash`() { assertNull(SyncMode.parse("lan_only")) assertNull(SyncMode.parse(null)) assertEquals(SyncMode.PRIVATE, SyncMode.parse("private")) } @Test fun `the private-relay refusal is a red light with the browser's words`() { val summary = SyncOutcomeSummary(reason = SyncOutcomeReason.PRIVATE_RELAY_UNSET) assertTrue(SyncOutcomeReason.PRIVATE_RELAY_UNSET.isFailure) assertEquals(SyncIndicator.ERROR, syncIndicator(summary, pairedCount = 1)) assertEquals("Private relay chosen but no address set", describeSyncOutcome(summary)) } }