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
+87
View File
@@ -0,0 +1,87 @@
import { BadRequestException } from '@nestjs/common';
import { UsersService } from './users.service';
function createPrismaMock() {
const roles = new Map<string, { id: string; code: string; name: string; scope: string }>();
return {
user: {
create: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'user-1', ...data, tenant: null, roles: [{ role: roles.get('platform_admin') ?? roles.get('enterprise_admin') }] })),
findMany: jest.fn().mockResolvedValue([]),
findFirst: jest.fn(),
findUnique: jest.fn(),
update: jest.fn(),
},
role: {
upsert: jest.fn().mockImplementation(({ where, create, update }) => {
const role = { id: `role-${where.code}`, ...create, ...update };
roles.set(where.code, role);
return Promise.resolve(role);
}),
findMany: jest.fn(),
create: jest.fn(),
},
userRole: {
upsert: jest.fn(),
deleteMany: jest.fn(),
create: jest.fn(),
},
permission: { findMany: jest.fn(), create: jest.fn() },
rolePermission: { upsert: jest.fn() },
operationLog: { create: jest.fn() },
$transaction: jest.fn((callback) => callback({
user: {
update: jest.fn().mockResolvedValue({ id: 'user-1', username: 'u', roles: [] }),
},
userRole: {
deleteMany: jest.fn(),
create: jest.fn(),
},
})),
};
}
describe('UsersService', () => {
it('requires enterprise admins to be linked to a tenant', async () => {
const service = new UsersService(createPrismaMock() as never);
await expect(service.create({
displayName: '企业管理员',
email: 'tenant@example.com',
password: 'secret1',
roleCode: 'enterprise_admin',
})).rejects.toBeInstanceOf(BadRequestException);
});
it('rejects platform admins linked to tenants', async () => {
const service = new UsersService(createPrismaMock() as never);
await expect(service.create({
tenantId: 'tenant-1',
displayName: '平台管理员',
email: 'admin@example.com',
password: 'secret1',
roleCode: 'platform_admin',
})).rejects.toThrow('platform_admin must not be linked to a tenant');
});
it('creates platform admins and writes operation logs', async () => {
const prisma = createPrismaMock();
const service = new UsersService(prisma as never);
await service.create({
displayName: '平台管理员',
email: 'admin@example.com',
phone: '13800000000',
password: 'secret1',
roleCode: 'platform_admin',
operatorId: 'operator-1',
});
expect(prisma.user.create).toHaveBeenCalledWith(expect.objectContaining({
data: expect.objectContaining({
email: 'admin@example.com',
phone: '13800000000',
status: 'active',
}),
}));
expect(prisma.operationLog.create).toHaveBeenCalledWith(expect.objectContaining({
data: expect.objectContaining({ action: 'user.created', resource: 'user' }),
}));
});
});