1package com.peachpasswords.sync23import kotlinx.coroutines.runBlocking4import org.junit.Assert.assertEquals5import org.junit.Assert.assertFalse6import org.junit.Assert.assertTrue7import org.junit.Test8910111213141516171819202122class AdoptionAdapterCoverageTest {2324 private val request = AdoptionFrame.Request(25 sessionRequestId = "session-request",26 deviceName = "Phone",27 deviceType = "android",28 )2930 private val requestV2 = AdoptionFrame.RequestV2(31 adoptionVersion = ADOPTION_VERSION,32 deviceId = "receiver-id",33 deviceName = "Phone",34 deviceType = "android",35 longTermPublicKeyHex = "aa".repeat(32),36 signingPublicKeyHex = "bb".repeat(32),37 )3839 private val device = PairedDevice(40 deviceId = "receiver-id",41 deviceName = "Phone",42 deviceType = "android",43 longTermPublicKeyHex = "aa".repeat(32),44 pairedAt = 1L,45 )4647 private val presented = LocalDeviceIdentity(48 deviceId = "presented-id",49 deviceName = "This browser",50 deviceType = DeviceType.ANDROID,51 longTermPublicKeyHex = "cc".repeat(32),52 signingPublicKeyHex = "dd".repeat(32),53 )5455 56 private class RecordingSourceDeps : AdoptionSourceV2Deps {57 val calls = mutableSetOf<String>()5859 override val sourceIdentity = LocalDeviceIdentity(60 deviceId = "probe-id",61 deviceName = "Probe",62 deviceType = DeviceType.ANDROID,63 longTermPublicKeyHex = "ee".repeat(32),64 signingPublicKeyHex = "ff".repeat(32),65 )6667 override val pairState = AdoptionPairState("01".repeat(32), "wss://probe.test")6869 override suspend fun rootKeyHex() = "11".repeat(32).also { calls += "rootKeyHex" }70 override suspend fun vaultId() = "22".repeat(32).also { calls += "vaultId" }71 override suspend fun license() = "PPK-probe".also { calls += "license" }72 override suspend fun applyLicense(license: String) {73 calls += "applyLicense"74 }7576 override suspend fun onConsent(request: AdoptionFrame.Request) =77 true.also { calls += "onConsentV1" }7879 override suspend fun onConsent(request: AdoptionFrame.RequestV2) =80 true.also { calls += "onConsentV2" }8182 override suspend fun persistPendingHolder(request: AdoptionFrame.Request, nowMs: Long) =83 true.also { calls += "persistPendingHolder" }8485 override suspend fun completePendingHolder(request: AdoptionFrame.Request, device: PairedDevice) =86 true.also { calls += "completePendingHolder" }8788 override suspend fun persistPendingIdentity(device: PairedDevice, nowMs: Long) =89 true.also { calls += "persistPendingIdentity" }9091 override suspend fun completePendingIdentity(device: PairedDevice) =92 true.also { calls += "completePendingIdentity" }9394 override suspend fun upsertPairedDevice(device: PairedDevice) {95 calls += "upsertPairedDevice"96 }9798 override fun hasDeviceIdentityCollision(device: PairedDevice) =99 false.also { calls += "hasDeviceIdentityCollision" }100101 override suspend fun rotateSourceIdentity() =102 sourceIdentity.also { calls += "rotateSourceIdentity" }103104 override fun snapshotCapable() = false.also { calls += "snapshotCapable" }105106 override suspend fun vaultSnapshot() = null.also { calls += "vaultSnapshot" }107 override suspend fun outgoingDeviceManifest() = null.also { calls += "outgoingDeviceManifest" }108109 override suspend fun markSynced(deviceId: String) {110 calls += "markSynced"111 }112113 override suspend fun duressSlotJson() = null.also { calls += "duressSlotJson" }114 }115116 @Test117 fun `the session binder forwards every member, and covers the whole interface`() = runBlocking {118 val probe = RecordingSourceDeps()119 val sessionPairState = AdoptionPairState("33".repeat(32), "wss://session.test")120 var confirmedSas: String? = null121 var confirmedRequest: AdoptionFrame.Request? = null122123 val bound = bindSourceSessionDeps(124 sourceDeps = probe,125 sourceIdentity = presented,126 pairState = sessionPairState,127 sas = "12345678",128 snapshotCapable = true,129 confirm = { sas, request ->130 confirmedSas = sas131 confirmedRequest = request132 true133 },134 )135136 137 assertEquals("the presented identity is the session's", presented, bound.sourceIdentity)138 assertEquals(sessionPairState, bound.pairState)139 assertTrue("snapshot capability is a session fact", bound.snapshotCapable())140141 bound.rootKeyHex()142 bound.vaultId()143 bound.license()144 bound.applyLicense("PPK-x")145 bound.onConsent(request)146 bound.persistPendingHolder(request, 1L)147 bound.completePendingHolder(request, device)148 bound.persistPendingIdentity(device, 1L)149 bound.completePendingIdentity(device)150 bound.upsertPairedDevice(device)151 bound.hasDeviceIdentityCollision(device)152 bound.rotateSourceIdentity()153 bound.vaultSnapshot()154 bound.outgoingDeviceManifest()155 bound.markSynced(device.deviceId)156 bound.duressSlotJson()157 assertEquals(true, bound.onConsent(requestV2))158159 160 161 assertEquals("the v2 consent must carry the session SAS", "12345678", confirmedSas)162 assertEquals(163 "the v2 consent must hand the human the requesting device's label",164 "receiver-id",165 confirmedRequest?.sessionRequestId,166 )167 168 169 assertFalse("the v1 consent is answered locally", probe.calls.contains("onConsentV1"))170171 172 173 val declared = AdoptionSourceV2Deps::class.java.methods174 .map { it.name }175 .filterNot { it in OBJECT_METHODS }176 .toSet()177 val covered = probe.calls178 .filterNot { it == "onConsentV1" || it == "onConsentV2" }179 .toSet() +180 setOf("onConsent", "getSourceIdentity", "getPairState", "snapshotCapable")181 assertEquals(182 "every AdoptionSourceV2Deps member must be covered by this test",183 declared,184 covered,185 )186 }187188 private companion object {189 val OBJECT_METHODS = setOf("equals", "hashCode", "toString")190 }191}