PasswordCredentialPlannerTest.kt

5.4 KB · raw

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    /** PAI-2026-001: an app's display label is not a credential origin. */53    @Test fun native_caller_does_not_match_by_app_title() {54        val netflix = entry("Netflix", null)55        val hits = PasswordCredentialPlanner.candidates(listOf(netflix), emptySet(), "Netflix")56        assertTrue("a mutable app label must never select a credential", hits.isEmpty())57    }5859    @Test fun native_caller_matches_an_entry_associated_with_its_package() {60        val saved = entry("Saved here", "com.example.app")61        val hits = PasswordCredentialPlanner.candidates(listOf(saved), emptySet(), "com.example.app")62        assertEquals(listOf("Saved here"), hits.map { it.title })63    }6465    @Test fun associated_package_match_is_exact_not_registrable_domain() {66        val mine = entry("Mine", "com.a.example.app")67        val theirs = entry("Theirs", "com.b.example.app")68        val hits = PasswordCredentialPlanner.candidates(listOf(mine, theirs), emptySet(), "com.a.example.app")69        assertEquals(listOf("Mine"), hits.map { it.title })70    }7172    @Test fun entries_without_password_are_never_offered() {73        val notes = entry("Notes", "https://example.com", password = null)74        val blank = entry("Blank", "https://example.com", password = "  ")75        val hits = PasswordCredentialPlanner.candidates(listOf(notes, blank), setOf("example.com"), null)76        assertTrue(hits.isEmpty())77    }7879    @Test fun deleted_entries_are_never_offered() {80        val gone = entry("Gone", "https://example.com").copy(deletedAt = 1L)81        val hits = PasswordCredentialPlanner.candidates(listOf(gone), setOf("example.com"), null)82        assertTrue(hits.isEmpty())83    }8485    @Test fun entries_without_any_username_are_skipped() {86        // Credential Manager requires an id; an entry with neither username nor87        // email cannot produce a PasswordCredential.88        val nameless = entry("Nameless", "https://example.com", username = null)89        val hits = PasswordCredentialPlanner.candidates(listOf(nameless), setOf("example.com"), null)90        assertTrue(hits.isEmpty())91    }9293    @Test fun email_is_used_when_username_is_missing() {94        val emailOnly = entry("Mail", "https://example.com", username = null)95            .copy(email = "[email protected]")96        val hits = PasswordCredentialPlanner.candidates(listOf(emailOnly), setOf("example.com"), null)97        assertEquals(listOf("[email protected]"), hits.map { it.username })98    }99100    @Test fun allowed_user_ids_filter_restricts_rows() {101        val a = entry("A", "https://example.com", username = "alice")102        val b = entry("B", "https://example.com", username = "bob")103        val hits = PasswordCredentialPlanner.candidates(104            listOf(a, b), setOf("example.com"), null, allowedUserIds = setOf("bob"),105        )106        assertEquals(listOf("bob"), hits.map { it.username })107    }108109    @Test fun most_recently_used_ranks_first() {110        val old = entry("Old", "https://example.com", username = "old", lastUsedAt = 100L)111        val fresh = entry("Fresh", "https://example.com", username = "fresh", lastUsedAt = 200L)112        val hits = PasswordCredentialPlanner.candidates(listOf(old, fresh), setOf("example.com"), null)113        assertEquals(listOf("fresh", "old"), hits.map { it.username })114    }115116    @Test fun duplicate_entries_yield_one_row() {117        val peach = entry("Peach", "https://peachpasswords.com")118        val hits = PasswordCredentialPlanner.candidates(119            listOf(peach, peach), setOf("peachpasswords.com"),120        )121        assertEquals(1, hits.size)122    }123}