File size: 4,656 Bytes
1f5ea39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import { describe, it, expect } from 'vitest';
import { validatePassword } from '../../../src/services/passwordPolicy';

describe('validatePassword', () => {
  // AUTH-006 β€” Registration with weak password
  describe('length requirement', () => {
    it('AUTH-006: rejects passwords shorter than 8 characters', () => {
      expect(validatePassword('Ab1!')).toEqual({ ok: false, reason: expect.stringContaining('8 characters') });
      expect(validatePassword('Ab1!456')).toEqual({ ok: false, reason: expect.stringContaining('8 characters') });
    });

    it('accepts passwords of exactly 8 characters that meet all requirements', () => {
      expect(validatePassword('Ab1!abcd')).toEqual({ ok: true });
    });
  });

  describe('complexity requirements', () => {
    it('AUTH-006: rejects password missing uppercase letter', () => {
      const result = validatePassword('abcd1234!');
      expect(result.ok).toBe(false);
      expect(result.reason).toContain('uppercase');
    });

    it('AUTH-006: rejects password missing lowercase letter', () => {
      const result = validatePassword('ABCD1234!');
      expect(result.ok).toBe(false);
      expect(result.reason).toContain('lowercase');
    });

    it('AUTH-006: rejects password missing a number', () => {
      const result = validatePassword('Abcdefg!');
      expect(result.ok).toBe(false);
      expect(result.reason).toContain('number');
    });

    it('AUTH-006: rejects password missing a special character', () => {
      // 'TrekApp1' β€” has upper, lower, number, NO special char, NOT in blocklist
      const result = validatePassword('TrekApp1');
      expect(result.ok).toBe(false);
      expect(result.reason).toContain('special character');
    });
  });

  // AUTH-007 β€” Registration with common password
  describe('common password blocklist', () => {
    it('AUTH-007: rejects password matching exact blocklist entry (case-insensitive)', () => {
      // 'password1' is in the blocklist. A capitalised+special variant still matches
      // because the check is COMMON_PASSWORDS.has(password.toLowerCase()).
      // However, 'Password1!' lowercased is 'password1!' which is NOT in the set.
      // We must use a password whose lowercase is exactly in the set:
      // 'Iloveyou1!' β€” lowercased: 'iloveyou1!' β€” NOT in set.
      // Use a password whose *lowercase* IS in set: 'changeme' β†’ 'Changeme' is 8 chars
      // but lacks uppercase/number/special β€” test blocklist with full complex variants:
      // 'ILoveyou1!' lowercased = 'iloveyou1!' β€” not in set.
      // Just test exact matches that satisfy complexity: use blocklist entry itself.
      // 'Iloveyou' is 8 chars, no number/special β†’ fails complexity, not blocklist.
      // Better: pick a blocklist entry that, when capitalised + special added, still matches.
      // The check is: COMMON_PASSWORDS.has(password.toLowerCase())
      // So 'FOOTBALL!' lowercased = 'football!' β€” not in set ('football' is in set).
      // We need password.toLowerCase() to equal a set entry exactly:
      // 'football' β†’ add uppercase β†’ 'Football' is still 8 chars, no number, no special β†’ fails complexity first
      // The blocklist check happens BEFORE complexity checks, after length + repetitive checks.
      // So any 8+ char string whose lowercase is in the blocklist gets caught first.
      // 'Password1' lowercased = 'password1' β†’ in blocklist! βœ“ (length ok, not repetitive)
      expect(validatePassword('Password1')).toEqual({
        ok: false,
        reason: expect.stringContaining('common'),
      });
    });

    it('AUTH-007: rejects "Changeme" whose lowercase is in the blocklist', () => {
      // 'changeme' is in the set; 'Changeme'.toLowerCase() === 'changeme' βœ“
      expect(validatePassword('Changeme')).toEqual({
        ok: false,
        reason: expect.stringContaining('common'),
      });
    });

    it('accepts a strong password that is not in the blocklist', () => {
      expect(validatePassword('MyUniq!1Trek')).toEqual({ ok: true });
    });
  });

  describe('repetitive password', () => {
    it('rejects passwords made of a single repeated character', () => {
      const result = validatePassword('AAAAAAAA');
      expect(result.ok).toBe(false);
      expect(result.reason).toContain('repetitive');
    });
  });

  describe('valid passwords', () => {
    it('accepts a strong unique password', () => {
      expect(validatePassword('Tr3k!SecurePass')).toEqual({ ok: true });
    });

    it('accepts a strong password with special characters', () => {
      expect(validatePassword('MyP@ss#2024')).toEqual({ ok: true });
    });
  });
});