Assessor_2.0/tests/persistence/technical-explanation-resul...

80 lines
6.6 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { FindingOccurrence, ScanRun } from '../../src/domain/canonical-model';
import { discloseResultDbProjectionWithTrace } from '../../src/domain/result-db-disclosure';
import { technicalExplanation, type UnifiedResultProjection } from '../../src/domain/unified-result';
import { buildRegulatoryFindingQuery } from '../../src/compliance/regulatory-query';
import { LocalAssessorRepository } from '../../src/persistence/local-assessor-repository';
// A scanner's technical explanation (PingCastle today) is public rule guidance. It enters the Result DB
// as its own field, redacted as strictly as a description, guarded against client names and traced;
// tools without one leave it empty, and unknown sources stay fail-closed.
const stamp = '2026-10-05T08:00:00.000Z';
const confidence = { level: 'high' as const, score: 1, reasons: ['synthetic'] };
const provenance = { decisionSource: 'source-native' as const, confidence, mappingVersion: '1.0.0' };
const explanation = 'The AdminSDHolder ACL is copied to protected accounts every 60 minutes; Dummy Customer changed it on 2026-01-03.';
describe('technical explanation field', () => {
it('reads the extractor key or the source label and ignores other or empty values', () => {
expect(technicalExplanation([{ key: 'technical-explanation', label: 'Technical explanation', value: ' Native text. ' }])).toBe('Native text.');
expect(technicalExplanation([{ key: 'column-3', label: 'Technical Explanation', value: 'Table text.' }])).toBe('Table text.');
expect(technicalExplanation([{ key: 'description', label: 'Description', value: 'Other text.' }])).toBeUndefined();
expect(technicalExplanation([{ key: 'technical-explanation', label: 'Technical explanation', value: ' ' }])).toBeUndefined();
expect(technicalExplanation([{ key: 'technical-explanation', label: 'Technical explanation', value: null }])).toBeUndefined();
});
it('is redacted like a description, traced, and withheld for unknown or GPO sources', () => {
const row = { resultProjectionId: 'scanner-finding:dummy', sourceKind: 'scanner-finding', sourceResultId: 'dummy', projectId: 'dummy-project',
collectionRunId: 'dummy-run', sourceTool: 'pingcastle', sourceRuleId: 'P-AdminSDHolder', title: 'AdminSDHolder ACL modified',
technicalExplanation: explanation, severityOriginal: 'high', severityNormalized: 'high', outcomeOriginal: 'fail', outcomeNormalized: 'fail',
technicalExecutionState: 'completed', resultType: 'compliance_check', isIoe: false, isDeviation: true, lifecycleState: 'new',
mappingConfidence: 1, importedAt: stamp, createdAt: stamp, frameworkReferences: [], categories: [] } as unknown as UnifiedResultProjection;
const disclosed = discloseResultDbProjectionWithTrace(row);
expect(disclosed.technicalExplanation).toContain('AdminSDHolder ACL is copied to protected accounts');
expect(disclosed.technicalExplanation).not.toContain('60');
expect(disclosed.technicalExplanation).not.toContain('2026-01-03');
expect(disclosed.disclosureTrace?.redactions.some(entry => entry.field === 'technicalExplanation')).toBe(true);
expect(JSON.stringify(disclosed.disclosureTrace)).not.toContain('AdminSDHolder');
expect(discloseResultDbProjectionWithTrace({ ...row, sourceTool: 'unknown-scanner' }).technicalExplanation).toBeUndefined();
expect(discloseResultDbProjectionWithTrace({ ...row, sourceKind: 'policy-evaluation' } as UnifiedResultProjection).technicalExplanation).toBeUndefined();
});
it('reaches the stored Result DB row of a PingCastle finding with the client name withheld, and the ISO query', async () => {
const repository = new LocalAssessorRepository();
await repository.saveProject({ id: 'dummy-project', name: 'Dummy Customer', createdAt: stamp, updatedAt: stamp, attributes: {} });
await repository.saveAssessment({ id: 'dummy-assessment', projectId: 'dummy-project', name: 'Dummy', createdAt: stamp, updatedAt: stamp });
const scanRun: ScanRun = { id: 'dummy-run', assessmentId: 'dummy-assessment', sequence: 1, label: 'Dummy',
tool: { id: 'pingcastle', name: 'PingCastle' }, importedAt: stamp };
const occurrence: FindingOccurrence = { id: 'dummy-occurrence', definitionId: 'dummy-definition', scanRunId: scanRun.id, importFileId: 'dummy-file',
sourceIdentity: { tool: scanRun.tool, originalRuleId: 'P-AdminSDHolder', canonicalRuleId: 'P-AdminSDHolder', originalTitle: 'AdminSDHolder ACL modified' },
sourcePosition: { rowIndex: 1 },
originalFields: [{ key: 'technical-explanation', label: 'Technical explanation', value: explanation }],
fieldMappings: [], severity: { originalValue: 'High', normalizedValue: 'high', provenance },
status: { originalValue: 'Matched', normalizedValue: 'failed', provenance } };
const session = await repository.beginImport({ sessionId: 'dummy-session', projectId: 'dummy-project', assessmentId: 'dummy-assessment', scanRun });
await session.stage({
importFiles: [{ id: 'dummy-file', scanRunId: scanRun.id, fileName: 'dummy.html', format: 'HTML', sizeBytes: 10, importedAt: stamp,
detectedTool: scanRun.tool, detection: confidence,
processedWith: { parserId: 'html', parserVersion: '1.0.0', profileId: 'pingcastle', profileVersion: '1.0.0', mappingVersion: '1.0.0' },
sourceArtifact: { kind: 'not-retained', reason: 'synthetic' } }],
findingDefinitions: [{ id: 'dummy-definition', canonicalKey: 'pingcastle:P-AdminSDHolder',
identity: { version: 1, strategy: 'tool-rule-id', durable: true, evidence: ['synthetic'] },
title: 'AdminSDHolder ACL modified', category: 'Privileged Accounts', frameworkReferences: [], createdAt: stamp, updatedAt: stamp }],
findingOccurrences: [occurrence]
});
await session.commit();
const stored = await repository.getUnifiedResultById('scanner-finding:dummy-occurrence', 'dummy-project');
expect(stored?.technicalExplanation).toContain('AdminSDHolder ACL is copied to protected accounts');
expect(stored?.technicalExplanation).not.toContain('Dummy Customer');
expect(stored?.technicalExplanation).not.toContain('2026-01-03');
// Internal analysis keeps the complete original.
const internal = await repository.queryUnifiedResults({ audience: 'internal-analysis', projectId: 'dummy-project' });
expect(internal.items[0].technicalExplanation).toBe(explanation);
// The ISO query reads the disclosed row, so it carries the redacted technical explanation.
const query = buildRegulatoryFindingQuery(stored!)!.query;
expect(query).toContain('Technical explanation: The AdminSDHolder ACL is copied to protected accounts');
expect(query).not.toContain('Dummy Customer');
});
});