Selkie.git | t/ | 42-password-strength.rakutest


use Test;
use lib 'lib';
use Selkie::Widget::PasswordStrength :scoring;

# Scoring-only unit test. The widget itself is exercised visually via
# xt/ snapshots (needs notcurses); here we verify the scoring heuristic
# produces sensible numbers and the level-for mapping behaves.

plan 16;

# Empty → 0.
is score-password(''), 0, 'empty password scores 0';

# Very short single-class → weak band.
cmp-ok score-password('abc'), '<', 20, "'abc' is weak";

# Short single-class → still weak.
cmp-ok score-password('password'), '<', 40, "'password' (lowercase only) is below good";

# Adding a second class.
cmp-ok score-password('Password'),  '>', score-password('password'),
    'mixed-case scores higher than lowercase only';
cmp-ok score-password('Password1'), '>', score-password('Password'),
    'digit bump';
cmp-ok score-password('Password1!'), '>', score-password('Password1'),
    'symbol bump';

# Very long + diverse → high.
my $strong = 'Correct Horse Battery Staple!';
cmp-ok score-password($strong), '>=', 60, "long diverse passphrase is good or better";

# Monotonic in length for same class-set.
cmp-ok score-password('aaaa'), '<', score-password('aaaaaaaa'),
    'longer same-class scores higher';

# Level mapping.
is level-for(0)<label>,   '',            'score 0 → no label';
is level-for(5)<label>,   'weak',        'score 5 → weak';
is level-for(50)<label>,  'good',        'score 50 → good';
is level-for(100)<label>, 'very strong', 'score 100 → very strong';
is level-for(0)<slot>,   'password-empty',       'score 0 uses empty theme slot';
is level-for(5)<slot>,   'password-weak',        'score 5 uses weak theme slot';
is level-for(50)<slot>,  'password-good',        'score 50 uses good theme slot';
is level-for(100)<slot>, 'password-very-strong', 'score 100 uses strongest theme slot';