package com.peachpasswords.ui.vault import com.peachpasswords.ui.PeachNav import com.peachpasswords.vault.TOTPGenerator import com.peachpasswords.vault.VaultEntry import com.peachpasswords.vault.isBreach import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test /** * EntryDetail suite (brief Suite 4): the screen's pure rendering logic — full-title * ghost watermark, NOTE routing, breach-banner predicate, TOTP timing/hot-state, * and masked card display. The biometric-reveal and FLAG_SECURE paths need an * instrumented device. */ class EntryDetailScreenTest { // ── Ghost watermark + routing ───────────────────────────────────────────── @Test fun ghost_watermark_uses_full_title() { assertEquals("My Bank", ghostWatermarkText("My Bank")) } @Test fun note_entry_routes_to_notes_mode() { val note = VaultEntry(id = "n1", title = "Diary", type = "note", createdAt = 0, modifiedAt = 0) assertEquals(PeachNav.NotesMode("n1"), entryTapDestination(note)) } @Test fun login_entry_routes_to_entry_detail() { val login = VaultEntry(id = "l1", title = "GitHub", type = "login", createdAt = 0, modifiedAt = 0) assertEquals(PeachNav.EntryDetail("l1"), entryTapDestination(login)) } // ── Breach banner ───────────────────────────────────────────────────────── @Test fun breach_banner_shown_when_breach_positive() { assertTrue(VaultEntry(id = "a", title = "x", breach = 2, createdAt = 0, modifiedAt = 0).isBreach()) } @Test fun breach_banner_hidden_when_zero_or_null() { assertFalse(VaultEntry(id = "a", title = "x", breach = 0, createdAt = 0, modifiedAt = 0).isBreach()) assertFalse(VaultEntry(id = "a", title = "x", breach = null, createdAt = 0, modifiedAt = 0).isBreach()) } // ── TOTP display + hot state ────────────────────────────────────────────── private val secret = "JBSWY3DPEHPK3PXP" /** The screen highlights the TOTP code in its final 5 seconds. */ private fun isHot(nowMillis: Long) = TOTPGenerator.remainingSeconds(nowMillis) <= 5 @Test fun totp_code_is_six_digits() { val code = TOTPGenerator.generateCode(secret, nowMillis = 0L)!! assertEquals(6, code.length) assertTrue(code.all { it.isDigit() }) } @Test fun totp_code_updates_across_periods() { val a = TOTPGenerator.generateCode(secret, nowMillis = 0L) val b = TOTPGenerator.generateCode(secret, nowMillis = 30_000L) // next 30s window assertFalse("code rolls over at the period boundary", a == b) } @Test fun totp_hot_state_only_in_final_five_seconds() { assertFalse(isHot(10_000L)) // 20s remaining assertTrue(isHot(27_000L)) // 3s remaining assertTrue(isHot(29_000L)) // 1s remaining } @Test fun totp_remaining_and_progress_are_consistent() { val now = 27_000L assertEquals(3, TOTPGenerator.remainingSeconds(now)) assertEquals(27f / 30f, TOTPGenerator.periodProgress(now), 0.0001f) } // ── Masked card display ─────────────────────────────────────────────────── @Test fun masked_pan_shows_only_last_four_visa() { assertEquals("•••• •••• •••• 4242", CardFieldUtils.maskedPan("4242424242424242")) } @Test fun masked_pan_groups_amex_4_6_5() { assertEquals("•••• •••••• •0005", CardFieldUtils.maskedPan("378282246310005")) } @Test fun masked_pan_empty_for_no_number() { assertEquals("", CardFieldUtils.maskedPan(null)) } @Test fun display_expiry_combines_month_and_year() { assertEquals("04/2030", CardFieldUtils.displayExpiry("04", "2030")) assertEquals("04/30", CardFieldUtils.displayExpiry("04", "30")) } }