package com.peachpasswords.sync import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Test class PairingInvitationTest { private val token = "AAECAwQFBgcICQoLDA0ODw" private val relay = "wss://relay.peachpasswords.com" private val staticKey = "01".repeat(32) private val kat = "peach-pair-v2:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyIsInZhdWx0IjoicG9wdWxhdGVkIn0" private val emptyKat = "peach-pair-v2:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyIsInZhdWx0IjoiZW1wdHkifQ" @Test fun `matches browser invitation KAT with default populated vault`() { assertEquals(kat, createPairingInvitation("$relay/", token)) val parsed = parsePairingInvitation(kat) assertEquals(PairingInvitation(relay, token, VaultStatus.POPULATED), parsed) assertEquals("$relay/r/$token", parsed!!.roomUrl()) } @Test fun `empty invitation round-trips as source role`() { assertEquals(emptyKat, createPairingInvitation(relay, token, VaultStatus.EMPTY)) val parsed = parsePairingInvitation(emptyKat) assertEquals(PairingInvitation(relay, token, VaultStatus.EMPTY), parsed) } @Test fun `explicit populated invitation round-trips as receiver role`() { val invitation = createPairingInvitation(relay, token, VaultStatus.POPULATED) val parsed = parsePairingInvitation(invitation) assertEquals(VaultStatus.POPULATED, parsed?.vaultStatus) } @Test fun `Noise static key round-trips and malformed keys fail closed`() { val invitation = createPairingInvitation( relay, token, VaultStatus.POPULATED, staticKey, ) assertEquals(staticKey, parsePairingInvitation(invitation)?.noiseStaticPublicKeyHex) assertNull(parsePairingInvitation(raw( """{"relay":"$relay","token":"$token","vault":"populated","static":"bad"}""", ))) } @Test fun `missing vault field defaults to populated for historical compatibility`() { val older = raw("""{"relay":"$relay","token":"$token"}""") val parsed = parsePairingInvitation(older) assertEquals(VaultStatus.POPULATED, parsed?.vaultStatus) assertEquals(PairingInvitation(relay, token), parsed) } @Test fun `unknown vault value defaults to populated`() { val weird = raw("""{"relay":"$relay","token":"$token","vault":"maybe"}""") val parsed = parsePairingInvitation(weird) assertEquals(VaultStatus.POPULATED, parsed?.vaultStatus) } @Test fun `vaultStatus on the parsed invitation drives the adoption flow`() { val empty = parsePairingInvitation(emptyKat) val populated = parsePairingInvitation(kat) val legacy = parsePairingInvitation(raw("""{"relay":"$relay","token":"$token"}""")) assertNotNull(empty) assertNotNull(populated) assertNotNull(legacy) assertEquals(VaultStatus.EMPTY, empty!!.vaultStatus) assertEquals(VaultStatus.POPULATED, populated!!.vaultStatus) assertEquals(VaultStatus.POPULATED, legacy!!.vaultStatus) } @Test fun `rejects malformed insecure and short-token invitations`() { assertNull(parsePairingInvitation("not-an-invitation")) assertNull(parsePairingInvitation("peach-pair-v2:%%%")) assertNull(parsePairingInvitation(raw("""{"relay":"ws://relay.test","token":"$token"}"""))) assertNull(parsePairingInvitation(raw("""{"relay":"$relay","token":"short"}"""))) } private fun raw(json: String): String = "peach-pair-v2:" + java.util.Base64.getUrlEncoder().withoutPadding() .encodeToString(json.toByteArray()) }