70 lines
4.0 KiB
TypeScript
70 lines
4.0 KiB
TypeScript
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import type { Server } from 'node:http';
|
|
import type { AddressInfo } from 'node:net';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { createAssessorHttpServer } from '../../server/assessor-http-server';
|
|
import { BsiCatalogError, type BsiCatalogService } from '../../server/compliance/bsi-catalog-service';
|
|
import type { BsiCatalogState } from '../../src/compliance/bsi-catalog-management';
|
|
import { LocalAssessorRepository } from '../../src/persistence/local-assessor-repository';
|
|
|
|
// Data Hub `Update from BSI` routes; the service is a stub with synthetic states.
|
|
const bundled: BsiCatalogState = { version: 1, origin: 'bundled', catalogVersion: '2026-01-01T00:00:00+00:00', crosswalkVersion: '0.1-synthetic',
|
|
requirementCount: 2, crosswalkEntryCount: 1, catalogSha256: 'a'.repeat(64), crosswalkSha256: 'b'.repeat(64), missingTargets: [] };
|
|
const updated: BsiCatalogState = { ...bundled, origin: 'bsi', updatedAt: '2026-10-08T12:00:00.000Z', catalogVersion: '2099-01-01T00:00:00+00:00' };
|
|
|
|
describe('BSI catalog routes', () => {
|
|
let directory: string;
|
|
let server: Server | undefined;
|
|
let baseUrl: string;
|
|
const service = { current: vi.fn(), refresh: vi.fn(), reset: vi.fn() };
|
|
|
|
const start = async (bsiCatalogService?: Pick<BsiCatalogService, 'current' | 'refresh' | 'reset'>) => {
|
|
server = createAssessorHttpServer({ repository: new LocalAssessorRepository(), staticRoot: directory, bsiCatalogService });
|
|
await new Promise<void>((resolve, reject) => { server!.once('error', reject); server!.listen(0, '127.0.0.1', resolve); });
|
|
baseUrl = `http://127.0.0.1:${(server!.address() as AddressInfo).port}/api/v1/reference-data/bsi-grundschutz`;
|
|
};
|
|
const call = (suffix = '', method = 'GET') => fetch(`${baseUrl}${suffix}`, method === 'GET' ? {} : { method, headers: { 'Content-Type': 'application/json', 'X-Assessor-Request': '1', Origin: new URL(baseUrl).origin }, body: '{}' });
|
|
|
|
beforeEach(async () => {
|
|
directory = await mkdtemp(join(tmpdir(), 'assessor-bsi-http-'));
|
|
await writeFile(join(directory, 'index.html'), '<main>synthetic app</main>');
|
|
service.current.mockResolvedValue(bundled);
|
|
service.refresh.mockResolvedValue({ ...updated, updated: true });
|
|
service.reset.mockResolvedValue(bundled);
|
|
});
|
|
afterEach(async () => {
|
|
if (server) await new Promise<void>((resolve, reject) => server!.close(error => error ? reject(error) : resolve()));
|
|
server = undefined;
|
|
await rm(directory, { recursive: true, force: true });
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('reads, updates and resets the active catalog', async () => {
|
|
await start(service as never);
|
|
const read = await call();
|
|
expect(read.status).toBe(200);
|
|
expect(await read.json()).toEqual(bundled);
|
|
const refreshed = await call('/refresh', 'POST');
|
|
expect(refreshed.status).toBe(200);
|
|
expect(await refreshed.json()).toEqual({ ...updated, updated: true });
|
|
const reset = await call('/reset', 'POST');
|
|
expect(await reset.json()).toEqual(bundled);
|
|
expect([service.current.mock.calls.length, service.refresh.mock.calls.length, service.reset.mock.calls.length]).toEqual([1, 1, 1]);
|
|
});
|
|
|
|
it('reports a BSI failure as a bad gateway with its code and an unconfigured service as unavailable', async () => {
|
|
service.refresh.mockRejectedValue(new BsiCatalogError('bsi_unreachable', 'BSI could not be reached. Please try again later.'));
|
|
await start(service as never);
|
|
const failed = await call('/refresh', 'POST');
|
|
expect(failed.status).toBe(502);
|
|
expect(await failed.json()).toMatchObject({ error: { code: 'bsi_unreachable', message: 'BSI could not be reached. Please try again later.' } });
|
|
await new Promise<void>((resolve, reject) => server!.close(error => error ? reject(error) : resolve()));
|
|
await start();
|
|
const unavailable = await call();
|
|
expect(unavailable.status).toBe(503);
|
|
expect(await unavailable.json()).toMatchObject({ error: { code: 'bsi_catalog_unavailable' } });
|
|
});
|
|
});
|