import { expect, it } from 'vitest'; import { LocalAssessorRepository } from '../../src/persistence/local-assessor-repository'; import { ReferenceRecognitionService } from '../../server/reference-data/reference-recognition-service'; import { bundledReferenceCatalogs, referenceSnapshot } from '../../server/reference-data/reference-catalogs'; import { seedReferenceScan } from '../fixtures/reference-recognition'; import { canonicalModelToDataset } from '../../src/features/project-import/canonical-model-to-dataset'; import { buildFindingPathItems, defaultMitreGraphData } from '../../src/features/mitre-graph/mitre-graph'; it('reprocesses retained fields, persists provenance and freezes catalog mappings until explicitly reprocessed', async () => { const repository = new LocalAssessorRepository(); const { project, assessment, model } = await seedReferenceScan(repository); let catalogs = bundledReferenceCatalogs; const service = new ReferenceRecognitionService(repository, async () => catalogs); expect(await service.status(project.id)).toMatchObject({ totalScans: 1, indexedScans: 0, pendingScans: 1 }); expect(await service.reprocess(project.id)).toEqual({ processedScans: 1, unchangedScans: 0, references: 4 }); const revision = repository.revision; expect(await service.reprocess(project.id)).toMatchObject({ processedScans: 0, unchangedScans: 1 }); expect(repository.revision).toBe(revision); const stored = (await repository.loadAssessmentModel(assessment.id))!; expect(stored.findingOccurrences).toEqual(model.findingOccurrences); expect(stored.findingDefinitions).toEqual(model.findingDefinitions); expect(stored.reviews).toEqual(model.reviews); const record = canonicalModelToDataset(stored)!.sheets[0].analysis.records[0]; const path = buildFindingPathItems([record], defaultMitreGraphData)[0]; expect(path.techniqueIds).toEqual(['T1059.001', 'T1003']); expect(path.defenses).toEqual(['D3-CH · Credential Hardening']); expect(path.mappingOrigin).toBe('native'); expect(record.referenceRecognition?.references[0].catalogSha256).toBe(catalogs[0].sha256); const { sha256: _, ...attack } = catalogs[0]; catalogs = [referenceSnapshot({ ...attack, version: '99.0', entries: attack.entries.map(entry => entry.id === 'T1059.001' ? { ...entry, tactics: ['Synthetic future tactic'] } : entry) }), catalogs[1]]; expect(await service.status(project.id)).toMatchObject({ indexedScans: 1, pendingScans: 1 }); expect(buildFindingPathItems([record], { ...defaultMitreGraphData, techniques: [] })[0].tactics).toContain('Execution'); await service.reprocess(project.id); expect(await repository.listReferenceRuns(project.id)).toHaveLength(2); const exported = await repository.exportProject(project.id); expect(exported.referenceCatalogs).toHaveLength(3); expect(exported.referenceRuns).toHaveLength(2); expect(exported.referenceRuns[0]).toEqual(stored.referenceRuns![0]); expect(await repository.listReferenceRuns('another-client')).toEqual([]); expect(await repository.listReferenceCatalogs('another-client')).toEqual([]); await expect(repository.saveReferenceRun({ ...stored.referenceRuns![0], id: 'foreign', projectId: 'another-client' }, bundledReferenceCatalogs)).rejects.toThrow(); await expect(repository.saveReferenceRun({ ...stored.referenceRuns![0], inputSha256: 'b'.repeat(64) }, bundledReferenceCatalogs)).rejects.toThrow('unveränderlich'); await repository.deleteProject(project.id, { projectId: project.id, cascade: true }); expect(await repository.listReferenceRuns(project.id)).toEqual([]); expect(await repository.listReferenceCatalogs(project.id)).toEqual([]); });