1package com.peachpasswords.vault23import kotlinx.coroutines.runBlocking4import org.junit.Assert.assertEquals5import org.junit.Assert.assertFalse6import org.junit.Assert.assertTrue7import org.junit.Test8910111213141516class DecoySettingsIsolationTest {1718 private class MemStorage : VaultStorage {19 var slotA: StoredVault? = null20 var slotB: String? = null21 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 { slotA = slot; return true }26 override fun loadSlotB() = slotB27 override fun writeSlotB(blobBase64: String): Boolean { slotB = blobBase64; return true }28 var decoy: StoredVault? = null29 override fun loadDecoySlot() = decoy30 override fun writeDecoySlot(slot: StoredVault): Boolean { decoy = slot; return true }31 override fun hasDecoySlot() = decoy != null32 override fun clearDecoySlot() { decoy = null }33 override fun version() = ver34 override fun stampLocalMutation(): Long { ver = maxOf(System.currentTimeMillis(), ver + 1); return ver }35 override fun loadSettings() = settings36 override fun writeSettings(s: VaultSettings): Boolean { settings = s; return true }37 var history = VaultHistory()38 override fun loadVaultHistory() = history39 override fun writeVaultHistory(h: VaultHistory): Boolean { history = h; return true }40 override fun clearVaultHistory() { history = VaultHistory() }41 override fun clearVault(): Boolean { slotA = null; slotB = null; decoy = null; ver = 042 settings = VaultSettings(); history = VaultHistory(); return true }43 }4445 private val mainPassword = "correct-horse-battery-staple"46 private val duressPassword = "open-the-decoy-vault-456"4748 @Test fun a_decoy_session_neither_reads_nor_writes_the_real_settings() = runBlocking {49 val store = MemStorage()50 val repo = VaultRepository(store)51 repo.createVault(mainPassword)5253 54 repo.updateSettings(repo.settings.value.copy(allowScreenshots = true, sortOrder = "az"))55 assertTrue("fixture: the real record carries the change", store.loadSettings().allowScreenshots)5657 repo.setDuressPassphrase(duressPassword)58 repo.lock()59 val duress = repo.tryUnlockDuress(duressPassword)!!60 assertEquals(UnlockResult.Success, repo.commitDuressUnlock(duress))6162 63 assertFalse("decoy must not surface the real capture setting", repo.settings.value.allowScreenshots)64 assertEquals("url-first", repo.settings.value.sortOrder)6566 67 68 repo.updateSettings(repo.settings.value.copy(breachMonitoring = false))69 assertTrue("decoy write must not persist", store.loadSettings().breachMonitoring)70 assertTrue("the real record is untouched", store.loadSettings().allowScreenshots)71 assertFalse("the decoy's own view still reflects the edit", repo.settings.value.breachMonitoring)72 }73}