package com.peachpasswords.vault import com.peachpasswords.ui.vault.CsvImport import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test class BitwardenPasskeyImportTest { private val bitwardenWithPasskey = """ { "encrypted": false, "items": [ { "id": "abc-123", "type": 1, "name": "example.com", "login": { "username": "june@example.com", "password": "hunter2", "totp": null, "uris": [{"match": null, "uri": "https://example.com/login"}], "fido2Credentials": [ { "credentialId": "Y3JlZC1pZA", "keyType": "public-key", "keyAlgorithm": "ECDSA", "keyCurve": "P-256", "keyValue": "cHJpdmF0ZS1rZXktc2NhbGFy", "rpId": "example.com", "rpName": "Example Inc", "userHandle": "dXNlci1oYW5kbGU", "userName": "june@example.com", "userDisplayName": "June", "counter": 7, "discoverable": true, "creationDate": "2025-06-01T00:00:00Z" } ] }, "notes": null } ] } """.trimIndent() private val bitwardenNoPasskey = """ { "encrypted": false, "items": [ { "id": "def-456", "type": 1, "name": "plain.com", "login": { "username": "user", "password": "pass", "uris": [{"match": null, "uri": "https://plain.com"}] } } ] } """.trimIndent() @Test fun detects_bitwarden_json() { assertEquals(CsvImport.BITWARDEN_JSON, CsvImport.detectProvider(bitwardenWithPasskey)) } @Test fun parses_login_with_passkey() { val entries = CsvImport.parse(bitwardenWithPasskey, CsvImport.BITWARDEN_JSON) assertEquals(1, entries.size) val entry = entries[0] assertEquals("example.com", entry.title) assertEquals("june@example.com", entry.username) assertEquals("hunter2", entry.password) assertEquals(1, entry.passkeys.size) val pk = entry.passkeys[0] assertEquals("Y3JlZC1pZA", pk.credentialId) assertEquals("example.com", pk.rpId) assertEquals("Example Inc", pk.rpName) assertEquals(-7, pk.algorithm) assertEquals("dXNlci1oYW5kbGU", pk.userHandle) assertEquals("june@example.com", pk.userName) assertEquals("June", pk.userDisplayName) assertEquals(7, pk.signCount) assertTrue(pk.privateKeyJwk.contains("\"kty\":\"EC\"")) assertTrue(pk.privateKeyJwk.contains("\"d\":\"cHJpdmF0ZS1rZXktc2NhbGFy\"")) } @Test fun login_without_passkey_has_empty_passkeys() { val entries = CsvImport.parse(bitwardenNoPasskey, CsvImport.BITWARDEN_JSON) assertEquals(1, entries.size) assertTrue(entries[0].passkeys.isEmpty()) } @Test fun ed25519_passkey_maps_to_algorithm_minus_8() { val json = """ { "items": [ { "type": 1, "name": "ed.example.com", "login": { "username": "u", "fido2Credentials": [ { "credentialId": "Y3JlZA", "keyAlgorithm": "EdDSA", "keyCurve": "Ed25519", "keyValue": "c2VlZA", "rpId": "ed.example.com", "counter": 0 } ] } } ] } """.trimIndent() val entries = CsvImport.parse(json, CsvImport.BITWARDEN_JSON) val pk = entries[0].passkeys[0] assertEquals(-8, pk.algorithm) assertTrue(pk.privateKeyJwk.contains("\"kty\":\"OKP\"")) } @Test fun unsupported_algorithm_is_skipped() { val json = """ { "items": [ { "type": 1, "name": "rsa.example.com", "login": { "username": "u", "fido2Credentials": [ { "credentialId": "Y3JlZA", "keyAlgorithm": "RSA", "keyCurve": "", "keyValue": "a2V5", "rpId": "rsa.example.com", "counter": 0 } ] } } ] } """.trimIndent() val entries = CsvImport.parse(json, CsvImport.BITWARDEN_JSON) assertTrue(entries[0].passkeys.isEmpty()) } }