PairingInvitationTest.kt

3.8 KB · raw

1package com.peachpasswords.sync23import org.junit.Assert.assertEquals4import org.junit.Assert.assertNotNull5import org.junit.Assert.assertNull6import org.junit.Test78class PairingInvitationTest {9    private val token = "AAECAwQFBgcICQoLDA0ODw"10    private val relay = "wss://relay.peachpasswords.com"11    private val staticKey = "01".repeat(32)12    private val kat =13        "peach-pair-v2:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyIsInZhdWx0IjoicG9wdWxhdGVkIn0"14    private val emptyKat =15        "peach-pair-v2:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyIsInZhdWx0IjoiZW1wdHkifQ"1617    @Test fun `matches browser invitation KAT with default populated vault`() {18        assertEquals(kat, createPairingInvitation("$relay/", token))19        val parsed = parsePairingInvitation(kat)20        assertEquals(PairingInvitation(relay, token, VaultStatus.POPULATED), parsed)21        assertEquals("$relay/r/$token", parsed!!.roomUrl())22    }2324    @Test fun `empty invitation round-trips as source role`() {25        assertEquals(emptyKat, createPairingInvitation(relay, token, VaultStatus.EMPTY))26        val parsed = parsePairingInvitation(emptyKat)27        assertEquals(PairingInvitation(relay, token, VaultStatus.EMPTY), parsed)28    }2930    @Test fun `explicit populated invitation round-trips as receiver role`() {31        val invitation = createPairingInvitation(relay, token, VaultStatus.POPULATED)32        val parsed = parsePairingInvitation(invitation)33        assertEquals(VaultStatus.POPULATED, parsed?.vaultStatus)34    }3536    @Test fun `Noise static key round-trips and malformed keys fail closed`() {37        val invitation = createPairingInvitation(38            relay,39            token,40            VaultStatus.POPULATED,41            staticKey,42        )43        assertEquals(staticKey, parsePairingInvitation(invitation)?.noiseStaticPublicKeyHex)44        assertNull(parsePairingInvitation(raw(45            """{"relay":"$relay","token":"$token","vault":"populated","static":"bad"}""",46        )))47    }4849    @Test fun `missing vault field defaults to populated for historical compatibility`() {50        val older = raw("""{"relay":"$relay","token":"$token"}""")51        val parsed = parsePairingInvitation(older)52        assertEquals(VaultStatus.POPULATED, parsed?.vaultStatus)53        assertEquals(PairingInvitation(relay, token), parsed)54    }5556    @Test fun `unknown vault value defaults to populated`() {57        val weird = raw("""{"relay":"$relay","token":"$token","vault":"maybe"}""")58        val parsed = parsePairingInvitation(weird)59        assertEquals(VaultStatus.POPULATED, parsed?.vaultStatus)60    }6162    @Test fun `vaultStatus on the parsed invitation drives the adoption flow`() {63        val empty = parsePairingInvitation(emptyKat)64        val populated = parsePairingInvitation(kat)65        val legacy = parsePairingInvitation(raw("""{"relay":"$relay","token":"$token"}"""))66        assertNotNull(empty)67        assertNotNull(populated)68        assertNotNull(legacy)69        assertEquals(VaultStatus.EMPTY, empty!!.vaultStatus)70        assertEquals(VaultStatus.POPULATED, populated!!.vaultStatus)71        assertEquals(VaultStatus.POPULATED, legacy!!.vaultStatus)72    }7374    @Test fun `rejects malformed insecure and short-token invitations`() {75        assertNull(parsePairingInvitation("not-an-invitation"))76        assertNull(parsePairingInvitation("peach-pair-v2:%%%"))77        assertNull(parsePairingInvitation(raw("""{"relay":"ws://relay.test","token":"$token"}""")))78        assertNull(parsePairingInvitation(raw("""{"relay":"$relay","token":"short"}""")))79    }8081    private fun raw(json: String): String =82        "peach-pair-v2:" + java.util.Base64.getUrlEncoder().withoutPadding()83            .encodeToString(json.toByteArray())84}