package com.peachpasswords.autofill import com.peachpasswords.vault.VaultEntry import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test class AutofillSaveTest { private fun entry( title: String, url: String, username: String? = "june", password: String = "old", ) = VaultEntry( id = title, type = "login", title = title, username = username, password = password, url = url, createdAt = 1, modifiedAt = 1, ) @Test fun autofillSaveDraft_requiresOriginAndPassword() { assertNull(autofillSaveDraft(ParsedStructure(webDomain = "example.com"))) assertNull(autofillSaveDraft(ParsedStructure(passwordValue = "pw"))) assertNull( autofillSaveDraft( ParsedStructure( webDomain = "example.com", passwordValue = "pw", credentialReleaseAllowed = false, ), ), ) } @Test fun planAutofillSave_addsNewWebLogin() { val draft = autofillSaveDraft( ParsedStructure( emailValue = "june@example.com", passwordValue = "new-secret", webDomain = "login.example.com", ), )!! val plan = planAutofillSave(emptyList(), draft) assertTrue(plan is AutofillSavePlan.Add) val entry = (plan as AutofillSavePlan.Add).entry assertEquals("login.example.com", entry.title) assertEquals("june@example.com", entry.email) assertEquals("new-secret", entry.password) assertEquals("https://login.example.com", entry.url) } @Test fun planAutofillSave_updatesMatchingWebOriginAndIdentity() { val existing = entry("Example", "https://example.com/login", username = "june") val draft = AutofillSaveDraft( origin = "app.example.com", isWebOrigin = true, identity = "june", identityIsEmail = false, password = "new", ) val plan = planAutofillSave(listOf(existing), draft) assertTrue(plan is AutofillSavePlan.Update) val updated = (plan as AutofillSavePlan.Update).entry assertEquals(existing.id, updated.id) assertEquals("new", updated.password) assertEquals("june", updated.username) } @Test fun planAutofillSave_doesNotUpdateSubstringLookalikeOrigin() { val existing = entry("GitHub", "https://github.com/login", username = "june") val draft = AutofillSaveDraft( origin = "notgithub.com", isWebOrigin = true, identity = "june", identityIsEmail = false, password = "phished", ) val plan = planAutofillSave(listOf(existing), draft) assertTrue(plan is AutofillSavePlan.Add) } @Test fun planAutofillSave_updatesNativePackageOnlyOnExactOrigin() { val existing = entry("Native", "com.example.app", username = "june").copy(totp = "JBSWY3DPEHPK3PXP") val draft = AutofillSaveDraft( origin = "com.example.app", isWebOrigin = false, identity = "june", identityIsEmail = false, password = "new-native", ) val plan = planAutofillSave(listOf(existing), draft) assertTrue(plan is AutofillSavePlan.Update) val updated = (plan as AutofillSavePlan.Update).entry assertEquals("new-native", updated.password) assertEquals("a save must never overwrite the stored seed", "JBSWY3DPEHPK3PXP", updated.totp) } @Test fun autofillSaveDraft_ignores_a_live_otp_code_from_the_page() { // The page's OTP box holds whatever was typed into it, usually a live // six-digit code. Treating that as a seed silently destroyed the entry's // second factor (PB-26-135). val draft = autofillSaveDraft( ParsedStructure( webDomain = "example.com", passwordValue = "new", otpValue = "123456", ), ) assertTrue(draft != null) val existing = entry("Example", "https://example.com/login").copy(totp = "JBSWY3DPEHPK3PXP") val plan = planAutofillSave(listOf(existing), draft!!) assertEquals( "the stored seed is untouched by a save", "JBSWY3DPEHPK3PXP", (plan as AutofillSavePlan.Update).entry.totp, ) } }