93 lines
4.3 KiB
TypeScript
93 lines
4.3 KiB
TypeScript
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'), '<!doctype html><title>ASSESSOR</title>', 'utf8');
|
|
const server = createAssessorHttpServer({ repository: new LocalAssessorRepository(), staticRoot: directory, regulationMapping });
|
|
await new Promise<void>((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<void>((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' }
|
|
});
|
|
});
|
|
});
|