import { describe, expect, it } from 'vitest'; import type { ImportedDataset } from '../../src/domain/dataset'; import type { ImportRecognitionResult } from '../../src/domain/import-recognition'; import { confirmEntryMappings, createBrowserFingerprintStore, createImportReport, enterImportPreview, preparePreviewEntriesForReview, previewEntryFromDataset, recognitionOverridesFromResult } from '../../src/features/import-workflow/import-workflow'; const recognition: ImportRecognitionResult = { version: 1, source: { selected: { toolId: 'generic', toolName: 'Generic', confidence: { level: 'low', score: 0.4, reasons: [] }, evidence: [] }, candidates: [], decisionSource: 'automatic', requiresConfirmation: true }, tables: [], fields: [] }; const dataset: ImportedDataset = { fileName: 'findings.csv', fileSize: 20, lastModified: 0, format: 'CSV', rawText: '', profileId: 'generic', scanTool: 'Generic', recognition, sheets: [{ id: 'table-1', name: 'Findings', rows: [['Title'], ['Issue']], raw: 'Title\nIssue', analysis: { headerIndex: 0, headers: ['Title'], records: [{ id: 'record-1', index: 1, title: 'Issue', fields: [], filled: [], meta: [] }], columns: [], signals: [], title: 'Findings' } }] }; const fingerprint = { sha256: 'a'.repeat(64), fileName: 'findings.csv', sizeBytes: 20, lastModified: 0 }; describe('import workflow', () => { it('summarizes recognition and clears explicit mapping confirmations', () => { const preview = previewEntryFromDataset(dataset, fingerprint); expect(preview).toMatchObject({ status: 'warning', selected: true, recordCount: 1, mappingsToConfirm: 1 }); const confirmed = confirmEntryMappings(preview); expect(confirmed.mappingsToConfirm).toBe(0); expect(confirmed.dataset?.recognition?.source.decisionSource).toBe('user-confirmed'); expect(recognitionOverridesFromResult(confirmed.dataset?.recognition)).toEqual({ sourceTool: { toolId: 'generic', toolName: 'Generic' }, tableSelections: undefined, fieldRoles: undefined }); }); it('persists fingerprints and reports repeated imports deterministically', () => { const values = new Map(); const storage = { getItem: (key: string) => values.get(key) ?? null, setItem: (key: string, value: string) => { values.set(key, value); } }; const store = createBrowserFingerprintStore(storage); store.record([fingerprint], '2026-07-17T08:00:00.000Z'); const repeated = store.record([fingerprint], '2026-07-17T09:00:00.000Z'); expect(repeated[0]).toMatchObject({ sha256: fingerprint.sha256, importCount: 2, firstImportedAt: '2026-07-17T08:00:00.000Z' }); const entry = previewEntryFromDataset(dataset, fingerprint, 'findings.csv'); const report = createImportReport({ version: 1, batchId: 'batch-1', createdAt: 'now', entries: [entry] }, new Set()); expect(report).toMatchObject({ importedFiles: 0, skippedFiles: 1, duplicateFiles: 1 }); }); it('keeps an expected duplicate selected in the targeted reimport review', () => { const duplicate = previewEntryFromDataset(dataset, fingerprint, 'findings.csv'); expect(duplicate).toMatchObject({ status: 'duplicate', selected: false }); expect(preparePreviewEntriesForReview([duplicate], { reimport: true, singleFile: true })) .toEqual([expect.objectContaining({ status: 'duplicate', selected: true })]); expect(preparePreviewEntriesForReview([duplicate], { reimport: false, singleFile: false })) .toEqual([expect.objectContaining({ status: 'duplicate', selected: false })]); }); it('preserves the target tenant while a confirmed preview is completed', () => { const preview = { version: 1 as const, batchId: 'batch-targeted', createdAt: 'now', entries: [] }; const state = enterImportPreview({ phase: 'preparing', targetProjectId: 'project-target' }, preview); expect(state).toEqual({ phase: 'preview', targetProjectId: 'project-target', preview, error: undefined }); }); it('keeps a possible duplicate selectable while displaying it as a warning', () => { const entry = previewEntryFromDataset(dataset, fingerprint, undefined, 'PingCastle vom 22.07.2026, 10:00:00'); expect(entry).toMatchObject({ status: 'warning', selected: true, possibleDuplicateOf: 'PingCastle vom 22.07.2026, 10:00:00' }); });});