Assessor_2.0/tests/features/datahub-import-groups.test.ts

120 lines
4.5 KiB
TypeScript

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>): 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
});
});
});