Assessor_2.0/tests/features/gpo-status-row.test.tsx

78 lines
5.1 KiB
TypeScript

import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { expect, it } from 'vitest';
import type { GpoBackupStatusRecord } from '../../src/api/assessor-api-contract';
import { GpoStatusRow } from '../../src/features/project-import/GpoStatusRow';
import { summarizeClientProcessingReadiness } from '../../src/features/project-import/datahub-processing-readiness';
const record: GpoBackupStatusRecord = {
version: 1, artifactId: 'synthetic-artifact', fileName: 'Synthetic.zip', sizeBytes: 12, submittedAt: '', updatedAt: '',
state: 'succeeded', attempts: 1, maxAttempts: 3, gpoCount: 1, backupInstanceCount: 1,
evaluationUpdates: [{ evaluationRunId: 'old', aggregationRunId: 'aggregation', baselineAnalysisIds: ['baseline'],
artifactIds: ['synthetic-artifact'], previousVersion: '1.3.3', currentVersion: '1.4.0',
reasons: ['engine-version'] }]
};
it('makes the client actionable and renders an explicit evaluation action, not an upload request', () => {
expect(summarizeClientProcessingReadiness([], [], [record])).toMatchObject({ state: 'update' });
const html = renderToStaticMarkup(createElement(GpoStatusRow, { projectId: 'synthetic-project', record, imported: 'Synthetic date', busy: false,
onEvaluate() {}, onReprocess() {}, onRetry() {}, onDelete() {} }));
expect(html).toContain('Re-evaluation required');
expect(html).toContain('>Re-evaluate</button>');
expect(html).not.toContain('>Ready<');
expect(html).toContain('no file upload is required');
expect(summarizeClientProcessingReadiness([], [], [{ ...record, evaluationUpdates: [] }])).toMatchObject({ state: 'ready' });
expect(summarizeClientProcessingReadiness([], [], [{ ...record, state: 'dead-letter' }])).toMatchObject({ state: 'error' });
});
it('shows partial GPO processing as a technical analysis limit without requesting manual review', () => {
const html = renderToStaticMarkup(createElement(GpoStatusRow, { projectId: 'synthetic-project',
record: { ...record, state: 'partial', evaluationUpdates: [] },
imported: 'Synthetic date', busy: false, onEvaluate() {}, onReprocess() {}, onRetry() {}, onDelete() {}
}));
expect(html).toContain('Limited analysis');
expect(html).not.toContain('review required');
expect(html).not.toContain('>Imported<');
});
it('offers the same re-evaluation when only the baseline analysis generation changed', () => {
const current: GpoBackupStatusRecord = { ...record, evaluationUpdates: [{
...record.evaluationUpdates![0], previousVersion: '1.4.0', currentVersion: '1.4.0',
baselineAnalysisIds: ['baseline-current'], reasons: ['baseline-analysis']
}] };
expect(summarizeClientProcessingReadiness([], [], [current])).toMatchObject({ state: 'update' });
const html = renderToStaticMarkup(createElement(GpoStatusRow, { projectId: 'synthetic-project', record: current, imported: 'Synthetic date',
busy: false, onEvaluate() {}, onReprocess() {}, onRetry() {}, onDelete() {} }));
expect(html).toContain('Re-evaluation required');
expect(html).toContain('Updated Microsoft baseline analysis');
expect(html).not.toContain('Engine 1.4.0');
expect(html).toContain('>Re-evaluate</button>');
});
it('asks for reprocessing, not re-evaluation, when the retained analysis uses an older analyzer', () => {
const outdated: GpoBackupStatusRecord = { ...record, evaluationUpdates: [], analysisUpdate: {
identityAnalysisRunId: 'analysis-1', expectedLatestProcessingRunId: 'processing-1',
previousAnalyzerVersion: 'gpo-backup-policy-analysis:2.2.3',
currentAnalyzerVersion: 'gpo-backup-policy-analysis:2.2.4'
} };
expect(summarizeClientProcessingReadiness([], [], [outdated])).toMatchObject({ state: 'update' });
const html = renderToStaticMarkup(createElement(GpoStatusRow, { projectId: 'synthetic-project', record: outdated, imported: 'Synthetic date',
busy: false, onEvaluate() {}, onReprocess() {}, onRetry() {}, onDelete() {} }));
expect(html).toContain('Reprocessing required');
expect(html).toContain('Analyzer gpo-backup-policy-analysis:2.2.3 → gpo-backup-policy-analysis:2.2.4');
// Reprocessing rebuilds retained parsing output, so it replaces the re-evaluation action.
expect(html).toContain('>Reprocess</button>');
expect(html).not.toContain('>Re-evaluate</button>');
expect(html).toContain('no file upload is required');
});
it('shows the number of GPOs without a link once link facts are retained', () => {
const withLinks: GpoBackupStatusRecord = { ...record, evaluationUpdates: [], gpoCount: 184, backupInstanceCount: 184, notLinkedGpoCount: 29 };
const html = renderToStaticMarkup(createElement(GpoStatusRow, { projectId: 'synthetic-project', record: withLinks, imported: 'Synthetic date',
busy: false, onEvaluate() {}, onReprocess() {}, onRetry() {}, onDelete() {} }));
expect(html).toContain('184 GPOs');
expect(html).toContain(' · 29 not linked');
const unknown = renderToStaticMarkup(createElement(GpoStatusRow, { projectId: 'synthetic-project', record: { ...withLinks, notLinkedGpoCount: undefined },
imported: 'Synthetic date', busy: false, onEvaluate() {}, onReprocess() {}, onRetry() {}, onDelete() {} }));
expect(unknown).not.toContain('not linked');
});