feat: complete login and user management flow

This commit is contained in:
hectorzhao
2026-07-02 16:28:48 +08:00
parent 9c639d54b9
commit e26d8324c3
22 changed files with 1320 additions and 300 deletions
+62
View File
@@ -0,0 +1,62 @@
import { UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { hashPassword } from '../users/users.service';
function createUsersMock(roleCode: string, overrides: Record<string, unknown> = {}) {
const user = {
id: 'user-1',
tenantId: roleCode === 'enterprise_admin' ? 'tenant-1' : null,
username: 'user',
email: 'user@example.com',
phone: '13800000000',
displayName: '用户',
passwordHash: hashPassword('secret1'),
status: 'active',
deletedAt: null,
lockedUntil: null,
tenant: { id: 'tenant-1', name: '企业A' },
roles: [{ role: { code: roleCode } }],
...overrides,
};
return {
findByLogin: jest.fn().mockResolvedValue(user),
recordLoginSuccess: jest.fn(),
recordLoginFailure: jest.fn(),
};
}
async function loginWithCaptcha(service: AuthService, portal: 'admin' | 'client', password = 'secret1') {
const captcha = service.createCaptcha();
const answer = captcha.challenge.split('=')[0].split('+').map((part) => Number(part.trim())).reduce((sum, value) => sum + value, 0);
return service.login({
login: 'user@example.com',
password,
captchaId: captcha.captchaId,
captchaText: String(answer),
}, portal);
}
describe('AuthService', () => {
it('allows platform admins to login admin portal', async () => {
const users = createUsersMock('platform_admin');
const service = new AuthService(users as never);
await expect(loginWithCaptcha(service, 'admin')).resolves.toEqual(expect.objectContaining({ portal: 'admin' }));
expect(users.recordLoginSuccess).toHaveBeenCalledWith('user-1');
});
it('rejects enterprise admins on admin portal', async () => {
const users = createUsersMock('enterprise_admin');
const service = new AuthService(users as never);
await expect(loginWithCaptcha(service, 'admin')).rejects.toBeInstanceOf(UnauthorizedException);
expect(users.recordLoginFailure).toHaveBeenCalledWith('user-1');
});
it('locks user after five failed password attempts', async () => {
const users = createUsersMock('platform_admin');
const service = new AuthService(users as never);
for (let index = 0; index < 5; index += 1) {
await expect(loginWithCaptcha(service, 'admin', 'bad-password')).rejects.toBeInstanceOf(UnauthorizedException);
}
expect(users.recordLoginFailure).toHaveBeenCalledTimes(5);
});
});