package com.peachpasswords.ui.security import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test /** * Behavioural contract for the shared strength scorer. These assertions pin the * two gates the app relies on: CreateVault blocks `< 2`, ExportScreen blocks * `< 3`. They are intentionally inequality-based where the exact bucket is not * load-bearing, and exact where a boundary matters. */ class PasswordStrengthTest { @Test fun empty_scores_zero() { assertEquals(0, scorePassword("")) } @Test fun single_repeated_char_scores_zero() { assertEquals(0, scorePassword("aaaaaaaaaaaa")) } @Test fun common_passwords_score_zero_case_insensitive() { assertEquals(0, scorePassword("password")) assertEquals(0, scorePassword("Password")) assertEquals(0, scorePassword("PASSWORD")) assertEquals(0, scorePassword("123456")) assertEquals(0, scorePassword("qwerty")) assertEquals(0, scorePassword("peach")) } @Test fun short_single_class_is_weak() { // 8 lowercase letters: real but low-entropy → "weak" (1), below both gates. assertTrue(scorePassword("abcdefgh") < 2) } @Test fun straight_sequence_is_penalized() { // Pure ascending run collapses toward zero entropy. assertTrue(scorePassword("abcdefghij") < 2) assertTrue(scorePassword("0123456789") < 2) } @Test fun mixed_8char_three_class_passes_create_gate() { // "short1A!" — lower+upper+digit+symbol, length 8 → "fair" (2). assertTrue(scorePassword("short1A!") >= 2) } @Test fun mixed_12char_four_class_is_strong_and_passes_export_gate() { assertTrue(scorePassword("xK9#mQ2vL8wR") >= 3) assertTrue(scorePassword("Password123!") >= 3) } @Test fun long_passphrase_is_strong() { assertEquals(4, scorePassword("correcthorsebatterystaple")) } @Test fun score_is_always_in_range() { val samples = listOf( "", "a", "ab", "abc123", "Tr0ub4dor&3", " ", "P@ssw0rd!2024xyz", "hunter2", "my secret diary 2024", ) for (pw in samples) { val s = scorePassword(pw) assertTrue("score for '$pw' = $s out of range", s in 0..4) } } @Test fun labels_cover_full_range_and_clamp() { assertEquals("very weak", strengthLabel(0)) assertEquals("weak", strengthLabel(1)) assertEquals("fair", strengthLabel(2)) assertEquals("good", strengthLabel(3)) assertEquals("strong", strengthLabel(4)) // Out-of-range inputs clamp rather than crash. assertEquals("very weak", strengthLabel(-1)) assertEquals("strong", strengthLabel(99)) } @Test fun more_length_never_lowers_a_clean_password() { // Monotonic-ish sanity: extending a varied password should not drop it // below the create gate. val base = "Xk9#mQ2v" assertTrue(scorePassword(base + "L8wR") >= scorePassword(base)) } }