package com.peachpasswords.sync /** * In-memory [SyncGenerationPort] for the JVM tests. Doubles as the crash seam: * [crashAt] makes the matching checkpoint throw, and [archive] / [cleared] * record what survived so a test can assert no trust material was lost. */ internal class FakeSyncGeneration( var pairedDevices: Boolean = false, var revocations: Boolean = false, var issuerSigningKeys: Boolean = false, var pendingVouchedDevices: Boolean = false, ) : SyncGenerationPort { var blob: String? = null private set /** Writes that report failure, to model storage that cannot land the record. */ var failWrites: Boolean = false /** Simulated process death: the checkpoint with this name throws. */ var crashAt: String? = null var archiveRef: String? = null private set var archiveCount: Int = 0 private set var cleared: Boolean = false private set var failArchive: Boolean = false var failClear: Boolean = false var failIdentity: Boolean = false var identityMints: Int = 0 private set var deviceId: String? = "anchor-device" /** The order the reset actually touched storage, for the ordering assertion. */ val calls = mutableListOf() val record: SyncGeneration? get() = blob?.let { runCatching { jsonForTest.decodeFromString(it).active }.getOrNull() } override fun readBlob(): String? = blob override fun writeBlob(blob: String): Boolean { if (failWrites) return false this.blob = blob return true } override fun hasPairedDevices() = pairedDevices override fun hasRevocations() = revocations override fun hasIssuerSigningKeys() = issuerSigningKeys override fun hasPendingVouchedDevices() = pendingVouchedDevices override fun archiveLegacyState(generation: Int): String? { if (failArchive) return null archiveCount++ archiveRef = "peach_sync_archive_v1" calls += "archive:$generation" return archiveRef } override fun clearLegacyTrustState(): Boolean { if (failClear) return false cleared = true pairedDevices = false revocations = false issuerSigningKeys = false pendingVouchedDevices = false calls += "clear" return true } override fun ensureV2Identity(): Boolean { if (failIdentity) return false identityMints++ calls += "identity" return true } override fun localDeviceId(): String? = deviceId /** Runs [confirmSecureReset] so a checkpoint in [crashAt] throws as a crash would. */ fun reset(nowMs: Long = 1_000L): ResetResult = confirmSecureReset(this, nowMs) { name -> if (name == crashAt) throw SimulatedCrash(name) } class SimulatedCrash(name: String) : RuntimeException("crash at $name") } private val jsonForTest = kotlinx.serialization.json.Json { ignoreUnknownKeys = true } /** The minimum registry the adoption dispatch paths need: nothing paired. */ internal object EmptyRegistry : PairedDeviceRegistry { override fun getAll(): List = emptyList() override fun get(id: String): PairedDevice? = null override fun upsert(device: PairedDevice) = Unit override fun applyManifestBatch(batch: DeviceManifestRegistryBatch): Boolean = true }