import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import type { AddressInfo } from 'node:net'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import type { Server } from 'node:http'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { createAssessorHttpServer } from '../../server/assessor-http-server'; import { LocalAssessorRepository } from '../../src/persistence/local-assessor-repository'; import { RegulationMappingUnavailableError, type RegulationMapping, type RegulationCapabilityReport } from '../../server/regmap/regulation-mapping'; function readyMapping(): RegulationMapping { const report: RegulationCapabilityReport = { capability: 'ready', catalogVersion: 'TEST/2024', embeddingModel: 'test-embed', collection: 'c' }; return { cachedCapability: () => report, refreshCapability: async () => report, retrieve: async () => ({ catalogVersion: 'TEST/2024', embeddingModel: 'test-embed', candidates: [{ unitId: '8.24', controlId: '8.24', score: 0.9 }] }) }; } function unavailableMapping(): RegulationMapping { const report: RegulationCapabilityReport = { capability: 'misconfigured', detailCode: 'collection_missing' }; return { cachedCapability: () => report, refreshCapability: async () => report, retrieve: async () => { throw new RegulationMappingUnavailableError(report); } }; } async function startServer(regulationMapping: RegulationMapping): Promise<{ server: Server; baseUrl: string; directory: string }> { const directory = await mkdtemp(join(tmpdir(), 'assessor-regmap-')); await writeFile(join(directory, 'index.html'), 'ASSESSOR', 'utf8'); const server = createAssessorHttpServer({ repository: new LocalAssessorRepository(), staticRoot: directory, regulationMapping }); await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); }); const address = server.address() as AddressInfo; return { server, baseUrl: `http://127.0.0.1:${address.port}`, directory }; } describe('regulation mapping HTTP endpoints', () => { let server: Server; let baseUrl: string; let directory: string; afterEach(async () => { await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())); await rm(directory, { recursive: true, force: true }); }); const retrieve = (body: unknown) => fetch(`${baseUrl}/api/v1/regulation-mapping/retrieve`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Assessor-Request': '1', Origin: baseUrl }, body: JSON.stringify(body) }); it('exposes the capability on health and the dedicated endpoint', async () => { ({ server, baseUrl, directory } = await startServer(readyMapping())); const health = await (await fetch(`${baseUrl}/api/v1/health`)).json(); expect(health.regulationMapping).toMatchObject({ capability: 'ready', catalogVersion: 'TEST/2024' }); const capability = await (await fetch(`${baseUrl}/api/v1/regulation-mapping/capability`)).json(); expect(capability).toMatchObject({ capability: 'ready', embeddingModel: 'test-embed' }); }); it('retrieves candidate units when ready', async () => { ({ server, baseUrl, directory } = await startServer(readyMapping())); const response = await retrieve({ definition: { title: 'RC4 enabled on DC' } }); expect(response.status).toBe(200); expect(await response.json()).toMatchObject({ candidates: [{ unitId: '8.24' }] }); }); it('rejects a retrieve request without a title', async () => { ({ server, baseUrl, directory } = await startServer(readyMapping())); const response = await retrieve({ definition: { description: 'no title' } }); expect(response.status).toBe(400); await expect(response.json()).resolves.toMatchObject({ error: { code: 'invalid_regulation_definition' } }); }); it('returns 503 with the capability when the feature is unavailable', async () => { ({ server, baseUrl, directory } = await startServer(unavailableMapping())); const response = await retrieve({ definition: { title: 'anything' } }); expect(response.status).toBe(503); await expect(response.json()).resolves.toMatchObject({ error: { code: 'regulation_mapping_unavailable' }, capability: { capability: 'misconfigured', detailCode: 'collection_missing' } }); }); });