Assessor_2.0/tests/importers/maester-coverage.test.ts

138 lines
11 KiB
TypeScript

// @vitest-environment jsdom
import { describe, expect, it } from 'vitest';
import * as XLSX from 'xlsx';
import literalFixture from '../../fixtures/maester/object-literal.synthetic.html?raw';
import { importFile } from '../../src/importers/importer';
import { importSource } from '../../src/importers/engine';
import { importProfiles } from '../../src/profiles/profile-catalog';
import { nodeImportRuntimeAdapters } from '../../server/node-import-runtime-adapters';
import { previewEntryFromDataset, confirmEntryMappings } from '../../src/features/import-workflow/import-workflow';
import { createCanonicalImportBatch } from '../../src/importers/canonical-import';
import { assertCanonicalAssessmentModel } from '../../src/domain/canonical-validation';
import { canonicalModelToDataset } from '../../src/features/project-import/canonical-model-to-dataset';
import { resultMessageFor, resultStatusFieldFor } from '../../src/features/linked-results/result-message';
import { processingReadiness } from '../../src/domain/processing-compatibility';
import { mapFindingCandidates } from '../../src/importers/candidate-mapping';
import { maesterProfile } from '../../src/profiles/maester/maester-profile';
import type { SourceExtractionResult } from '../../src/domain/source-extraction';
const htmlImport = (html: string) => importFile(new File([html], 'ReportTemplate.html', { type: 'text/html' }), { profiles: importProfiles });
function canonical(dataset: Awaited<ReturnType<typeof htmlImport>>) {
const entry = previewEntryFromDataset(dataset, { sha256: 'a'.repeat(64), fileName: dataset.fileName, sizeBytes: 1000, lastModified: 0 });
const model = createCanonicalImportBatch('maester-coverage', [confirmEntryMappings(entry, importProfiles)], '2026-09-09T08:00:00Z').model;
assertCanonicalAssessmentModel(model);
return model;
}
describe('Maester variant and context coverage', () => {
it('imports all literal tests through the browser and server without running the report', async () => {
delete (globalThis as Record<string, unknown>).__maesterActiveContentExecuted;
const bytes = new TextEncoder().encode(literalFixture);
const results = [await htmlImport(literalFixture), await importSource({ metadata: { name: 'ReportTemplate.html', mediaType: 'text/html', lastModified: 0, sizeBytes: bytes.length }, bytes }, { profiles: importProfiles }, nodeImportRuntimeAdapters)];
for (const imported of results) {
expect(imported.profileId).toBe('maester');
expect(imported.recognition?.source.requiresConfirmation).toBe(false);
expect(imported.extraction?.candidates).toHaveLength(6);
expect(imported.extraction?.processing.status).toBe('partial');
expect(imported.extraction?.diagnostics.filter(item => item.code === 'maester-count-mismatch').map(item => item.details)).toEqual([
{ objectPath: '$.TotalCount', counter: 'TotalCount', declared: 5, actual: 6 },
{ objectPath: '$.FailedCount', counter: 'FailedCount', declared: 0, actual: 1 }
]);
const model = canonical(imported);
expect(model.findingOccurrences.map(item => item.status.normalizedValue)).toEqual(['failed', 'investigate', 'passed', 'not-run', 'not-run', 'error']);
expect(model.findingOccurrences.map(item => item.status.originalValue)).toEqual(['Failed', 'Investigate', 'Passed', 'Skipped', 'NotRun', 'Error']);
expect(model.findingOccurrences[0].originalFields).toContainEqual(expect.objectContaining({ label: 'FutureField', value: 0, path: '$.Tests[0].FutureField' }));
expect(model.findingOccurrences[0].originalFields).toContainEqual(expect.objectContaining({ label: 'ResultDetail.FutureDetail', value: { retained: true }, path: '$.Tests[0].ResultDetail.FutureDetail' }));
expect(model.evidence.filter(item => item.sourceFieldKeys.includes('TestResult'))).toHaveLength(3);
expect(model.environmentObservations).toContainEqual(expect.objectContaining({ property: 'entra.tenant_id', normalizedValue: 'synthetic-tenant-id', source: expect.objectContaining({ objectPath: '$.TenantId' }) }));
expect(model.environmentObservations).toContainEqual(expect.objectContaining({ property: 'maester.source.SystemInfo', originalValue: { OS: 'Synthetic OS', Available: true, Count: 0, Nullable: null } }));
expect(model.environmentObservations).toContainEqual(expect.objectContaining({ property: 'maester.tests.declared_total', originalValue: 5 }));
expect(model.environmentObservations).toContainEqual(expect.objectContaining({ property: 'maester.tests.actual_total', normalizedValue: 6, attributes: expect.objectContaining({ derived: true }) }));
expect(model.environmentObservations).toContainEqual(expect.objectContaining({ property: 'maester.source.LoadedModules', originalValue: [], state: 'empty' }));
const projection = canonicalModelToDataset(model)!;
const failed = projection.sheets.flatMap(sheet => sheet.analysis.records).find(record => record.fields.some(field => field.label === 'ID' && field.value === 'MT.9901'))!;
expect(resultMessageFor(failed, 'maester')).toBe('<p>Synthetic evidence</p>');
expect(resultStatusFieldFor(failed, 'maester')?.value).toBe('Failed');
}
expect((globalThis as Record<string, unknown>).__maesterActiveContentExecuted).toBeUndefined();
});
it('recognizes a validated report beyond the raw-text detection sample', async () => {
const source = literalFixture.replace('<script>', '<script>/*' + 'x'.repeat(300_000) + '*/');
const imported = await htmlImport(source.replace('<title>Maester Security Tests</title>', '<title>Report</title>'));
expect(imported.profileId).toBe('maester');
expect(imported.recognition?.source.selected.evidence).toContainEqual(expect.objectContaining({ signal: 'source-extractor' }));
});
it('maps Excel Result as evidence regardless of column order and preserves every cell', async () => {
const rows = [['ID', 'Result', 'Title', 'Severity', 'Status', 'Notes'],
['MT.9910', 'Synthetic result text', 'Synthetic Excel test', 'High', 'Failed', 'Synthetic note'],
['MT.9911', 'Passed', 'Synthetic second test', 'Low', 'Investigate', '']];
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet(rows), 'Tests');
const imported = await importFile(new File([XLSX.write(workbook, { type: 'array', bookType: 'xlsx' })], 'Maester.xlsx'), { profiles: importProfiles });
expect(imported.recognition?.fields.find(field => field.columnIndex === 1)?.appliedRole).toBe('evidence');
const model = canonical(imported);
expect(model.findingOccurrences.map(item => item.status.normalizedValue)).toEqual(['failed', 'investigate']);
expect(model.findingOccurrences.map(item => item.originalFields.map(field => field.value))).toEqual(rows.slice(1));
expect(model.evidence.map(item => item.value)).toEqual(['Synthetic result text', 'Passed']);
const record = imported.sheets[0].analysis.records[0];
expect(resultMessageFor(record, 'maester')).toBe('Synthetic result text');
expect(resultStatusFieldFor(record, 'maester')?.value).toBe('Failed');
const investigate = imported.sheets[0].analysis.records[1];
expect(maesterProfile.matchesStatus(investigate, 'investigate')).toBe(true);
expect(maesterProfile.matchesStatus(investigate, 'passed')).toBe(false);
});
it('keeps native CSV Result as status when there is no separate Status column', async () => {
const imported = await importFile(new File(['ID,Title,Result,ResultDetail\nMT.9910,Synthetic,Failed,Synthetic proof'], 'Maester-TestResults.csv'), { profiles: importProfiles });
const model = canonical(imported);
expect(model.findingOccurrences[0].status.normalizedValue).toBe('failed');
expect(model.evidence[0].value).toBe('Synthetic proof');
const record = imported.sheets[0].analysis.records[0];
expect(resultMessageFor(record, 'maester')).toBe('Synthetic proof');
expect(resultStatusFieldFor(record, 'maester')?.value).toBe('Failed');
});
it.each([
'const testResults={Result:"Failed",Tests:loadResults(),EndOfJson:true};',
'const testResults={Result:"Failed",Tests:[{Id:"MT.1",Title:`${execute()}`}],EndOfJson:true};',
'const testResults={Result:"Failed",Tests:[{Id:"MT.1"}],get TenantId(){return execute()}};',
'const testResults=loadResults();',
'const testResults={Result:"Failed",Tests:[null],EndOfJson:true};',
'const testResults={Result:"Failed",Tests:[{Id:"MT.1"}],EndOfJson:true}.Tests;',
'const testResults={Result:"Failed",Tests:[{Id:"MT.1"}]};const data={Result:"Failed",Tests:[{Id:"MT.2"}]};'
])('rejects unsupported or invalid reports instead of importing zero/partial findings', async script => {
await expect(htmlImport(`<html><title>Maester Security Tests</title><body>Report<script>${script}</script></body></html>`)).rejects.toThrow(/Maester/);
});
it('keeps multi-tenant findings and report metadata source-scoped', async () => {
const payload = { WrapperVersion: 'synthetic', Tenants: [
{ TenantId: 'tenant-a', Result: 'Failed', Tests: [{ Id: 'MT.1', Title: 'Synthetic one', Result: 'Failed' }] },
{ TenantId: 'tenant-b', Result: 'Passed', Tests: [{ Id: 'MT.1', Title: 'Synthetic two', Result: 'Passed' }] }
] };
const imported = await htmlImport(`<html><script type="application/json">${JSON.stringify({ Tenants: payload.Tenants, WrapperVersion: payload.WrapperVersion })}</script></html>`);
const model = canonical(imported);
expect(model.findingOccurrences).toHaveLength(2);
expect(model.environmentObservations?.filter(item => item.property === 'entra.tenant_id').map(item => item.subject.identity)).toEqual(['tenant-a', 'tenant-b']);
expect(model.environmentObservations).toContainEqual(expect.objectContaining({ property: 'maester.source.WrapperVersion', originalValue: 'synthetic' }));
expect(model.findingOccurrences.map(item => item.sourcePosition?.objectPath)).toEqual(['$.Tenants[0].Tests[0]', '$.Tenants[1].Tests[0]']);
});
it('marks earlier imports for reimport without breaking their comparison key', () => {
expect(processingReadiness('maester', { profileVersion: '1.0.0' } as never)).toBe('reimport-recommended');
expect(processingReadiness('maester', { profileVersion: '1.1.0' } as never)).toBe('current');
});
it('keeps conditional decisions separate for different native candidate shapes', () => {
const native = (id: string, entries: string[][]) => ({ version: 1 as const, id, source: { fileName: 'synthetic' },
fields: entries.map(([label, value]) => ({ key: label, label, originalValue: value, decodedValue: value, source: { fileName: 'synthetic', objectPath: `${id}.${label}` } })) });
const extraction = { candidates: [native('a', [['Result', 'Failed']]), native('b', [['Result', 'Synthetic text'], ['Status', 'Passed']])],
extractor: { kind: 'source-extractor', id: 'synthetic', version: '1.0.0' } } as SourceExtractionResult;
const mapped = mapFindingCandidates(extraction, maesterProfile);
expect(mapped.candidates[0].fields[0].role).toBe('status');
expect(mapped.candidates[1].fields[0].role).toBe('evidence');
expect(mapped.candidates[0].fields[0].mappingDecisionId).not.toBe(mapped.candidates[1].fields[0].mappingDecisionId);
});
});