|
1 | 1 | import { Test, TestingModule } from '@nestjs/testing'; |
| 2 | +import { BadRequestException } from '@nestjs/common'; |
2 | 3 | import { EvidenceFormsController } from './evidence-forms.controller'; |
3 | 4 | import { EvidenceFormsService } from './evidence-forms.service'; |
| 5 | +import { ActingUserResolver } from '../auth/acting-user.service'; |
4 | 6 | import { HybridAuthGuard } from '../auth/hybrid-auth.guard'; |
5 | 7 | import { PermissionGuard } from '../auth/permission.guard'; |
6 | | -import type { AuthContext as AuthContextType } from '../auth/types'; |
| 8 | +import type { |
| 9 | + AuthContext as AuthContextType, |
| 10 | + AuthenticatedRequest, |
| 11 | +} from '../auth/types'; |
| 12 | + |
| 13 | +jest.mock('@db', () => ({ db: {} })); |
7 | 14 |
|
8 | 15 | jest.mock('../auth/auth.server', () => ({ |
9 | 16 | auth: { api: { getSession: jest.fn() } }, |
@@ -61,10 +68,17 @@ describe('EvidenceFormsController', () => { |
61 | 68 | userRoles: ['admin'], |
62 | 69 | }; |
63 | 70 |
|
| 71 | + const mockActingUser = { |
| 72 | + resolve: jest.fn(), |
| 73 | + }; |
| 74 | + |
64 | 75 | beforeEach(async () => { |
65 | 76 | const module: TestingModule = await Test.createTestingModule({ |
66 | 77 | controllers: [EvidenceFormsController], |
67 | | - providers: [{ provide: EvidenceFormsService, useValue: mockService }], |
| 78 | + providers: [ |
| 79 | + { provide: EvidenceFormsService, useValue: mockService }, |
| 80 | + { provide: ActingUserResolver, useValue: mockActingUser }, |
| 81 | + ], |
68 | 82 | }) |
69 | 83 | .overrideGuard(HybridAuthGuard) |
70 | 84 | .useValue(mockGuard) |
@@ -298,26 +312,71 @@ describe('EvidenceFormsController', () => { |
298 | 312 | }); |
299 | 313 |
|
300 | 314 | describe('uploadSubmission', () => { |
301 | | - it('should call service.uploadSubmission with correct params', async () => { |
| 315 | + it('should call service.uploadSubmission with the session userId', async () => { |
302 | 316 | const body = { fileUrl: 'https://example.com/file.pdf' }; |
303 | 317 | const mockResult = { id: 'sub_upload' }; |
304 | 318 | mockService.uploadSubmission.mockResolvedValue(mockResult); |
| 319 | + mockActingUser.resolve.mockResolvedValue({ |
| 320 | + userId: 'user_1', |
| 321 | + source: 'session', |
| 322 | + }); |
305 | 323 |
|
| 324 | + const req = {} as AuthenticatedRequest; |
306 | 325 | const result = await controller.uploadSubmission( |
307 | 326 | 'org_1', |
308 | | - mockAuthContext, |
309 | 327 | 'security-awareness', |
310 | 328 | body, |
| 329 | + req, |
311 | 330 | ); |
312 | 331 |
|
313 | 332 | expect(result).toEqual(mockResult); |
| 333 | + expect(mockActingUser.resolve).toHaveBeenCalledWith(req, 'org_1'); |
314 | 334 | expect(service.uploadSubmission).toHaveBeenCalledWith({ |
315 | 335 | organizationId: 'org_1', |
316 | 336 | formType: 'security-awareness', |
317 | | - authContext: mockAuthContext, |
| 337 | + userId: 'user_1', |
318 | 338 | payload: body, |
319 | 339 | }); |
320 | 340 | }); |
| 341 | + |
| 342 | + it('should attribute to the org owner when called with API key auth', async () => { |
| 343 | + const body = { fileUrl: 'https://example.com/file.pdf' }; |
| 344 | + const mockResult = { id: 'sub_upload' }; |
| 345 | + mockService.uploadSubmission.mockResolvedValue(mockResult); |
| 346 | + mockActingUser.resolve.mockResolvedValue({ |
| 347 | + userId: 'owner_user', |
| 348 | + source: 'org-owner-fallback', |
| 349 | + callerLabel: 'via API key "CI"', |
| 350 | + }); |
| 351 | + |
| 352 | + const req = {} as AuthenticatedRequest; |
| 353 | + await controller.uploadSubmission('org_1', 'meeting', body, req); |
| 354 | + |
| 355 | + expect(service.uploadSubmission).toHaveBeenCalledWith({ |
| 356 | + organizationId: 'org_1', |
| 357 | + formType: 'meeting', |
| 358 | + userId: 'owner_user', |
| 359 | + payload: body, |
| 360 | + }); |
| 361 | + }); |
| 362 | + |
| 363 | + it('should throw BadRequestException when no owner can be resolved', async () => { |
| 364 | + mockActingUser.resolve.mockResolvedValue({ |
| 365 | + userId: null, |
| 366 | + source: 'org-owner-fallback', |
| 367 | + callerLabel: 'via API key', |
| 368 | + }); |
| 369 | + |
| 370 | + await expect( |
| 371 | + controller.uploadSubmission( |
| 372 | + 'org_1', |
| 373 | + 'meeting', |
| 374 | + { fileUrl: 'x' }, |
| 375 | + {} as AuthenticatedRequest, |
| 376 | + ), |
| 377 | + ).rejects.toBeInstanceOf(BadRequestException); |
| 378 | + expect(service.uploadSubmission).not.toHaveBeenCalled(); |
| 379 | + }); |
321 | 380 | }); |
322 | 381 |
|
323 | 382 | describe('reviewSubmission', () => { |
|
0 commit comments