60 lines
3.9 KiB
TypeScript
60 lines
3.9 KiB
TypeScript
import { renderToStaticMarkup } from 'react-dom/server';
|
||
import { describe, expect, it } from 'vitest';
|
||
import { RegulatoryMappingPanel } from '../../src/features/finding-reader/RegulatoryMappingPanel';
|
||
import type { RegulatoryMappingResponse } from '../../src/domain/regulatory-mapping';
|
||
|
||
const response: RegulatoryMappingResponse = {
|
||
version: 1, projectId: 'project-1', resultProjectionId: 'scanner-finding:occurrence-1', generationEnabled: true,
|
||
state: 'succeeded', decisions: [], run: {
|
||
version: 1, id: 'regulatory-mapping-abc', ruleKey: 'purple-knight:A-SmbSigning', sourceTool: 'purple-knight', sourceRuleId: 'A-SmbSigning',
|
||
status: 'succeeded', queryHash: 'a'.repeat(64),
|
||
preparedQuery: { schemaVersion: 1, query: 'Synthetic public query.', specificity: 'topic', publicTopics: ['Synthetic topic'] },
|
||
corpusId: 'iso-test', corpusVersion: 'v2', sourceHashes: ['b'.repeat(64)], embeddingModel: 'bge-m3',
|
||
retrievalVersion: 'test', promptVersion: 'test', outputSchemaVersion: 1, model: 'work-model',
|
||
createdAt: '2026-09-15T10:00:00.000Z', updatedAt: '2026-09-15T10:00:00.000Z',
|
||
candidates: [{
|
||
controlId: '5.1', annexAControlId: 'A.5.1', title: 'Synthetic title', score: 0.91,
|
||
evidence: [{ chunkId: 'chunk-1', textSha256: 'c'.repeat(64), sourceSha256: 'b'.repeat(64), sourceStandard: 'ISO/IEC 27002',
|
||
sectionKind: 'purpose', pageStart: 12, pageEnd: 13, text: 'Synthetic evidence text.' }]
|
||
}, {
|
||
controlId: '5.2', annexAControlId: 'A.5.2', title: 'Other synthetic title', score: 0.72,
|
||
evidence: [{ chunkId: 'chunk-2', textSha256: 'd'.repeat(64), sourceSha256: 'b'.repeat(64), sourceStandard: 'ISO/IEC 27001',
|
||
sectionKind: 'control', pageStart: 14, pageEnd: 14, text: 'Other synthetic evidence.' }]
|
||
}],
|
||
result: { schemaVersion: 1, outcome: 'mapped', mappings: [{ controlId: '5.1', relationship: 'primary', confidence: 'high',
|
||
rationale: 'Synthetic rationale.', evidenceChunkIds: ['chunk-1'] }] }
|
||
}
|
||
};
|
||
|
||
describe('regulatory mapping panel', () => {
|
||
it('renders proposed controls before collapsed other candidates with page-referenced evidence and review actions', () => {
|
||
const html = renderToStaticMarkup(<RegulatoryMappingPanel response={response} loading={false} error="" onStart={() => {}} onDecision={() => {}}/>);
|
||
expect(html.indexOf('A.5.1 — Synthetic title')).toBeLessThan(html.indexOf('Other retrieved controls'));
|
||
expect(html).not.toContain('5.1 · A.5.1');
|
||
expect(html).toContain('primary · high confidence');
|
||
expect(html).toContain('pages 12–13');
|
||
expect(html).toContain('ISO/IEC 27002 · purpose');
|
||
expect(html).not.toContain('Synthetic evidence text.');
|
||
expect(html).toContain('Accept');
|
||
expect(html).toContain('Reject');
|
||
expect(html).toContain('not proof of compliance');
|
||
});
|
||
|
||
it('surfaces the structural failure detail alongside the safe error code', () => {
|
||
const failed: RegulatoryMappingResponse = {
|
||
...response, state: 'failed',
|
||
run: { ...response.run!, status: 'failed', result: undefined,
|
||
errorCode: 'regulatory_mapping_invalid_output', errorDetail: 'mappings[0].evidenceChunkIds[0] is not grounded in the supplied control.' }
|
||
};
|
||
const html = renderToStaticMarkup(<RegulatoryMappingPanel response={failed} loading={false} error="" onStart={() => {}} onDecision={() => {}}/>);
|
||
expect(html).toContain('regulatory_mapping_invalid_output');
|
||
expect(html).toContain('is not grounded in the supplied control.');
|
||
});
|
||
|
||
it('shows a concrete disabled reason without exposing a generation action', () => {
|
||
const html = renderToStaticMarkup(<RegulatoryMappingPanel response={{ ...response, generationEnabled: false, state: 'unsupported', disabledReason: 'Qdrant is unavailable.', run: undefined }} loading={false} error="" onStart={() => {}} onDecision={() => {}}/>);
|
||
expect(html).toContain('Qdrant is unavailable.');
|
||
expect(html).not.toContain('Generate proposals');
|
||
});
|
||
});
|