1package com.peachpasswords.vault23import kotlinx.coroutines.runBlocking4import kotlinx.serialization.json.Json5import org.junit.Assert.assertEquals6import org.junit.Assert.assertFalse7import org.junit.Assert.assertTrue8import org.junit.Test91011private class CardFakeStorage : VaultStorage {12 private var slotA: StoredVault? = null13 private var slotB: String? = null14 private var ver: Long = 015 private var settings = VaultSettings()16 override fun loadSlotA() = slotA17 override fun hasVault() = slotA != null18 override fun writeSlotA(slot: StoredVault): Boolean { slotA = slot; return true }19 override fun writeSlotB(blobBase64: String): Boolean { slotB = blobBase64; return true }20 private var decoy: StoredVault? = null21 override fun loadDecoySlot() = decoy22 override fun writeDecoySlot(slot: StoredVault): Boolean { decoy = slot; return true }23 override fun hasDecoySlot() = decoy != null24 override fun clearDecoySlot() { decoy = null }25 override fun version() = ver26 override fun stampLocalMutation(): Long { ver = maxOf(System.currentTimeMillis(), ver + 1); return ver }27 override fun loadSettings() = settings28 override fun writeSettings(s: VaultSettings): Boolean { settings = s; return true }29 override fun clearVault(): Boolean { slotA = null; slotB = null; ver = 0; settings = VaultSettings(); return true }30}3132class CardSerializationTest {33 private val json = Json { encodeDefaults = true }3435 @Test fun card_network_serializes_as_browser_key() {36 val card = VaultEntry(37 id = "c1", type = "card", title = "Visa", cardNumber = "4242424242424242",38 cardNetwork = "visa", last4 = "4242", createdAt = 0, modifiedAt = 0,39 )40 val encoded = json.encodeToString(VaultEntry.serializer(), card)41 assertTrue("serializes cardNetwork as \"network\"", encoded.contains("\"network\":\"visa\""))42 assertTrue("serializes last4 explicitly", encoded.contains("\"last4\":\"4242\""))43 assertFalse("never emits the Android-only key name", encoded.contains("cardNetwork"))44 }4546 @Test fun network_and_last4_round_trip_from_browser_keys() {47 val wire = """{"id":"c1","title":"Visa","type":"card","network":"visa","last4":"4242","createdAt":0,"modifiedAt":0}"""48 val decoded = json.decodeFromString(VaultEntry.serializer(), wire)49 assertEquals("visa", decoded.cardNetwork)50 assertEquals("4242", decoded.last4)51 }5253 @Test fun last4_is_derived_on_save() = runBlocking {54 val repo = VaultRepository(CardFakeStorage())55 repo.createVault("correct horse battery staple")56 val saved = repo.addEntry(57 VaultEntry(id = "", type = "card", title = "My Card", cardNumber = "4111 1111 1111 1234", createdAt = 0, modifiedAt = 0),58 )59 assertEquals("derives last4 from the PAN on save", "1234", saved.last4)6061 val updated = repo.updateEntry(saved.copy(cardNumber = "5555 5555 5555 4321"))62 assertEquals("re-derives last4 on update", "4321", updated.last4)63 }6465 @Test fun non_card_entry_has_no_last4() = runBlocking {66 val repo = VaultRepository(CardFakeStorage())67 repo.createVault("correct horse battery staple")68 val saved = repo.addEntry(VaultEntry(id = "", type = "login", title = "GitHub", password = "pw", createdAt = 0, modifiedAt = 0))69 assertEquals(null, saved.last4)70 }71}