package com.peachpasswords.vault import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test class VaultHealthAdvancedTest { private val now = 1_000_000_000_000L private val day = 86_400_000L private fun entry( id: String, url: String? = "https://example.com", password: String? = "Str0ng!Passw0rd", // 15 chars, 4 classes -> not weak totp: String? = "otpauth://totp/x?secret=JBSWY3DPEHPK3PXP", breach: Int? = null, passwordChangedAt: Long? = now, createdAt: Long = now, type: String? = "login", deletedAt: Long? = null, ) = VaultEntry( id = id, title = "Entry $id", type = type, url = url, password = password, totp = totp, breach = breach, passwordChangedAt = passwordChangedAt, createdAt = createdAt, modifiedAt = createdAt, deletedAt = deletedAt, ) @Test fun reused_passwords_form_one_group_and_score() { val a = entry("a", url = "https://one.com", password = "Sh4red!Password") val b = entry("b", url = "https://two.com", password = "Sh4red!Password") val report = VaultHealth.buildAdvancedReport(listOf(a, b), now) assertEquals(1, report.reusedGroups) val byId = report.remediation.associateBy { it.id } assertTrue(byId["a"]!!.factors.reused) assertTrue(byId["b"]!!.factors.reused) assertEquals(25, byId["a"]!!.score) assertTrue(byId["a"]!!.reasons.contains("Reused")) } @Test fun weak_short_password_flagged() { val report = VaultHealth.buildAdvancedReport(listOf(entry("w", password = "short")), now) val r = report.remediation.single() assertTrue(r.factors.weak) assertTrue(r.reasons.contains("Weak")) assertEquals(20, r.score) } @Test fun non_sensitive_ages_at_180_days() { val fresh = entry("fresh", password = "Fresh!Passw0rd1", passwordChangedAt = now - 100 * day) val stale = entry("stale", password = "Stale!Passw0rd2", passwordChangedAt = now - 200 * day) val report = VaultHealth.buildAdvancedReport(listOf(fresh, stale), now) assertEquals(1, report.agedCount) val staleReport = report.remediation.first { it.id == "stale" } assertTrue(staleReport.factors.aged) assertEquals(200L, staleReport.ageDays) assertTrue(staleReport.reasons.contains("200 days old")) assertEquals(15, staleReport.score) } @Test fun sensitive_domain_ages_at_90_days() { // 100 days old: aged on a bank (90d threshold). val bank = entry("bank", url = "https://mybank.com", passwordChangedAt = now - 100 * day) val r = VaultHealth.buildAdvancedReport(listOf(bank), now).remediation.single() assertTrue(r.factors.aged) assertFalse(r.factors.missingTotpOnSensitive) // totp present assertEquals(15, r.score) } @Test fun sensitive_without_totp_adds_reason() { val bank = entry("bank", url = "https://mybank.com", totp = null) val r = VaultHealth.buildAdvancedReport(listOf(bank), now).remediation.single() assertTrue(r.factors.missingTotpOnSensitive) assertTrue(r.reasons.contains("No 2FA on sensitive site")) assertEquals(10, r.score) } @Test fun breached_carries_exposure_count() { val r = VaultHealth.buildAdvancedReport(listOf(entry("x", breach = 3)), now).remediation.single() assertTrue(r.factors.breached) assertEquals(3, r.factors.breachCount) assertTrue(r.reasons.contains("Breached (3 exposures)")) assertEquals(60, r.score) } @Test fun score_caps_at_100() { // breached(60) + reused(25) + weak(20) + aged(15) = 120 -> capped at 100. val a = entry("a", password = "weak", breach = 1, passwordChangedAt = now - 400 * day) val b = entry("b", password = "weak") // makes "weak" a reused group val report = VaultHealth.buildAdvancedReport(listOf(a, b), now) assertEquals(100, report.remediation.first { it.id == "a" }.score) } @Test fun remediation_sorted_desc_and_excludes_zero() { val breached = entry("hi", password = "Hi!Passw0rd\$trong1", breach = 5) val weak = entry("lo", password = "weak") val healthy = entry("ok", password = "Ok!Passw0rd\$trong2") // score 0 val report = VaultHealth.buildAdvancedReport(listOf(weak, breached, healthy), now) assertEquals(listOf("hi", "lo"), report.remediation.map { it.id }) assertTrue(report.remediation.none { it.id == "ok" }) } @Test fun deleted_and_non_login_excluded_from_count() { val entries = listOf( entry("live"), entry("gone", deletedAt = now), entry("card", type = "card"), ) assertEquals(1, VaultHealth.buildAdvancedReport(entries, now).entryCount) } @Test fun vault_score_weights_sensitive_double() { val bank = entry("bank", url = "https://mybank.com", totp = null) // sensitive, score 10, weight 2 val weak = entry("weak", password = "weak") // score 20, weight 1 val report = VaultHealth.buildAdvancedReport(listOf(bank, weak), now) assertEquals(13, report.vaultScore) // (10*2 + 20*1) / 3 = 13.33 -> 13 } @Test fun empty_vault_yields_zero() { val report = VaultHealth.buildAdvancedReport(emptyList(), now) assertEquals(0, report.vaultScore) assertEquals(0, report.entryCount) assertTrue(report.remediation.isEmpty()) } }