import { INestApplication } from '@nestjs/common'; import request = require('supertest'); import { InquiryController } from '../../src/inquiry/inquiry.controller'; import { InquiryService } from '../../src/inquiry/inquiry.service'; import { ProviderOrchestratorService } from '../../src/providers/strategy/provider-orchestrator.service'; import { InquiryLogService } from '../../src/logging/inquiry-log.service'; import { JwtAuthGuard } from '../../src/auth/guards/jwt-auth.guard'; import { InquiryAccessGuard } from '../../src/auth/guards/inquiry-access.guard'; import { UserRateLimitGuard } from '../../src/rate-limit/user-rate-limit.guard'; import { inquiryEndpointCases } from '../support/inquiry-endpoints'; import { createHttpTestApp } from '../support/app'; import { authenticatedGuard, passGuard } from '../support/auth'; import { normalizedProviderError, providerErrorCases, successfulProviderResult, } from '../support/mock-provider'; import { expectErrorEnvelope, expectSuccessEnvelope } from '../support/assertions'; describe('inquiry endpoints (integration)', () => { let app: INestApplication; const orchestrator = { executeWithFallback: jest.fn(), }; const inquiryLogs = { create: jest.fn().mockResolvedValue(undefined), }; beforeAll(async () => { app = await createHttpTestApp( { controllers: [InquiryController], providers: [ InquiryService, { provide: ProviderOrchestratorService, useValue: orchestrator }, { provide: InquiryLogService, useValue: inquiryLogs }, ], }, (builder) => builder .overrideGuard(JwtAuthGuard) .useValue(authenticatedGuard()) .overrideGuard(InquiryAccessGuard) .useValue(passGuard()) .overrideGuard(UserRateLimitGuard) .useValue(passGuard()), ); }); beforeEach(() => { jest.clearAllMocks(); orchestrator.executeWithFallback.mockResolvedValue(successfulProviderResult()); }); afterAll(async () => { await app.close(); }); describe.each(inquiryEndpointCases)('$name', (endpoint) => { it('returns a successful response and maps provider data', async () => { orchestrator.executeWithFallback.mockResolvedValueOnce( successfulProviderResult({ raw: { mapped: true, inquiryType: endpoint.inquiryType } }), ); const response = await request(app.getHttpServer()) .post(endpoint.path) .send(endpoint.validPayload) .expect(201); expectSuccessEnvelope(response); expect(response.body.data).toMatchObject({ mapped: true, inquiryType: endpoint.inquiryType, }); expect(orchestrator.executeWithFallback).toHaveBeenCalledWith( endpoint.inquiryType, expect.objectContaining(endpoint.validPayload), expect.objectContaining({ requestId: 'test-request-id' }), ); }); it('returns validation errors without calling providers', async () => { const response = await request(app.getHttpServer()) .post(endpoint.path) .send(endpoint.invalidPayload) .expect(400); expectErrorEnvelope(response, 'VALIDATION_ERROR'); if (endpoint.name === 'person inquiry') { expect(response.body.error).toMatchObject({ message: 'nationalCode must be exactly 10 digits', messageFa: 'کد ملی باید دقیقا ۱۰ رقم باشد', details: [ { field: 'nationalCode', constraints: ['nationalCode must be exactly 10 digits'], }, ], }); } expect(orchestrator.executeWithFallback).not.toHaveBeenCalled(); }); it('rejects malformed JSON without calling providers', async () => { await request(app.getHttpServer()) .post(endpoint.path) .set('Content-Type', 'application/json') .send('{"broken":') .expect(400); expect(orchestrator.executeWithFallback).not.toHaveBeenCalled(); }); describe.each(providerErrorCases)('$name', (providerCase) => { it('maps provider errors into the normalized response envelope', async () => { orchestrator.executeWithFallback.mockRejectedValueOnce( normalizedProviderError(providerCase.error), ); const response = await request(app.getHttpServer()) .post(endpoint.path) .send(endpoint.validPayload) .expect(201); expectErrorEnvelope(response, providerCase.code); expect(response.body.data).toBeNull(); expect(response.body.error.providerCode).toBe(providerCase.error.providerCode); }); }); }); });