Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 6m41s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 52s
Deploy to Staging / Verify Staging (pull_request) Failing after 4m7s
Deploy to Staging / Notify Staging Ready (pull_request) Has been skipped
Deploy to Staging / Notify Staging Failure (pull_request) Successful in 9s
Backend test fixtures: - Replace auth0|xxx format with UUID in all test userId values - Update admin tests for new id/userProfileId schema - Add missing deletionRequestedAt/deletionScheduledFor to auth test mocks - Fix admin integration test supertest usage (app.server) Frontend: - AdminUser type: auth0Sub -> id + userProfileId - admin.api.ts: all user management methods use userId (UUID) params - useUsers/useAdmins hooks: auth0Sub -> userId/id in mutations - AdminUsersPage + AdminUsersMobileScreen: user.auth0Sub -> user.id - Remove encodeURIComponent (UUIDs don't need encoding) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
232 lines
7.4 KiB
TypeScript
232 lines
7.4 KiB
TypeScript
/**
|
|
* @ai-summary Admin service unit tests
|
|
* @ai-context Tests business logic for admin management
|
|
*/
|
|
|
|
import { AdminService } from '../../domain/admin.service';
|
|
import { AdminRepository } from '../../data/admin.repository';
|
|
|
|
// Mock the audit log service
|
|
jest.mock('../../../audit-log', () => ({
|
|
auditLogService: {
|
|
info: jest.fn().mockResolvedValue(undefined),
|
|
warn: jest.fn().mockResolvedValue(undefined),
|
|
error: jest.fn().mockResolvedValue(undefined),
|
|
},
|
|
}));
|
|
|
|
describe('AdminService', () => {
|
|
let adminService: AdminService;
|
|
let mockRepository: jest.Mocked<AdminRepository>;
|
|
|
|
beforeEach(() => {
|
|
mockRepository = {
|
|
getAdminById: jest.fn(),
|
|
getAdminByUserProfileId: jest.fn(),
|
|
getAdminByEmail: jest.fn(),
|
|
getAllAdmins: jest.fn(),
|
|
getActiveAdmins: jest.fn(),
|
|
createAdmin: jest.fn(),
|
|
revokeAdmin: jest.fn(),
|
|
reinstateAdmin: jest.fn(),
|
|
logAuditAction: jest.fn(),
|
|
getAuditLogs: jest.fn(),
|
|
} as any;
|
|
|
|
adminService = new AdminService(mockRepository);
|
|
});
|
|
|
|
describe('getAdminById', () => {
|
|
it('should return admin when found', async () => {
|
|
const mockAdmin = {
|
|
id: '7c9e6679-7425-40de-944b-e07fc1f90ae7',
|
|
userProfileId: '7c9e6679-7425-40de-944b-e07fc1f90ae7',
|
|
email: 'admin@motovaultpro.com',
|
|
role: 'admin' as const,
|
|
createdAt: new Date(),
|
|
createdBy: '550e8400-e29b-41d4-a716-446655440000',
|
|
revokedAt: null,
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
mockRepository.getAdminById.mockResolvedValue(mockAdmin);
|
|
|
|
const result = await adminService.getAdminById('7c9e6679-7425-40de-944b-e07fc1f90ae7');
|
|
|
|
expect(result).toEqual(mockAdmin);
|
|
expect(mockRepository.getAdminById).toHaveBeenCalledWith('7c9e6679-7425-40de-944b-e07fc1f90ae7');
|
|
});
|
|
|
|
it('should return null when admin not found', async () => {
|
|
mockRepository.getAdminById.mockResolvedValue(null);
|
|
|
|
const result = await adminService.getAdminById('00000000-0000-0000-0000-000000000000');
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('createAdmin', () => {
|
|
it('should create new admin and log audit', async () => {
|
|
const newAdminId = '8f14e45f-ceea-367f-a27f-c9a6d0c67e0e';
|
|
const creatorId = '7c9e6679-7425-40de-944b-e07fc1f90ae7';
|
|
const mockAdmin = {
|
|
id: newAdminId,
|
|
userProfileId: newAdminId,
|
|
email: 'newadmin@motovaultpro.com',
|
|
role: 'admin' as const,
|
|
createdAt: new Date(),
|
|
createdBy: creatorId,
|
|
revokedAt: null,
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
mockRepository.getAdminByEmail.mockResolvedValue(null);
|
|
mockRepository.createAdmin.mockResolvedValue(mockAdmin);
|
|
mockRepository.logAuditAction.mockResolvedValue({} as any);
|
|
|
|
const result = await adminService.createAdmin(
|
|
'newadmin@motovaultpro.com',
|
|
'admin',
|
|
newAdminId,
|
|
creatorId
|
|
);
|
|
|
|
expect(result).toEqual(mockAdmin);
|
|
expect(mockRepository.createAdmin).toHaveBeenCalled();
|
|
expect(mockRepository.logAuditAction).toHaveBeenCalledWith(
|
|
creatorId,
|
|
'CREATE',
|
|
mockAdmin.id,
|
|
'admin_user',
|
|
mockAdmin.email,
|
|
expect.any(Object)
|
|
);
|
|
});
|
|
|
|
it('should reject if admin already exists', async () => {
|
|
const existingId = '7c9e6679-7425-40de-944b-e07fc1f90ae7';
|
|
const existingAdmin = {
|
|
id: existingId,
|
|
userProfileId: existingId,
|
|
email: 'admin@motovaultpro.com',
|
|
role: 'admin' as const,
|
|
createdAt: new Date(),
|
|
createdBy: '550e8400-e29b-41d4-a716-446655440000',
|
|
revokedAt: null,
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
mockRepository.getAdminByEmail.mockResolvedValue(existingAdmin);
|
|
|
|
await expect(
|
|
adminService.createAdmin('admin@motovaultpro.com', 'admin', '8f14e45f-ceea-367f-a27f-c9a6d0c67e0e', existingId)
|
|
).rejects.toThrow('already exists');
|
|
});
|
|
});
|
|
|
|
describe('revokeAdmin', () => {
|
|
it('should revoke admin when multiple active admins exist', async () => {
|
|
const toRevokeId = 'a1b2c3d4-e5f6-7890-1234-567890abcdef';
|
|
const admin1Id = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
|
|
const admin2Id = '8f14e45f-ceea-367f-a27f-c9a6d0c67e0e';
|
|
|
|
const revokedAdmin = {
|
|
id: toRevokeId,
|
|
userProfileId: toRevokeId,
|
|
email: 'toadmin@motovaultpro.com',
|
|
role: 'admin' as const,
|
|
createdAt: new Date(),
|
|
createdBy: '550e8400-e29b-41d4-a716-446655440000',
|
|
revokedAt: new Date(),
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
const activeAdmins = [
|
|
{
|
|
id: admin1Id,
|
|
userProfileId: admin1Id,
|
|
email: 'admin1@motovaultpro.com',
|
|
role: 'admin' as const,
|
|
createdAt: new Date(),
|
|
createdBy: '550e8400-e29b-41d4-a716-446655440000',
|
|
revokedAt: null,
|
|
updatedAt: new Date(),
|
|
},
|
|
{
|
|
id: admin2Id,
|
|
userProfileId: admin2Id,
|
|
email: 'admin2@motovaultpro.com',
|
|
role: 'admin' as const,
|
|
createdAt: new Date(),
|
|
createdBy: '550e8400-e29b-41d4-a716-446655440000',
|
|
revokedAt: null,
|
|
updatedAt: new Date(),
|
|
},
|
|
];
|
|
|
|
mockRepository.getActiveAdmins.mockResolvedValue(activeAdmins);
|
|
mockRepository.revokeAdmin.mockResolvedValue(revokedAdmin);
|
|
mockRepository.logAuditAction.mockResolvedValue({} as any);
|
|
|
|
const result = await adminService.revokeAdmin(toRevokeId, admin1Id);
|
|
|
|
expect(result).toEqual(revokedAdmin);
|
|
expect(mockRepository.revokeAdmin).toHaveBeenCalledWith(toRevokeId);
|
|
expect(mockRepository.logAuditAction).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should prevent revoking last active admin', async () => {
|
|
const lastAdminId = '7c9e6679-7425-40de-944b-e07fc1f90ae7';
|
|
const lastAdmin = {
|
|
id: lastAdminId,
|
|
userProfileId: lastAdminId,
|
|
email: 'last@motovaultpro.com',
|
|
role: 'admin' as const,
|
|
createdAt: new Date(),
|
|
createdBy: '550e8400-e29b-41d4-a716-446655440000',
|
|
revokedAt: null,
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
mockRepository.getActiveAdmins.mockResolvedValue([lastAdmin]);
|
|
|
|
await expect(
|
|
adminService.revokeAdmin(lastAdminId, lastAdminId)
|
|
).rejects.toThrow('Cannot revoke the last active admin');
|
|
});
|
|
});
|
|
|
|
describe('reinstateAdmin', () => {
|
|
it('should reinstate revoked admin and log audit', async () => {
|
|
const reinstateId = 'b2c3d4e5-f6a7-8901-2345-678901bcdef0';
|
|
const adminActorId = '7c9e6679-7425-40de-944b-e07fc1f90ae7';
|
|
const reinstatedAdmin = {
|
|
id: reinstateId,
|
|
userProfileId: reinstateId,
|
|
email: 'reinstate@motovaultpro.com',
|
|
role: 'admin' as const,
|
|
createdAt: new Date(),
|
|
createdBy: '550e8400-e29b-41d4-a716-446655440000',
|
|
revokedAt: null,
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
mockRepository.reinstateAdmin.mockResolvedValue(reinstatedAdmin);
|
|
mockRepository.logAuditAction.mockResolvedValue({} as any);
|
|
|
|
const result = await adminService.reinstateAdmin(reinstateId, adminActorId);
|
|
|
|
expect(result).toEqual(reinstatedAdmin);
|
|
expect(mockRepository.reinstateAdmin).toHaveBeenCalledWith(reinstateId);
|
|
expect(mockRepository.logAuditAction).toHaveBeenCalledWith(
|
|
adminActorId,
|
|
'REINSTATE',
|
|
reinstateId,
|
|
'admin_user',
|
|
reinstatedAdmin.email
|
|
);
|
|
});
|
|
});
|
|
});
|