1package com.peachpasswords.credentials23import com.peachpasswords.vault.VaultEntry4import org.junit.Assert.assertEquals5import org.junit.Assert.assertTrue6import org.junit.Test78class PasswordCredentialPlannerTest {9 private fun entry(10 title: String,11 url: String?,12 username: String? = "[email protected]",13 password: String? = "correct-horse-battery",14 lastUsedAt: Long? = null,15 ) = VaultEntry(16 id = title,17 title = title,18 url = url,19 username = username,20 password = password,21 createdAt = 0,22 modifiedAt = 0,23 lastUsedAt = lastUsedAt,24 )2526 @Test fun web_caller_matches_entry_on_same_registrable_domain() {27 val peach = entry("Peach", "https://peachpasswords.com/login")28 val mail = entry("Fastmail", "https://fastmail.com")29 val hits = PasswordCredentialPlanner.candidates(listOf(mail, peach), setOf("peachpasswords.com"), null)30 assertEquals(listOf("Peach"), hits.map { it.title })31 }3233 @Test fun subdomain_entry_matches_registrable_caller_domain() {34 val peach = entry("Peach", "https://app.peachpasswords.com")35 val hits = PasswordCredentialPlanner.candidates(listOf(peach), setOf("peachpasswords.com"), null)36 assertEquals(1, hits.size)37 }3839 @Test fun no_false_positive_on_substring_lookalike() {40 val gh = entry("GitHub", "https://github.com/login")41 val hits = PasswordCredentialPlanner.candidates(listOf(gh), setOf("notgithub.com"), null)42 assertTrue("github.com must not match notgithub.com", hits.isEmpty())43 }4445 @Test fun additional_urls_match_registrable_domain() {46 val peach = entry("Peach", "https://peachpasswords.com")47 .copy(additionalUrls = listOf("https://accounts.peachpasswords.co.uk/login"))48 val hits = PasswordCredentialPlanner.candidates(listOf(peach), setOf("peachpasswords.co.uk"), null)49 assertEquals(1, hits.size)50 }5152 @Test fun native_caller_matches_by_app_title() {53 val netflix = entry("Netflix", null)54 val hits = PasswordCredentialPlanner.candidates(listOf(netflix), emptySet(), "Netflix")55 assertEquals(listOf("Netflix"), hits.map { it.title })56 }5758 @Test fun entries_without_password_are_never_offered() {59 val notes = entry("Notes", "https://example.com", password = null)60 val blank = entry("Blank", "https://example.com", password = " ")61 val hits = PasswordCredentialPlanner.candidates(listOf(notes, blank), setOf("example.com"), null)62 assertTrue(hits.isEmpty())63 }6465 @Test fun deleted_entries_are_never_offered() {66 val gone = entry("Gone", "https://example.com").copy(deletedAt = 1L)67 val hits = PasswordCredentialPlanner.candidates(listOf(gone), setOf("example.com"), null)68 assertTrue(hits.isEmpty())69 }7071 @Test fun entries_without_any_username_are_skipped() {72 73 74 val nameless = entry("Nameless", "https://example.com", username = null)75 val hits = PasswordCredentialPlanner.candidates(listOf(nameless), setOf("example.com"), null)76 assertTrue(hits.isEmpty())77 }7879 @Test fun email_is_used_when_username_is_missing() {80 val emailOnly = entry("Mail", "https://example.com", username = null)81 .copy(email = "[email protected]")82 val hits = PasswordCredentialPlanner.candidates(listOf(emailOnly), setOf("example.com"), null)83 assertEquals(listOf("[email protected]"), hits.map { it.username })84 }8586 @Test fun allowed_user_ids_filter_restricts_rows() {87 val a = entry("A", "https://example.com", username = "alice")88 val b = entry("B", "https://example.com", username = "bob")89 val hits = PasswordCredentialPlanner.candidates(90 listOf(a, b), setOf("example.com"), null, allowedUserIds = setOf("bob"),91 )92 assertEquals(listOf("bob"), hits.map { it.username })93 }9495 @Test fun most_recently_used_ranks_first() {96 val old = entry("Old", "https://example.com", username = "old", lastUsedAt = 100L)97 val fresh = entry("Fresh", "https://example.com", username = "fresh", lastUsedAt = 200L)98 val hits = PasswordCredentialPlanner.candidates(listOf(old, fresh), setOf("example.com"), null)99 assertEquals(listOf("fresh", "old"), hits.map { it.username })100 }101102 @Test fun domain_and_title_matches_deduplicate_to_one_row() {103 val peach = entry("Peach", "https://peachpasswords.com")104 val hits = PasswordCredentialPlanner.candidates(105 listOf(peach), setOf("peachpasswords.com"), "Peach",106 )107 assertEquals(1, hits.size)108 }109}