Files
hope-events/backend/tests/twoFactor.test.js
T
joshuaandClaude Opus 5.5 e52bf67015 Add optional two-factor authentication
Users can turn on 2FA from Profile & Security and receive a 6-digit
login code by email or WhatsApp, with a "trust this device for 30 days"
option. Admins can reset it for a locked-out user, and the self-service
kiosk handles the code step at setup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:49:34 +02:00

75 lines
2.3 KiB
JavaScript

process.env.JWT_SECRET = 'test-secret';
// The pure helpers under test never touch the database.
jest.mock('../src/config/db', () => ({}));
const {
generateCode,
hashCode,
hashDeviceToken,
maskEmail,
maskPhone,
hasRealEmail,
} = require('../src/utils/twoFactor');
describe('generateCode', () => {
test('always returns exactly six digits', () => {
for (let i = 0; i < 500; i++) {
expect(generateCode()).toMatch(/^\d{6}$/);
}
});
});
describe('hashCode / hashDeviceToken', () => {
test('is deterministic and never returns the input', () => {
expect(hashCode('123456')).toBe(hashCode('123456'));
expect(hashCode('123456')).not.toContain('123456');
expect(hashCode('123456')).toMatch(/^[0-9a-f]{64}$/);
});
test('different codes hash differently', () => {
expect(hashCode('123456')).not.toBe(hashCode('123457'));
});
test('code and device-token hashes are namespaced apart', () => {
expect(hashCode('123456')).not.toBe(hashDeviceToken('123456'));
});
test('depends on JWT_SECRET', () => {
const before = hashCode('123456');
process.env.JWT_SECRET = 'another-secret';
expect(hashCode('123456')).not.toBe(before);
process.env.JWT_SECRET = 'test-secret';
});
});
describe('masking', () => {
test('maskEmail keeps the first letter and the domain', () => {
expect(maskEmail('jane@example.com')).toBe('j•••@example.com');
});
test('maskEmail copes with junk input', () => {
expect(maskEmail('')).toBe('•••');
expect(maskEmail(null)).toBe('•••');
});
test('maskPhone keeps only the last four digits', () => {
expect(maskPhone('27821234567')).toBe('••• ••• 4567');
expect(maskPhone('+27 82 123 4567')).toBe('••• ••• 4567');
});
});
describe('hasRealEmail', () => {
test('accepts a normal address', () => {
expect(hasRealEmail({ email: 'jane@example.com' })).toBe(true);
});
test('rejects guest and deleted placeholders and missing values', () => {
expect(hasRealEmail({ email: 'abc@guest.local' })).toBe(false);
expect(hasRealEmail({ email: 'deleted-1@deleted.invalid' })).toBe(false);
expect(hasRealEmail({ email: 'deleted-1@deleted.local' })).toBe(false);
expect(hasRealEmail({})).toBe(false);
expect(hasRealEmail(null)).toBe(false);
});
});