1package com.peachpasswords.vault23import kotlinx.coroutines.runBlocking4import org.junit.Assert.assertEquals5import org.junit.Assert.assertTrue6import org.junit.Test789101112131415class ReplaceEntriesAtomicityTest {1617 private class FaultStorage : VaultStorage {18 var slotA: StoredVault? = null19 var slotB: String? = null20 var failWriteSlotA = false21 private var ver: Long = 022 private var settings = VaultSettings()23 override fun loadSlotA() = slotA24 override fun hasVault() = slotA != null25 override fun writeSlotA(slot: StoredVault): Boolean {26 if (failWriteSlotA) return false27 slotA = slot28 return true29 }30 override fun loadSlotB() = slotB31 override fun writeSlotB(blobBase64: String): Boolean { slotB = blobBase64; return true }32 var decoy: StoredVault? = null33 override fun loadDecoySlot() = decoy34 override fun writeDecoySlot(slot: StoredVault): Boolean { decoy = slot; return true }35 override fun hasDecoySlot() = decoy != null36 override fun clearDecoySlot() { decoy = null }37 override fun version() = ver38 override fun stampLocalMutation(): Long { ver = maxOf(System.currentTimeMillis(), ver + 1); return ver }39 override fun loadSettings() = settings40 override fun writeSettings(s: VaultSettings): Boolean { settings = s; return true }41 override fun clearVault(): Boolean { slotA = null; slotB = null; ver = 0; settings = VaultSettings(); return true }42 }4344 private fun login(title: String) =45 VaultEntry(id = "", title = title, type = "login", password = "pw", createdAt = 0, modifiedAt = 0)4647 @Test fun replace_swaps_active_set_in_one_write() = runBlocking {48 val repo = VaultRepository(FaultStorage())49 repo.createVault("pw")50 repo.addEntry(login("Old A"))51 repo.addEntry(login("Old B"))5253 assertEquals(2, repo.replaceEntries(listOf(login("New A"), login("New B"))))5455 assertEquals(listOf("New A", "New B"), repo.getEntries().map { it.title }.sorted())56 57 58 assertEquals(listOf("Old A", "Old B"), repo.getDeletedEntries().map { it.title }.sorted())59 }6061 @Test fun failed_replace_restores_prior_active_entries() = runBlocking {62 val storage = FaultStorage()63 val repo = VaultRepository(storage)64 repo.createVault("pw")65 repo.addEntry(login("Keep me"))6667 storage.failWriteSlotA = true68 val threw = runCatching { repo.replaceEntries(listOf(login("Imported"))) }.isFailure69 assertTrue(threw)7071 72 assertEquals(listOf("Keep me"), repo.getEntries().map { it.title })73 assertTrue(repo.getDeletedEntries().isEmpty())74 }75}