Files
TREK/server/tests/unit/nest/common/passwordPolicy.test.ts
T
Maurice 724c88c201 style: format server and shared with prettier
A mechanical prettier --write over server/src, server/tests, server/scripts
and shared/src with the workspaces' .prettierrc; nothing but layout and
import order changes, and every file keeps the same set of imports. shared
is formatted throughout and its format baseline is empty. The server
baseline keeps the 35 files that Prettier would grow past their lint:size
limit (they need a split first, which is not a formatting change), and the
generated entity files stay as their generator writes them.
2026-10-10 21:23:37 +02:00

103 lines
4.6 KiB
TypeScript

import { validatePassword } from '../../../../src/nest/common/passwordPolicy';
import { describe, it, expect } from 'vitest';
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 });
});
});
});