Files
motovaultpro/backend/src/features/admin/tests/unit/admin.service.test.ts
Eric Gullickson 754639c86d
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
chore: update test fixtures and frontend for UUID identity (refs #217)
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>
2026-02-16 10:21:18 -06:00

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
);
});
});
});