Assessor_2.0/tests/assessment-context/context-read-reuse.test.ts

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