EntryDetailScreenTest.kt

4.2 KB · raw

1package com.peachpasswords.ui.vault23import com.peachpasswords.ui.PeachNav4import com.peachpasswords.vault.TOTPGenerator5import com.peachpasswords.vault.VaultEntry6import com.peachpasswords.vault.isBreach7import org.junit.Assert.assertEquals8import org.junit.Assert.assertFalse9import org.junit.Assert.assertTrue10import org.junit.Test1112/**13 * EntryDetail suite (brief Suite 4): the screen's pure rendering logic — full-title14 * ghost watermark, NOTE routing, breach-banner predicate, TOTP timing/hot-state,15 * and masked card display. The biometric-reveal and FLAG_SECURE paths need an16 * instrumented device.17 */18class EntryDetailScreenTest {1920    // ── Ghost watermark + routing ─────────────────────────────────────────────2122    @Test fun ghost_watermark_uses_full_title() {23        assertEquals("My Bank", ghostWatermarkText("My Bank"))24    }2526    @Test fun note_entry_routes_to_notes_mode() {27        val note = VaultEntry(id = "n1", title = "Diary", type = "note", createdAt = 0, modifiedAt = 0)28        assertEquals(PeachNav.NotesMode("n1"), entryTapDestination(note))29    }3031    @Test fun login_entry_routes_to_entry_detail() {32        val login = VaultEntry(id = "l1", title = "GitHub", type = "login", createdAt = 0, modifiedAt = 0)33        assertEquals(PeachNav.EntryDetail("l1"), entryTapDestination(login))34    }3536    // ── Breach banner ─────────────────────────────────────────────────────────3738    @Test fun breach_banner_shown_when_breach_positive() {39        assertTrue(VaultEntry(id = "a", title = "x", breach = 2, createdAt = 0, modifiedAt = 0).isBreach())40    }4142    @Test fun breach_banner_hidden_when_zero_or_null() {43        assertFalse(VaultEntry(id = "a", title = "x", breach = 0, createdAt = 0, modifiedAt = 0).isBreach())44        assertFalse(VaultEntry(id = "a", title = "x", breach = null, createdAt = 0, modifiedAt = 0).isBreach())45    }4647    // ── TOTP display + hot state ──────────────────────────────────────────────4849    private val secret = "JBSWY3DPEHPK3PXP"5051    /** The screen highlights the TOTP code in its final 5 seconds. */52    private fun isHot(nowMillis: Long) = TOTPGenerator.remainingSeconds(nowMillis) <= 55354    @Test fun totp_code_is_six_digits() {55        val code = TOTPGenerator.generateCode(secret, nowMillis = 0L)!!56        assertEquals(6, code.length)57        assertTrue(code.all { it.isDigit() })58    }5960    @Test fun totp_code_updates_across_periods() {61        val a = TOTPGenerator.generateCode(secret, nowMillis = 0L)62        val b = TOTPGenerator.generateCode(secret, nowMillis = 30_000L) // next 30s window63        assertFalse("code rolls over at the period boundary", a == b)64    }6566    @Test fun totp_hot_state_only_in_final_five_seconds() {67        assertFalse(isHot(10_000L)) // 20s remaining68        assertTrue(isHot(27_000L)) // 3s remaining69        assertTrue(isHot(29_000L)) // 1s remaining70    }7172    @Test fun totp_remaining_and_progress_are_consistent() {73        val now = 27_000L74        assertEquals(3, TOTPGenerator.remainingSeconds(now))75        assertEquals(27f / 30f, TOTPGenerator.periodProgress(now), 0.0001f)76    }7778    // ── Masked card display ───────────────────────────────────────────────────7980    @Test fun masked_pan_shows_only_last_four_visa() {81        assertEquals("•••• •••• •••• 4242", CardFieldUtils.maskedPan("4242424242424242"))82    }8384    @Test fun masked_pan_groups_amex_4_6_5() {85        assertEquals("•••• •••••• •0005", CardFieldUtils.maskedPan("378282246310005"))86    }8788    @Test fun masked_pan_empty_for_no_number() {89        assertEquals("", CardFieldUtils.maskedPan(null))90    }9192    @Test fun display_expiry_combines_month_and_year() {93        assertEquals("04/2030", CardFieldUtils.displayExpiry("04", "2030"))94        assertEquals("04/30", CardFieldUtils.displayExpiry("04", "30"))95    }96}