import { describe, expect, it } from 'vitest'; import type { GpoBackupStatusRecord } from '../../src/api/assessor-api-contract'; import type { Assessment, ImportFileRecord, ScanRun } from '../../src/domain/canonical-model'; import { groupProjectImportFiles } from '../../src/features/project-import/datahub-import-groups'; function file(overrides: Partial): ImportFileRecord { return { id: 'file', scanRunId: 'run-1', fileName: 'report.csv', format: 'CSV', sizeBytes: 10, importedAt: '2026-07-18T08:00:00.000Z', detectedTool: { id: 'generic', name: 'Tabular Data' }, detection: { level: 'unknown', score: 0, reasons: [] }, processedWith: { parserId: 'csv', parserVersion: '1', profileId: 'generic', profileVersion: '1', mappingVersion: '1' }, sourceArtifact: { kind: 'not-retained', reason: 'test' }, ...overrides }; } function assessment(id: string, effectiveAt?: string): Assessment { return { id, projectId: 'project', name: id, effectiveAt, createdAt: '2026-08-04T08:00:00.000Z', updatedAt: '2026-08-04T08:00:00.000Z' }; } function run(id: string, assessmentId: string): ScanRun { return { id, assessmentId, sequence: 1, label: id, tool: { id: 'multiple', name: 'Assessment source' }, importedAt: '2026-08-04T08:00:00.000Z' }; } const gpo: GpoBackupStatusRecord = { version: 1, artifactId: 'gpo-1', fileName: 'gpo.zip', sizeBytes: 50, submittedAt: '2026-08-04T08:00:00.000Z', updatedAt: '2026-08-04T08:00:00.000Z', state: 'partial', attempts: 1, maxAttempts: 3, gpoCount: 3, backupInstanceCount: 3 }; describe('Datahub assessment timeslot grouping', () => { it('shows one source entry for a multi-file scanner batch', () => { const groups = groupProjectImportFiles([ file({ id: 'summary', fileName: 'Assessment_summary.csv' }), file({ id: 'findings', fileName: 'Indicators_results.csv', sizeBytes: 30, detectedTool: { id: 'purple-knight', name: 'PurpleKnight' } }), file({ id: 'detail', fileName: 'SI000134.csv', sizeBytes: 20 }) ]); expect(groups).toHaveLength(1); expect(groups[0]).toMatchObject({ scanRunIds: ['run-1'], fileCount: 3, sourceCount: 1, sizeBytes: 60, tool: { id: 'purple-knight', name: 'PurpleKnight' }, formats: ['CSV'] }); }); it('keeps genuinely different assessment dates as separate timeslots', () => { const groups = groupProjectImportFiles([ file({ id: 'old', scanRunId: 'run-old', lastModifiedAt: '2026-07-01T08:00:00.000Z' }), file({ id: 'new', scanRunId: 'run-new', lastModifiedAt: '2026-07-12T08:00:00.000Z' }) ]); expect(groups.map(group => group.id)).toEqual(['timeslot-2026-07-12', 'timeslot-2026-07-01']); }); it('coalesces CIS-CAT, PingCastle, PurpleKnight, Maester and GPO into the one 22.07 timeslot', () => { const groups = groupProjectImportFiles([ file({ id: 'cis', scanRunId: 'run-cis', detectedTool: { id: 'cis-cat', name: 'CIS-CAT' }, lastModifiedAt: '2026-07-22T08:00:00.000Z' }), file({ id: 'ping', scanRunId: 'run-ping', detectedTool: { id: 'pingcastle', name: 'PingCastle' }, lastModifiedAt: '2026-07-22T08:00:02.000Z' }), file({ id: 'purple', scanRunId: 'run-purple', detectedTool: { id: 'purple-knight', name: 'PurpleKnight' }, lastModifiedAt: '2026-07-22T08:00:03.000Z' }), file({ id: 'maester', scanRunId: 'run-maester', detectedTool: { id: 'maester', name: 'Maester' }, lastModifiedAt: '2026-07-22T08:00:04.000Z' }) ], [], [], [gpo]); expect(groups).toHaveLength(1); expect(groups[0]).toMatchObject({ id: 'timeslot-2026-07-22', fileCount: 4, sourceCount: 5, sizeBytes: 90, tool: { id: 'multiple', name: 'Multiple Tools' } }); expect(groups[0].scanRunIds).toEqual(['run-cis', 'run-ping', 'run-purple', 'run-maester']); expect(groups[0].gpoBackups).toHaveLength(1); }); it('uses an explicit timeslot date instead of the later upload timestamp', () => { const slot = assessment('slot-1', '2026-07-22T12:00:00.000Z'); const scanRun = run('run-late-upload', slot.id); const groups = groupProjectImportFiles([ file({ id: 'late', scanRunId: scanRun.id, lastModifiedAt: '2026-08-04T08:00:00.000Z' }) ], [slot], [scanRun], [{ ...gpo, assessmentId: slot.id }]); expect(groups).toHaveLength(1); expect(groups[0]).toMatchObject({ id: 'timeslot-2026-07-22', assessmentIds: ['slot-1'], scanRunIds: ['run-late-upload'], sourceCount: 2 }); }); });