71 lines
3.9 KiB
TypeScript
71 lines
3.9 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { createAssessmentContext, reuseContextReads, type ContextReadPort } from '../../src/assessment-context/context-run';
|
|
import { buildAssessmentContext } from '../../src/assessment-context/context-engine';
|
|
import { contextGapReport } from '../../src/assessment-context/context-gaps';
|
|
import { RELEASED_ASSESSMENT_CONTEXT_CATALOG as catalog } from '../../src/assessment-context/context-catalog';
|
|
|
|
function source(): ContextReadPort {
|
|
return {
|
|
currentRevision: async () => 7,
|
|
queryUnifiedResults: async () => ({ items: [], total: 0 }),
|
|
listGpoPolicyAnalyses: async () => [], listReferenceRuns: async () => [],
|
|
getEnvironmentSnapshot: vi.fn() as never,
|
|
listPolicyEvaluations: vi.fn(async () => []),
|
|
listMicrosoftSecurityBaselineAnalyses: vi.fn(async () => []),
|
|
listAssessments: vi.fn(async () => []), loadAssessmentModel: vi.fn(async () => null)
|
|
};
|
|
}
|
|
|
|
describe('Context read reuse without persistent engine storage', () => {
|
|
it('reads evaluations, templates and models once per exact argument, retaining complete independent values', async () => {
|
|
const port = source();
|
|
const records = [{ id: 'synthetic', results: [{ original: 'Complete source', verdict: 'deviation' }], provenance: ['original run'] }];
|
|
const evaluations = vi.fn(async () => structuredClone(records));
|
|
port.listPolicyEvaluations = evaluations as never;
|
|
const reuse = reuseContextReads(port);
|
|
const [first, second] = await Promise.all([reuse.listPolicyEvaluations('p'), reuse.listPolicyEvaluations('p')]);
|
|
expect(first).toEqual(records);
|
|
expect(second).toEqual(records);
|
|
expect(first).not.toBe(second);
|
|
(first as unknown as typeof records)[0].results[0].original = 'changed by reader';
|
|
expect(await reuse.listPolicyEvaluations('p')).toEqual(records);
|
|
expect(evaluations).toHaveBeenCalledTimes(1);
|
|
await reuse.listPolicyEvaluations('other');
|
|
expect(evaluations).toHaveBeenCalledTimes(2);
|
|
await reuse.listMicrosoftSecurityBaselineAnalyses!('baseline');
|
|
await reuse.listMicrosoftSecurityBaselineAnalyses!('baseline');
|
|
await reuse.loadAssessmentModel!('assessment');
|
|
await reuse.loadAssessmentModel!('assessment');
|
|
expect(port.listMicrosoftSecurityBaselineAnalyses).toHaveBeenCalledTimes(1);
|
|
expect(port.loadAssessmentModel).toHaveBeenCalledTimes(1);
|
|
await reuseContextReads(port).listPolicyEvaluations('p');
|
|
expect(evaluations).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
it('does not retain failed reads or invent optional read capabilities', async () => {
|
|
const port = source();
|
|
const read = vi.fn().mockRejectedValueOnce(new Error('temporary')).mockResolvedValue([]);
|
|
port.listPolicyEvaluations = read;
|
|
delete port.loadAssessmentModel;
|
|
const reuse = reuseContextReads(port);
|
|
expect(reuse.loadAssessmentModel).toBeUndefined();
|
|
await expect(reuse.listPolicyEvaluations('p')).rejects.toThrow('temporary');
|
|
expect(await reuse.listPolicyEvaluations('p')).toEqual([]);
|
|
expect(read).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('repeats all necessary reads on revision change and delivers the same complete Context as direct derivation', async () => {
|
|
const port = source();
|
|
let revision = 7;
|
|
port.currentRevision = async () => revision;
|
|
port.listReferenceRuns = async () => { revision = 8; return []; };
|
|
const scope = { projectId: 'synthetic', assessmentIds: ['a'], generatedAt: '2026-10-02T00:00:00Z' };
|
|
const actual = await createAssessmentContext(port, scope);
|
|
const direct = buildAssessmentContext({ ...scope, repositoryRevision: 8, observations: [], evaluations: [],
|
|
analyses: [], scannerFindings: [], referenceRuns: [], cisReportSections: [], identityTargets: [], catalog });
|
|
expect(actual).toEqual({ ...direct, gapReport: contextGapReport(direct, catalog) });
|
|
expect(port.listPolicyEvaluations).toHaveBeenCalledTimes(2);
|
|
expect(revision).toBe(8);
|
|
});
|
|
});
|