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 keeps terminal records during peer merge capacity`() {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(0, merged.admitted.size)42 assertEquals("terminal-0", merged.records.first().deviceId)43 }4445 @Test46 fun `same issuer local flood uses bounded capacity`() {47 var records = emptyList<DeviceRevocation>()48 repeat(501) { index ->49 records = if (records.size < 500) records + DeviceRevocation("local-$index", index.toString(16).padStart(64, '0'), index.toLong(), "issuer-a")50 else records.drop(1) + DeviceRevocation("local-$index", index.toString(16).padStart(64, '0'), index.toLong(), "issuer-a")51 }52 assertEquals(500, records.size)53 assertEquals("local-1", records.first().deviceId)54 assertEquals("local-500", records.last().deviceId)55 }5657 @Test58 fun `different issuer local capacity preserves genuine issuer record`() {59 val existing = List(500) { index ->60 DeviceRevocation("terminal-$index", index.toString(16).padStart(64, '0'), index.toLong(), if (index == 0) "issuer-a" else "issuer-b")61 }62 val removable = existing.indexOfFirst { it.revokedByDeviceId != "issuer-a" }63 val records = existing.toMutableList().also { it.removeAt(removable); it += DeviceRevocation("local-new", "ff".repeat(32), 501L, "issuer-a") }64 assertEquals(500, records.size)65 assertEquals("terminal-0", records.first().deviceId)66 assertEquals("local-new", records.last().deviceId)67 }6869 @Test70 fun `existing revocation still removes a stale overlapping paired row`() {71 val existing = DeviceRevocation(72 deviceId = browser.deviceId,73 longTermPublicKeyHex = browser.longTermPublicKeyHex!!,74 revokedAt = 1L,75 revokedByDeviceId = "phone-1",76 )7778 val mutation = planDeviceRevocation(79 devices = listOf(browser),80 revocations = listOf(existing),81 deviceId = browser.deviceId,82 revokedByDeviceId = "phone-1",83 nowMs = 99L,84 )8586 assertEquals(emptyList<PairedDevice>(), mutation.devices)87 assertEquals(listOf(existing), mutation.revocations)88 assertNull(mutation.added)89 }90}