1package com.peachpasswords.sync23import org.junit.Assert.assertEquals4import org.junit.Assert.assertNull5import org.junit.Test67class PairedDeviceRevocationTest {8 private val browser = PairedDevice(9 deviceId = "browser-1",10 deviceName = "Chrome",11 deviceType = "extension",12 longTermPublicKeyHex = "ab".repeat(32),13 pairedAt = 1L,14 )1516 @Test17 fun `new revocation records the key and removes the paired row`() {18 val mutation = planDeviceRevocation(19 devices = listOf(browser),20 revocations = emptyList(),21 deviceId = browser.deviceId,22 revokedByDeviceId = "phone-1",23 nowMs = 42L,24 )2526 assertEquals(emptyList<PairedDevice>(), mutation.devices)27 assertEquals(1, mutation.revocations.size)28 assertEquals(browser.longTermPublicKeyHex, mutation.added?.longTermPublicKeyHex)29 assertEquals(42L, mutation.added?.revokedAt)30 }3132 @Test33 fun `full ledger admits another issuer by evicting the largest issuer oldest record`() {34 val existing = List(500) { index ->35 DeviceRevocation("terminal-$index", index.toString(16).padStart(64, '0'), index.toLong(), "issuer-a")36 }37 val merged = mergeRevocationRecords(existing, listOf(38 DeviceRevocation("peer-extra", "ff".repeat(32), 501L, "issuer-b"),39 ))40 assertEquals(500, merged.records.size)41 assertEquals(listOf("peer-extra"), merged.admitted.map { it.deviceId })42 assertEquals(false, merged.records.any { it.deviceId == "terminal-0" })43 assertEquals(true, merged.records.any { it.deviceId == "peer-extra" })44 }4546 @Test47 fun `same issuer local flood uses bounded capacity`() {48 var records = emptyList<DeviceRevocation>()49 repeat(501) { index ->50 records = if (records.size < 500) records + DeviceRevocation("local-$index", index.toString(16).padStart(64, '0'), index.toLong(), "issuer-a")51 else records.drop(1) + DeviceRevocation("local-$index", index.toString(16).padStart(64, '0'), index.toLong(), "issuer-a")52 }53 assertEquals(500, records.size)54 assertEquals("local-1", records.first().deviceId)55 assertEquals("local-500", records.last().deviceId)56 }5758 @Test59 fun `different issuer local capacity preserves genuine issuer record`() {60 val existing = List(500) { index ->61 DeviceRevocation("terminal-$index", index.toString(16).padStart(64, '0'), index.toLong(), if (index == 0) "issuer-a" else "issuer-b")62 }63 val removable = existing.indexOfFirst { it.revokedByDeviceId != "issuer-a" }64 val records = existing.toMutableList().also { it.removeAt(removable); it += DeviceRevocation("local-new", "ff".repeat(32), 501L, "issuer-a") }65 assertEquals(500, records.size)66 assertEquals("terminal-0", records.first().deviceId)67 assertEquals("local-new", records.last().deviceId)68 }6970 @Test71 fun `peer flood evicts only its own oldest records once it is largest issuer`() {72 val existing = listOf(73 DeviceRevocation("genuine", "aa".repeat(32), 1L, "honest-issuer"),74 )75 val flood = List(600) { index ->76 DeviceRevocation(77 "junk-$index",78 index.toString(16).padStart(64, '0'),79 index.toLong() + 2,80 "flooding-issuer",81 )82 }8384 val merged = mergeRevocationRecords(existing, flood, setOf("honest-issuer"))8586 assertEquals(500, merged.records.size)87 assertEquals(true, merged.records.any { it.deviceId == "genuine" })88 assertEquals(false, merged.records.any { it.deviceId == "junk-0" })89 assertEquals(true, merged.records.any { it.deviceId == "junk-599" })90 91 92 assertEquals(93 merged.records.count { it.deviceId.startsWith("junk-") },94 merged.admitted.size,95 )96 }9798 @Test99 fun `an all-protected ledger refuses the overflow instead of dropping trust`() {100 val existing = List(500) { index ->101 DeviceRevocation("terminal-$index", index.toString(16).padStart(64, '0'), index.toLong(), "honest-issuer")102 }103 val incoming = listOf(DeviceRevocation("incoming", "ff".repeat(32), 999L, "honest-issuer"))104105 val merged = mergeRevocationRecords(existing, incoming, setOf("honest-issuer"))106107 assertEquals(RevocationMergeOutcome.OVERFLOW_PROTECTED_LEDGER, merged.outcome)108 assertEquals("refusing to evict trusted state keeps the ledger as it was", 500, merged.records.size)109 assertEquals(true, merged.records.any { it.deviceId == "terminal-0" })110 assertEquals(false, merged.records.any { it.deviceId == "incoming" })111 assertEquals(0, merged.admitted.size)112 }113114 @Test115 fun `existing revocation still removes a stale overlapping paired row`() {116 val existing = DeviceRevocation(117 deviceId = browser.deviceId,118 longTermPublicKeyHex = browser.longTermPublicKeyHex!!,119 revokedAt = 1L,120 revokedByDeviceId = "phone-1",121 )122123 val mutation = planDeviceRevocation(124 devices = listOf(browser),125 revocations = listOf(existing),126 deviceId = browser.deviceId,127 revokedByDeviceId = "phone-1",128 nowMs = 99L,129 )130131 assertEquals(emptyList<PairedDevice>(), mutation.devices)132 assertEquals(listOf(existing), mutation.revocations)133 assertNull(mutation.added)134 }135136 @Test137 fun `fresh re-pair rotates a stable profile id already in the revocation ledger`() {138 val revoked = DeviceRevocation(139 deviceId = browser.deviceId,140 longTermPublicKeyHex = browser.longTermPublicKeyHex!!,141 revokedAt = 1L,142 revokedByDeviceId = "chrome-1",143 )144145 assertEquals(true, hasAdoptionIdentityConflict(browser, emptyList(), listOf(revoked)))146 }147148 @Test149 fun `fresh re-pair rotates a stable profile key revoked under an older id`() {150 val revoked = DeviceRevocation(151 deviceId = "old-firefox-id",152 longTermPublicKeyHex = browser.longTermPublicKeyHex!!,153 revokedAt = 1L,154 revokedByDeviceId = "chrome-1",155 )156157 assertEquals(true, hasAdoptionIdentityConflict(browser, emptyList(), listOf(revoked)))158 }159}