FakeSyncGeneration.kt

3.3 KB · raw

1package com.peachpasswords.sync23/**4 * In-memory [SyncGenerationPort] for the JVM tests. Doubles as the crash seam:5 * [crashAt] makes the matching checkpoint throw, and [archive] / [cleared]6 * record what survived so a test can assert no trust material was lost.7 */8internal class FakeSyncGeneration(9    var pairedDevices: Boolean = false,10    var revocations: Boolean = false,11    var issuerSigningKeys: Boolean = false,12    var pendingVouchedDevices: Boolean = false,13) : SyncGenerationPort {1415    var blob: String? = null16        private set1718    /** Writes that report failure, to model storage that cannot land the record. */19    var failWrites: Boolean = false2021    /** Simulated process death: the checkpoint with this name throws. */22    var crashAt: String? = null2324    var archiveRef: String? = null25        private set26    var archiveCount: Int = 027        private set28    var cleared: Boolean = false29        private set30    var failArchive: Boolean = false31    var failClear: Boolean = false32    var failIdentity: Boolean = false33    var identityMints: Int = 034        private set35    var deviceId: String? = "anchor-device"3637    /** The order the reset actually touched storage, for the ordering assertion. */38    val calls = mutableListOf<String>()3940    val record: SyncGeneration?41        get() = blob?.let {42            runCatching { jsonForTest.decodeFromString<SyncGenerationBlob>(it).active }.getOrNull()43        }4445    override fun readBlob(): String? = blob4647    override fun writeBlob(blob: String): Boolean {48        if (failWrites) return false49        this.blob = blob50        return true51    }5253    override fun hasPairedDevices() = pairedDevices5455    override fun hasRevocations() = revocations5657    override fun hasIssuerSigningKeys() = issuerSigningKeys5859    override fun hasPendingVouchedDevices() = pendingVouchedDevices6061    override fun archiveLegacyState(generation: Int): String? {62        if (failArchive) return null63        archiveCount++64        archiveRef = "peach_sync_archive_v1"65        calls += "archive:$generation"66        return archiveRef67    }6869    override fun clearLegacyTrustState(): Boolean {70        if (failClear) return false71        cleared = true72        pairedDevices = false73        revocations = false74        issuerSigningKeys = false75        pendingVouchedDevices = false76        calls += "clear"77        return true78    }7980    override fun ensureV2Identity(): Boolean {81        if (failIdentity) return false82        identityMints++83        calls += "identity"84        return true85    }8687    override fun localDeviceId(): String? = deviceId8889    /** Runs [confirmSecureReset] so a checkpoint in [crashAt] throws as a crash would. */90    fun reset(nowMs: Long = 1_000L): ResetResult = confirmSecureReset(this, nowMs) { name ->91        if (name == crashAt) throw SimulatedCrash(name)92    }9394    class SimulatedCrash(name: String) : RuntimeException("crash at $name")95}9697private val jsonForTest = kotlinx.serialization.json.Json { ignoreUnknownKeys = true }9899/** The minimum registry the adoption dispatch paths need: nothing paired. */100internal object EmptyRegistry : PairedDeviceRegistry {101    override fun getAll(): List<PairedDevice> = emptyList()102    override fun get(id: String): PairedDevice? = null103    override fun upsert(device: PairedDevice) = Unit104    override fun applyManifestBatch(batch: DeviceManifestRegistryBatch): Boolean = true105}