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