AutofillPreviewLabelsTest.kt

9.0 KB · raw

1package com.peachpasswords.autofill23import com.peachpasswords.autofill.AutofillResponseBuilder.FillMode4import com.peachpasswords.vault.VaultEntry5import org.junit.Assert.assertEquals6import org.junit.Assert.assertNotNull7import org.junit.Assert.assertNull8import org.junit.Test910class AutofillPreviewLabelsTest {11    private fun entry(12        title: String = "GitHub",13        username: String? = "june",14        email: String? = null,15        url: String? = "https://github.com",16    ) = VaultEntry(17        id = title, title = title, username = username, email = email, url = url,18        createdAt = 0, modifiedAt = 0,19    )2021    @Test fun normal_mode_shows_entry_title_and_identity() {22        val labels = AutofillResponseBuilder.previewLabels(false, entry(), "github.com")23        assertEquals("GitHub", labels.title)24        assertEquals("june", labels.subtitle)25        assertEquals(FillMode.LOGIN, labels.fillMode)26    }2728    @Test fun normal_mode_falls_back_email_then_domain() {29        val emailOnly = entry(username = null, email = "[email protected]")30        assertEquals("[email protected]", AutofillResponseBuilder.previewLabels(false, emailOnly, null).subtitle)31        val bare = entry(username = null, email = null)32        assertEquals("github.com", AutofillResponseBuilder.previewLabels(false, bare, null).subtitle)33    }3435    @Test fun private_mode_hides_title_and_identity() {36        val labels = AutofillResponseBuilder.previewLabels(true, entry(), "github.com")37        assertEquals("Peach", labels.title)38        assertEquals("github.com", labels.subtitle)39    }4041    @Test fun private_mode_without_origin_has_blank_subtitle() {42        val labels = AutofillResponseBuilder.previewLabels(true, entry(), null)43        assertEquals("Peach", labels.title)44        assertEquals("", labels.subtitle)45    }4647    // --- OTP_FILL fillMode: the chip surfaces the actual TOTP code ---4849    @Test fun otpFill_mode_shows_chunked_code_as_title_and_entry_as_subtitle() {50        val labels = AutofillResponseBuilder.previewLabels(51            privatePreview = false,52            entry = entry(),53            originDomain = "github.com",54            fillMode = FillMode.OTP_FILL,55            otpCode = "482901",56        )57        assertEquals("482 901", labels.title)58        assertEquals("2FA", labels.subtitle)59        assertEquals("GitHub", labels.iconSource)60        assertEquals(FillMode.OTP_FILL, labels.fillMode)61    }6263    @Test fun otpFill_mode_passes_through_long_codes_unchunked() {64        val labels = AutofillResponseBuilder.previewLabels(65            privatePreview = false,66            entry = entry(),67            originDomain = null,68            fillMode = FillMode.OTP_FILL,69            otpCode = "JBSWY3DPEHPK3PXP",70        )71        // 8-char Steam-style code — chunk only fires on <=6 digits, this passes through.72        assertEquals("JBSWY3DPEHPK3PXP", labels.title)73        assertEquals("2FA", labels.subtitle)74    }7576    @Test fun otpFill_mode_never_leaks_digits_in_private_preview() {77        val labels = AutofillResponseBuilder.previewLabels(78            privatePreview = true,79            entry = entry(),80            originDomain = "github.com",81            fillMode = FillMode.OTP_FILL,82            otpCode = "482901",83        )84        // Privacy always wins over fillMode — chip says "Peach" not the digits.85        assertEquals("Peach", labels.title)86        assertEquals("github.com", labels.subtitle)87    }8889    @Test(expected = IllegalArgumentException::class)90    fun otpFill_mode_requires_otpCode() {91        AutofillResponseBuilder.previewLabels(92            privatePreview = false,93            entry = entry(),94            originDomain = "github.com",95            fillMode = FillMode.OTP_FILL,96            otpCode = null,97        )98    }99100    @Test fun otpFill_mode_subtitle_appends_countdown_when_secondsRemaining_is_set() {101        val labels = AutofillResponseBuilder.previewLabels(102            privatePreview = false,103            entry = entry(),104            originDomain = "github.com",105            fillMode = FillMode.OTP_FILL,106            otpCode = "482901",107            secondsRemaining = 23,108        )109        assertEquals("2FA · 23s", labels.subtitle)110        assertEquals("482 901", labels.title)111    }112113    @Test fun otpFill_mode_subtitle_says_expired_when_secondsRemaining_is_zero() {114        val labels = AutofillResponseBuilder.previewLabels(115            privatePreview = false,116            entry = entry(),117            originDomain = "github.com",118            fillMode = FillMode.OTP_FILL,119            otpCode = "482901",120            secondsRemaining = 0,121        )122        assertEquals("2FA · expired", labels.subtitle)123    }124125    @Test fun otpFill_mode_subtitle_says_expired_when_secondsRemaining_is_negative() {126        // A new FillRequest issued right at the rollover can briefly carry a127        // negative remaining value. The chip should still render "expired"128        // -- the code is stale either way.129        val labels = AutofillResponseBuilder.previewLabels(130            privatePreview = false,131            entry = entry(),132            originDomain = null,133            fillMode = FillMode.OTP_FILL,134            otpCode = "482901",135            secondsRemaining = -1,136        )137        assertEquals("2FA · expired", labels.subtitle)138    }139140    @Test fun otpFill_mode_subtitle_is_just_2fa_when_secondsRemaining_is_null() {141        // Back-compat: pre-countdown callers may build OTP_FILL labels without142        // a countdown. Subtitle stays the original "2FA".143        val labels = AutofillResponseBuilder.previewLabels(144            privatePreview = false,145            entry = entry(),146            originDomain = null,147            fillMode = FillMode.OTP_FILL,148            otpCode = "482901",149            secondsRemaining = null,150        )151        assertEquals("2FA", labels.subtitle)152        assertNull("no end icon when secondsRemaining is unknown", labels.endIcon)153    }154155    @Test fun chunkOtpForDisplay_groups_only_6digit_codes_with_space() {156        // 6-digit only: grouped into 3+3.157        assertEquals("482 901", AutofillResponseBuilder.chunkOtpForDisplay("482901"))158        // 5-digit and 7-digit: pass through (no sane chunking).159        assertEquals("12345", AutofillResponseBuilder.chunkOtpForDisplay("12345"))160        assertEquals("1234567", AutofillResponseBuilder.chunkOtpForDisplay("1234567"))161        // Alphanumeric (Steam-style base32): pass through unchanged — never strip letters.162        assertEquals("JBSWY3DPEHPK3PXP", AutofillResponseBuilder.chunkOtpForDisplay("JBSWY3DPEHPK3PXP"))163    }164}165166// --- Countdown subtitle on the 2FA chip ---167168class AutofillPreviewLabelsCountdownTest {169    private fun entry(title: String = "GitHub") = VaultEntry(170        id = title, title = title, username = "june",171        createdAt = 0, modifiedAt = 0,172    )173174    @Test fun otpFill_chip_counts_down_seconds_remaining_in_subtitle() {175        val labels = AutofillResponseBuilder.previewLabels(176            privatePreview = false,177            entry = entry(),178            originDomain = "github.com",179            fillMode = FillMode.OTP_FILL,180            otpCode = "482901",181            secondsRemaining = 23,182        )183        assertEquals("482 901", labels.title)184        assertEquals("2FA · 23s", labels.subtitle)185    }186187    @Test fun otpFill_chip_amber_zone_at_five_to_nine_seconds() {188        val labels = AutofillResponseBuilder.previewLabels(189            false, entry(), null,190            fillMode = FillMode.OTP_FILL, otpCode = "123456", secondsRemaining = 7,191        )192        assertEquals("2FA · 7s", labels.subtitle)193    }194195    @Test fun otpFill_chip_red_zone_at_or_below_four_seconds() {196        val labels = AutofillResponseBuilder.previewLabels(197            false, entry(), null,198            fillMode = FillMode.OTP_FILL, otpCode = "123456", secondsRemaining = 3,199        )200        assertEquals("2FA · 3s", labels.subtitle)201    }202203    @Test fun otpFill_chip_at_zero_or_below_shows_expired() {204        val labels = AutofillResponseBuilder.previewLabels(205            false, entry(), null,206            fillMode = FillMode.OTP_FILL, otpCode = "123456", secondsRemaining = 0,207        )208        assertEquals("2FA · expired", labels.subtitle)209    }210211    @Test fun otpFill_chip_without_secondsRemaining_keeps_legacy_subtitle() {212        // Back-compat: callers that don't pass secondsRemaining get the same213        // label as v2.14.4. No end-icon either — purely visual change gated214        // on the freshness signal.215        val labels = AutofillResponseBuilder.previewLabels(216            false, entry(), "github.com",217            fillMode = FillMode.OTP_FILL, otpCode = "482901",218        )219        assertEquals("2FA", labels.subtitle)220        assertEquals(null, labels.endIcon)221    }222223    @Test fun private_mode_still_hides_digits_even_with_countdown() {224        // Privacy wins: "Peach" not the digits, no countdown leak either.225        val labels = AutofillResponseBuilder.previewLabels(226            true, entry(), "github.com",227            fillMode = FillMode.OTP_FILL, otpCode = "482901", secondsRemaining = 7,228        )229        assertEquals("Peach", labels.title)230        assertEquals("github.com", labels.subtitle)231    }232}