/* * Tests for the password-policy validation and lockout helpers. These exercise the pure logic * (complexity checks, identifier detection, lockout math) against an explicit policy object so * they do not depend on the database-backed settings store. */ const { checkPassword, describe: describePolicy, DEFAULT_POLICY, isLocked, minAgeRemainingMinutes, passwordExpired } = require('./passwordPolicy'); const strictPolicy = { ...DEFAULT_POLICY, min_length: 12, require_upper: true, require_lower: true, require_number: true, require_symbol: true, disallow_identifiers: true, history_count: 0 // disable DB-backed history for these pure tests }; describe('password policy', () => { test('a compliant password passes every rule', () => { expect(checkPassword('Str0ng!Passphrase', null, strictPolicy)).toEqual([]); }); test('each missing character class is reported', () => { expect(checkPassword('short', null, strictPolicy).length).toBeGreaterThan(0); expect(checkPassword('alllowercase1!', null, strictPolicy)).toContain('Add an uppercase letter'); expect(checkPassword('ALLUPPERCASE1!', null, strictPolicy)).toContain('Add a lowercase letter'); expect(checkPassword('NoNumbersHere!', null, strictPolicy)).toContain('Add a number'); expect(checkPassword('NoSymbolsHere1', null, strictPolicy)).toContain('Add a symbol'); }); test('minimum length is enforced', () => { expect(checkPassword('Ab1!', null, strictPolicy)).toContain('Use at least 12 characters'); }); test('the user name or email cannot appear in the password', () => { const user = { name: 'Jordan Banks', email: 'jbanks@example.com' }; expect(checkPassword('Jordan!Secret99', user, strictPolicy)).toContain('Must not contain your name or email'); expect(checkPassword('jbanks!Secret99', user, strictPolicy)).toContain('Must not contain your name or email'); // A password without the identifiers is fine. expect(checkPassword('Unrelated!Secret99', user, strictPolicy)).toEqual([]); }); test('identifier check is skipped when the policy disables it', () => { const policy = { ...strictPolicy, disallow_identifiers: false }; const user = { name: 'Jordan Banks', email: 'jbanks@example.com' }; expect(checkPassword('Jordan!Secret99', user, policy)).toEqual([]); }); test('describe lists the active rules', () => { const rules = describePolicy(strictPolicy); expect(rules.some((r) => /12 characters/.test(r))).toBe(true); expect(rules.some((r) => /uppercase/.test(r))).toBe(true); }); test('isLocked reflects the locked_until timestamp', () => { expect(isLocked({ locked_until: new Date(Date.now() + 60000).toISOString() })).toBe(true); expect(isLocked({ locked_until: new Date(Date.now() - 60000).toISOString() })).toBe(false); expect(isLocked({ locked_until: null })).toBe(false); }); test('passwordExpired compares age against expiry_days', () => { const policy = { ...DEFAULT_POLICY, expiry_days: 30 }; const old = new Date(Date.now() - 40 * 86400000).toISOString(); const recent = new Date(Date.now() - 5 * 86400000).toISOString(); expect(passwordExpired({ password_changed_at: old }, policy)).toBe(true); expect(passwordExpired({ password_changed_at: recent }, policy)).toBe(false); // expiry_days 0 means passwords never expire. expect(passwordExpired({ password_changed_at: old }, { ...policy, expiry_days: 0 })).toBe(false); }); test('minAgeRemainingMinutes blocks changes that are too soon', () => { const policy = { ...DEFAULT_POLICY, min_age_hours: 24 }; const justChanged = new Date(Date.now() - 60000).toISOString(); const longAgo = new Date(Date.now() - 48 * 3600000).toISOString(); expect(minAgeRemainingMinutes({ password_changed_at: justChanged }, policy)).toBeGreaterThan(0); expect(minAgeRemainingMinutes({ password_changed_at: longAgo }, policy)).toBe(0); }); });