Assessor_2.0/tests/recognition/recognition-engine.test.ts

72 lines
3.3 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { parseCsvDocument } from '../../src/importers/csv-importer';
import { applyRecognitionOverrides, recognizeImportDocument } from '../../src/recognition/recognition-engine';
import { sourceMetadata } from '../importers/import-source-fixtures';
describe('recognition and mapping engine', () => {
it('maps semantic field roles with confidence and reasons', () => {
const text = [
'ID;Title;Severity;Result;Description;ResultDetail;HelpUrl',
'MT.1001;Require multifactor authentication;High;Failed;A long description;Policy evidence;https://maester.dev/tests/MT.1001'
].join('\n');
const document = parseCsvDocument(sourceMetadata('TestResults.csv', text.length, 'text/csv'), text);
const recognition = recognizeImportDocument(document);
const roles = Object.fromEntries(recognition.fields.map(field => [field.sourceHeader, field]));
expect(roles.ID.appliedRole).toBe('rule-id');
expect(roles.Title.appliedRole).toBe('title');
expect(roles.Severity.appliedRole).toBe('severity');
expect(roles.Result.appliedRole).toBe('status');
expect(roles.Description.appliedRole).toBe('description');
expect(roles.ResultDetail.appliedRole).toBe('evidence');
expect(roles.HelpUrl.appliedRole).toBe('framework-reference');
expect(roles.Severity.confidence.level).toBe('high');
expect(roles.Severity.confidence.reasons.length).toBeGreaterThan(0);
expect(recognition.tables[0].selected).toBe(true);
});
it('keeps ambiguous fields as additional data until they are confirmed', () => {
const text = 'Object;Mystery\nserver01;unchanged';
const document = parseCsvDocument(sourceMetadata('unknown-table.csv', text.length), text);
const recognition = recognizeImportDocument(document);
const objectField = recognition.fields.find(field => field.sourceHeader === 'Object');
expect(recognition.source.selected.toolId).toBe('unknown');
expect(objectField).toMatchObject({
proposedRole: 'asset',
appliedRole: 'additional',
requiresConfirmation: true,
decisionSource: 'automatic'
});
expect(document.tables[0].rows[1].cells.map(cell => cell.decodedValue)).toEqual(['server01', 'unchanged']);
});
it('applies explicit source, table and field corrections without changing imported rows', () => {
const text = 'Object;Mystery\nserver01;unchanged';
const document = parseCsvDocument(sourceMetadata('unknown-table.csv', text.length), text);
const recognition = recognizeImportDocument(document);
const corrected = applyRecognitionOverrides(recognition, {
sourceTool: { toolId: 'internal-scanner', toolName: 'Internal Scanner' },
tableSelections: { [document.tables[0].id]: true },
fieldRoles: [{ tableId: document.tables[0].id, columnIndex: 0, role: 'title' }]
});
expect(corrected.source).toMatchObject({
decisionSource: 'user-confirmed',
requiresConfirmation: false,
selected: { toolId: 'internal-scanner', toolName: 'Internal Scanner' }
});
expect(corrected.tables[0]).toMatchObject({ selected: true, decisionSource: 'user-confirmed' });
expect(corrected.fields[0]).toMatchObject({
proposedRole: 'title',
appliedRole: 'title',
decisionSource: 'user-confirmed',
requiresConfirmation: false
});
expect(document.tables[0].rows).toHaveLength(2);
});
});