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 kat =12 "peach-pair-v1:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyIsInZhdWx0IjoicG9wdWxhdGVkIn0"13 private val emptyKat =14 "peach-pair-v1:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyIsInZhdWx0IjoiZW1wdHkifQ"1516 @Test fun `matches browser invitation KAT with default populated vault`() {17 assertEquals(kat, createPairingInvitation("$relay/", token))18 val parsed = parsePairingInvitation(kat)19 assertEquals(PairingInvitation(relay, token, VaultStatus.POPULATED), parsed)20 assertEquals("$relay/r/$token", parsed!!.roomUrl())21 }2223 @Test fun `empty invitation round-trips as source role`() {24 assertEquals(emptyKat, createPairingInvitation(relay, token, VaultStatus.EMPTY))25 val parsed = parsePairingInvitation(emptyKat)26 assertEquals(PairingInvitation(relay, token, VaultStatus.EMPTY), parsed)27 }2829 @Test fun `explicit populated invitation round-trips as receiver role`() {30 val invitation = createPairingInvitation(relay, token, VaultStatus.POPULATED)31 val parsed = parsePairingInvitation(invitation)32 assertEquals(VaultStatus.POPULATED, parsed?.vaultStatus)33 }3435 @Test fun `missing vault field defaults to populated for historical compatibility`() {36 val older = raw("""{"relay":"$relay","token":"$token"}""")37 val parsed = parsePairingInvitation(older)38 assertEquals(VaultStatus.POPULATED, parsed?.vaultStatus)39 assertEquals(PairingInvitation(relay, token), parsed)40 }4142 @Test fun `unknown vault value defaults to populated`() {43 val weird = raw("""{"relay":"$relay","token":"$token","vault":"maybe"}""")44 val parsed = parsePairingInvitation(weird)45 assertEquals(VaultStatus.POPULATED, parsed?.vaultStatus)46 }4748 @Test fun `vaultStatus on the parsed invitation drives the adoption flow`() {49 val empty = parsePairingInvitation(emptyKat)50 val populated = parsePairingInvitation(kat)51 val legacy = parsePairingInvitation(raw("""{"relay":"$relay","token":"$token"}"""))52 assertNotNull(empty)53 assertNotNull(populated)54 assertNotNull(legacy)55 assertEquals(VaultStatus.EMPTY, empty!!.vaultStatus)56 assertEquals(VaultStatus.POPULATED, populated!!.vaultStatus)57 assertEquals(VaultStatus.POPULATED, legacy!!.vaultStatus)58 }5960 @Test fun `rejects malformed insecure and short-token invitations`() {61 assertNull(parsePairingInvitation("not-an-invitation"))62 assertNull(parsePairingInvitation("peach-pair-v1:%%%"))63 assertNull(parsePairingInvitation(raw("""{"relay":"ws://relay.test","token":"$token"}""")))64 assertNull(parsePairingInvitation(raw("""{"relay":"$relay","token":"short"}""")))65 }6667 private fun raw(json: String): String =68 "peach-pair-v1:" + java.util.Base64.getUrlEncoder().withoutPadding()69 .encodeToString(json.toByteArray())70}