Spaces:
Runtime error
Runtime error
| import type { Request } from 'express'; | |
| import { AuthController } from './auth.controller'; | |
| import { AuditAction } from '../audit/entities/audit-log.entity'; | |
| import type { ApiKey } from './entities/api-key.entity'; | |
| import type { AuthService } from './auth.service'; | |
| import type { AuditService } from '../audit/audit.service'; | |
| // API-key lifecycle operations (create / delete / revoke) must leave an audit trail — they were | |
| // previously unrecorded. These assert the controller emits the matching audit action with the acting | |
| // admin key, the resolved client IP, and the target key in metadata. | |
| describe('AuthController — API-key lifecycle audit logging', () => { | |
| const actor = { id: 'admin-key', name: 'admin' } as unknown as ApiKey; | |
| const makeReq = (): Request => | |
| ({ method: 'POST', path: '/auth/api-keys', clientIp: '203.0.113.7' }) as unknown as Request; | |
| let authService: { | |
| createApiKey: jest.Mock; | |
| findOne: jest.Mock; | |
| update: jest.Mock; | |
| delete: jest.Mock; | |
| revoke: jest.Mock; | |
| }; | |
| let auditService: { logInfo: jest.Mock }; | |
| let controller: AuthController; | |
| beforeEach(() => { | |
| const createdKey = { | |
| id: 'k1', | |
| name: 'new-key', | |
| role: 'user', | |
| keyPrefix: 'ow_', | |
| isActive: true, | |
| usageCount: 0, | |
| createdAt: new Date(), | |
| }; | |
| authService = { | |
| createApiKey: jest.fn().mockResolvedValue({ apiKey: createdKey, rawKey: 'raw-secret' }), | |
| findOne: jest.fn().mockResolvedValue({ | |
| id: 'k1', | |
| name: 'target-key', | |
| role: 'viewer', | |
| allowedIps: null, | |
| allowedSessions: null, | |
| expiresAt: null, | |
| }), | |
| update: jest.fn().mockResolvedValue({ ...createdKey, role: 'admin' }), | |
| delete: jest.fn().mockResolvedValue(undefined), | |
| revoke: jest.fn().mockResolvedValue({ ...createdKey, isActive: false }), | |
| }; | |
| auditService = { logInfo: jest.fn().mockResolvedValue(null) }; | |
| controller = new AuthController(authService as unknown as AuthService, auditService as unknown as AuditService); | |
| }); | |
| const lastContextFor = ( | |
| action: AuditAction, | |
| ): | |
| | { | |
| apiKey?: ApiKey; | |
| ipAddress?: string; | |
| metadata?: { targetKeyId?: string; before?: { role?: string }; after?: { role?: string } }; | |
| } | |
| | undefined => { | |
| const calls = auditService.logInfo.mock.calls as Array< | |
| [ | |
| AuditAction, | |
| { | |
| apiKey?: ApiKey; | |
| ipAddress?: string; | |
| metadata?: { targetKeyId?: string; before?: { role?: string }; after?: { role?: string } }; | |
| }, | |
| ] | |
| >; | |
| return calls.find(c => c[0] === action)?.[1]; | |
| }; | |
| it('logs API_KEY_CREATED on create, with the acting key, IP, and target id', async () => { | |
| await controller.create({ name: 'new-key' }, makeReq(), actor); | |
| const ctx = lastContextFor(AuditAction.API_KEY_CREATED); | |
| expect(ctx).toBeDefined(); | |
| expect(ctx?.apiKey).toBe(actor); | |
| expect(ctx?.ipAddress).toBe('203.0.113.7'); | |
| expect(ctx?.metadata?.targetKeyId).toBe('k1'); | |
| expect(JSON.stringify(auditService.logInfo.mock.calls)).not.toContain('raw-secret'); | |
| }); | |
| it('logs API_KEY_DELETED on delete', async () => { | |
| await controller.delete('k1', makeReq(), actor); | |
| expect(authService.delete).toHaveBeenCalledWith('k1'); | |
| expect(lastContextFor(AuditAction.API_KEY_DELETED)?.metadata?.targetKeyId).toBe('k1'); | |
| }); | |
| it('logs API_KEY_UPDATED with before/after authorization state', async () => { | |
| await controller.update('k1', { role: 'admin' } as never, makeReq(), actor); | |
| const ctx = lastContextFor(AuditAction.API_KEY_UPDATED); | |
| expect(ctx?.apiKey).toBe(actor); | |
| expect(ctx?.metadata?.targetKeyId).toBe('k1'); | |
| expect(ctx?.metadata?.before?.role).toBe('viewer'); | |
| expect(ctx?.metadata?.after?.role).toBe('admin'); | |
| }); | |
| it('logs API_KEY_REVOKED on revoke', async () => { | |
| await controller.revoke('k1', makeReq(), actor); | |
| expect(lastContextFor(AuditAction.API_KEY_REVOKED)?.metadata?.targetKeyId).toBe('k1'); | |
| }); | |
| }); | |