VaultHealthAdvancedTest.kt

5.5 KB · raw

1package com.peachpasswords.vault23import org.junit.Assert.assertEquals4import org.junit.Assert.assertFalse5import org.junit.Assert.assertTrue6import org.junit.Test78class VaultHealthAdvancedTest {910    private val now = 1_000_000_000_000L11    private val day = 86_400_000L1213    private fun entry(14        id: String,15        url: String? = "https://example.com",16        password: String? = "Str0ng!Passw0rd", // 15 chars, 4 classes -> not weak17        totp: String? = "otpauth://totp/x?secret=JBSWY3DPEHPK3PXP",18        breach: Int? = null,19        passwordChangedAt: Long? = now,20        createdAt: Long = now,21        type: String? = "login",22        deletedAt: Long? = null,23    ) = VaultEntry(24        id = id,25        title = "Entry $id",26        type = type,27        url = url,28        password = password,29        totp = totp,30        breach = breach,31        passwordChangedAt = passwordChangedAt,32        createdAt = createdAt,33        modifiedAt = createdAt,34        deletedAt = deletedAt,35    )3637    @Test fun reused_passwords_form_one_group_and_score() {38        val a = entry("a", url = "https://one.com", password = "Sh4red!Password")39        val b = entry("b", url = "https://two.com", password = "Sh4red!Password")40        val report = VaultHealth.buildAdvancedReport(listOf(a, b), now)41        assertEquals(1, report.reusedGroups)42        val byId = report.remediation.associateBy { it.id }43        assertTrue(byId["a"]!!.factors.reused)44        assertTrue(byId["b"]!!.factors.reused)45        assertEquals(25, byId["a"]!!.score)46        assertTrue(byId["a"]!!.reasons.contains("Reused"))47    }4849    @Test fun weak_short_password_flagged() {50        val report = VaultHealth.buildAdvancedReport(listOf(entry("w", password = "short")), now)51        val r = report.remediation.single()52        assertTrue(r.factors.weak)53        assertTrue(r.reasons.contains("Weak"))54        assertEquals(20, r.score)55    }5657    @Test fun non_sensitive_ages_at_180_days() {58        val fresh = entry("fresh", password = "Fresh!Passw0rd1", passwordChangedAt = now - 100 * day)59        val stale = entry("stale", password = "Stale!Passw0rd2", passwordChangedAt = now - 200 * day)60        val report = VaultHealth.buildAdvancedReport(listOf(fresh, stale), now)61        assertEquals(1, report.agedCount)62        val staleReport = report.remediation.first { it.id == "stale" }63        assertTrue(staleReport.factors.aged)64        assertEquals(200L, staleReport.ageDays)65        assertTrue(staleReport.reasons.contains("200 days old"))66        assertEquals(15, staleReport.score)67    }6869    @Test fun sensitive_domain_ages_at_90_days() {70        // 100 days old: aged on a bank (90d threshold).71        val bank = entry("bank", url = "https://mybank.com", passwordChangedAt = now - 100 * day)72        val r = VaultHealth.buildAdvancedReport(listOf(bank), now).remediation.single()73        assertTrue(r.factors.aged)74        assertFalse(r.factors.missingTotpOnSensitive) // totp present75        assertEquals(15, r.score)76    }7778    @Test fun sensitive_without_totp_adds_reason() {79        val bank = entry("bank", url = "https://mybank.com", totp = null)80        val r = VaultHealth.buildAdvancedReport(listOf(bank), now).remediation.single()81        assertTrue(r.factors.missingTotpOnSensitive)82        assertTrue(r.reasons.contains("No 2FA on sensitive site"))83        assertEquals(10, r.score)84    }8586    @Test fun breached_carries_exposure_count() {87        val r = VaultHealth.buildAdvancedReport(listOf(entry("x", breach = 3)), now).remediation.single()88        assertTrue(r.factors.breached)89        assertEquals(3, r.factors.breachCount)90        assertTrue(r.reasons.contains("Breached (3 exposures)"))91        assertEquals(60, r.score)92    }9394    @Test fun score_caps_at_100() {95        // breached(60) + reused(25) + weak(20) + aged(15) = 120 -> capped at 100.96        val a = entry("a", password = "weak", breach = 1, passwordChangedAt = now - 400 * day)97        val b = entry("b", password = "weak") // makes "weak" a reused group98        val report = VaultHealth.buildAdvancedReport(listOf(a, b), now)99        assertEquals(100, report.remediation.first { it.id == "a" }.score)100    }101102    @Test fun remediation_sorted_desc_and_excludes_zero() {103        val breached = entry("hi", password = "Hi!Passw0rd\$trong1", breach = 5)104        val weak = entry("lo", password = "weak")105        val healthy = entry("ok", password = "Ok!Passw0rd\$trong2") // score 0106        val report = VaultHealth.buildAdvancedReport(listOf(weak, breached, healthy), now)107        assertEquals(listOf("hi", "lo"), report.remediation.map { it.id })108        assertTrue(report.remediation.none { it.id == "ok" })109    }110111    @Test fun deleted_and_non_login_excluded_from_count() {112        val entries = listOf(113            entry("live"),114            entry("gone", deletedAt = now),115            entry("card", type = "card"),116        )117        assertEquals(1, VaultHealth.buildAdvancedReport(entries, now).entryCount)118    }119120    @Test fun vault_score_weights_sensitive_double() {121        val bank = entry("bank", url = "https://mybank.com", totp = null) // sensitive, score 10, weight 2122        val weak = entry("weak", password = "weak") // score 20, weight 1123        val report = VaultHealth.buildAdvancedReport(listOf(bank, weak), now)124        assertEquals(13, report.vaultScore) // (10*2 + 20*1) / 3 = 13.33 -> 13125    }126127    @Test fun empty_vault_yields_zero() {128        val report = VaultHealth.buildAdvancedReport(emptyList(), now)129        assertEquals(0, report.vaultScore)130        assertEquals(0, report.entryCount)131        assertTrue(report.remediation.isEmpty())132    }133}