test: add MFA service and UserEntity MFA unit tests

Add comprehensive unit tests for TOTP-based MFA:
- MfaService: generateSetup, verifyTotp, backup code generation/verification
- UserEntity: enableTotp, disableTotp, consumeBackupCode, createNew MFA defaults

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-11 23:47:01 +07:00
parent 1fbe2f4e73
commit cb6664fbf9
2 changed files with 234 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
import { UserEntity, type UserProps } from '../entities/user.entity';
import { HashedPassword } from '../value-objects/hashed-password.vo';
import { Phone } from '../value-objects/phone.vo';
function createTestUser(overrides: Partial<UserProps> = {}): UserEntity {
const phone = Phone.create('+84912345678').unwrap();
const passwordHash = HashedPassword.fromHash('$2b$12$test-hash');
return new UserEntity('user-1', {
email: null,
phone,
passwordHash,
fullName: 'Test User',
avatarUrl: null,
role: 'AGENT',
kycStatus: 'NONE',
kycData: null,
isActive: true,
totpSecret: null,
totpEnabled: false,
totpBackupCodes: [],
totpEnabledAt: null,
...overrides,
});
}
describe('UserEntity MFA methods', () => {
describe('enableTotp', () => {
it('sets TOTP secret and enables MFA', () => {
const user = createTestUser();
expect(user.totpEnabled).toBe(false);
expect(user.totpSecret).toBeNull();
user.enableTotp('JBSWY3DPEHPK3PXP', ['hash1', 'hash2']);
expect(user.totpEnabled).toBe(true);
expect(user.totpSecret).toBe('JBSWY3DPEHPK3PXP');
expect(user.totpBackupCodes).toEqual(['hash1', 'hash2']);
expect(user.totpEnabledAt).toBeInstanceOf(Date);
});
});
describe('disableTotp', () => {
it('clears TOTP fields', () => {
const user = createTestUser({
totpSecret: 'JBSWY3DPEHPK3PXP',
totpEnabled: true,
totpBackupCodes: ['hash1'],
totpEnabledAt: new Date(),
});
user.disableTotp();
expect(user.totpEnabled).toBe(false);
expect(user.totpSecret).toBeNull();
expect(user.totpBackupCodes).toEqual([]);
expect(user.totpEnabledAt).toBeNull();
});
});
describe('consumeBackupCode', () => {
it('removes the backup code at the given index', () => {
const user = createTestUser({
totpBackupCodes: ['hash0', 'hash1', 'hash2', 'hash3'],
});
user.consumeBackupCode(1);
expect(user.totpBackupCodes).toEqual(['hash0', 'hash2', 'hash3']);
});
it('removes the first backup code', () => {
const user = createTestUser({
totpBackupCodes: ['hash0', 'hash1', 'hash2'],
});
user.consumeBackupCode(0);
expect(user.totpBackupCodes).toEqual(['hash1', 'hash2']);
});
it('removes the last backup code', () => {
const user = createTestUser({
totpBackupCodes: ['hash0', 'hash1', 'hash2'],
});
user.consumeBackupCode(2);
expect(user.totpBackupCodes).toEqual(['hash0', 'hash1']);
});
});
describe('createNew', () => {
it('initializes MFA fields to default values', () => {
const phone = Phone.create('+84912345678').unwrap();
const passwordHash = HashedPassword.fromHash('$2b$12$test');
const user = UserEntity.createNew('new-1', phone, 'New User', passwordHash);
expect(user.totpSecret).toBeNull();
expect(user.totpEnabled).toBe(false);
expect(user.totpBackupCodes).toEqual([]);
expect(user.totpEnabledAt).toBeNull();
});
});
});