Assessor_2.0/tests/compliance/iso-pdf-corpus.test.ts

51 lines
2.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
parseIso27002Controls,
validateIsoControlSets,
type ParsedControl,
type ParsedPdf,
type PdfLine
} from '../../server/compliance/iso-pdf-corpus';
const line = (text: string, page: number): PdfLine => ({ text, page, y: 500, spans: [{ text, page, x: 100, y: 500, width: text.length * 5, height: 10 }] });
function controls(): ParsedControl[] {
const counts = [[5, 37], [6, 8], [7, 14], [8, 34]];
return counts.flatMap(([theme, count]) => Array.from({ length: count }, (_, index) => ({
controlId: `${theme}.${index + 1}`,
title: `Synthetic ${theme}.${index + 1}`,
theme: theme === 5 ? 'organizational' as const : theme === 6 ? 'people' as const : theme === 7 ? 'physical' as const : 'technological' as const,
sections: [{ kind: 'control' as const, text: 'Synthetic statement.', pageStart: 1, pageEnd: 1 }]
})));
}
describe('ISO PDF corpus structure parser', () => {
it('keeps sections inside one control, preserves umlauts, and spans multi-page guidance', () => {
const pdf: ParsedPdf = { sha256: 'a'.repeat(64), pageCount: 2, pages: [
{ page: 1, lines: [
line('5.1 Informationssicherheitsrichtlinien', 1), line('Maßnahmenart', 1), line('präventiv', 1),
line('Maßnahme', 1), line('Synthetische Maßnahme.', 1), line('Zweck', 1), line('Synthetischer Zweck.', 1),
line('Anleitung', 1), line('Mehrseitige Anleitung mit Umlauten äöü.', 1)
] },
{ page: 2, lines: [line('DIN EN ISO/IEC 27002:2024-01', 2), line('Fortsetzung der Anleitung.', 2),
line('Weitere Informationen', 2), line('Synthetische Referenz.', 2)] }
] };
const parsed = parseIso27002Controls(pdf);
expect(parsed).toHaveLength(1);
expect(parsed[0].sections.map(section => section.kind)).toEqual(['attributes', 'control', 'purpose', 'guidance', 'other-information']);
expect(parsed[0].sections.find(section => section.kind === 'guidance')).toMatchObject({ pageStart: 1, pageEnd: 2 });
expect(parsed[0].sections.find(section => section.kind === 'guidance')?.text).toContain('äöü');
expect(parsed[0].sections.some(section => section.text.includes('DIN EN'))).toBe(false);
});
it('rejects incomplete, duplicate, and mismatched 93-control catalogs', () => {
const reference = controls();
const semantic = controls();
expect(() => validateIsoControlSets(reference, semantic)).not.toThrow();
expect(() => validateIsoControlSets(reference.slice(1), semantic)).toThrow(/exactly 93/);
expect(() => validateIsoControlSets([...reference.slice(0, -1), reference[0]], semantic)).toThrow(/unique controls/);
expect(() => validateIsoControlSets(reference, [...semantic.slice(0, -1), { ...semantic.at(-1)!, controlId: '8.35' }])).toThrow(/identifiers do not match/);
});
});