Assessor_2.0/tests/server/regulatory-finding-retrieva...

106 lines
5.3 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { RegulatoryFindingRetrievalService } from '../../server/regulatory/regulatory-finding-retrieval';
import type { RegulatoryRetrievalCandidate, RegulatoryRetrievalRequest } from '../../src/domain/regulatory-rag';
import type { UnifiedResultProjection } from '../../src/domain/unified-result';
import { buildRegulatoryFindingQuery } from '../../src/profiles/public-search/regulatory-query';
// Result-table / occurrence data and raw addresses that must never reach the query.
const resultTableAsset = 'DC01.customer.local';
const reporterEmail = 'ops@acme.invalid';
const ownerEmail = 'analyst@acme.invalid';
function finding(overrides: Partial<UnifiedResultProjection> = {}): UnifiedResultProjection {
return {
resultProjectionId: 'projection-1', sourceKind: 'scanner-finding', sourceResultId: 'source-1',
projectId: 'tenant-1', assessmentId: 'assessment-1', collectionRunId: 'run-1',
sourceTool: 'purple-knight',
title: 'SMB signing is not required on domain controllers',
description: `The SMB server permits unsigned sessions, allowing relay attacks. Reported by ${reporterEmail}.`,
recommendation: 'Require SMB signing through Group Policy on all domain controllers.',
affectedAsset: resultTableAsset,
severityOriginal: 'high', severityNormalized: 'high', outcomeOriginal: 'fail', outcomeNormalized: 'fail',
technicalExecutionState: 'completed', resultType: 'compliance_check', isIoe: false, isDeviation: true,
lifecycleState: 'new', mappingConfidence: 1, reviewStatus: 'open', reviewOwner: ownerEmail,
importedAt: '2026-09-15T10:00:00.000Z', createdAt: '2026-09-15T10:00:00.000Z', categories: [],
...overrides
};
}
const candidate: RegulatoryRetrievalCandidate = {
score: 0.8,
chunk: {
schemaVersion: 1, corpusId: 'synthetic', corpusVersion: '1', documentId: 'control-de',
framework: 'Synthetic Framework', frameworkVersion: '1', controlId: 'CTRL-1', chunkId: 'chunk-1',
chunkIndex: 0, language: 'de', title: 'Synthetic control', text: 'Synthetic regulatory text.',
textSha256: 'a'.repeat(64)
}
};
describe('regulatory finding retrieval boundary', () => {
it('builds the query from the non-personal check text and never from the result table', () => {
const query = buildRegulatoryFindingQuery(finding());
expect(query).toMatchObject({
schemaVersion: 1,
specificity: 'topic',
publicTool: 'Semperis Purple Knight',
publicTopics: ['SMB signing requirements']
});
// The rule-level check text is the intended source.
expect(query?.query).toContain('SMB signing is not required on domain controllers');
expect(query?.query).toContain('unsigned sessions');
// Result-table / occurrence data and raw email addresses never appear.
expect(query?.query).not.toContain(resultTableAsset);
expect(query?.query).not.toContain(reporterEmail);
expect(query?.query).not.toContain(ownerEmail);
expect(query?.query).toContain('[redacted-email]');
});
it('maps every scanner finding, sharpening with a catalog rule ID when available', () => {
expect(buildRegulatoryFindingQuery(finding({
sourceTool: 'pingcastle', sourceRuleId: 'a-krbtgt'
}))).toMatchObject({ specificity: 'rule', publicRuleId: 'A-Krbtgt' });
// Unknown tool, non-catalog rule ID, no topic match: still mapped from the check title.
const generic = buildRegulatoryFindingQuery(finding({
sourceTool: 'customer-private-scanner', sourceRuleId: 'A-Custom',
title: 'Weak service account configuration', description: undefined, recommendation: undefined
}));
expect(generic).toMatchObject({ specificity: 'topic' });
expect(generic?.query).toContain('Weak service account configuration');
expect(generic?.publicRuleId).toBeUndefined();
});
it('fails closed only for non-scanner records or findings without a check title', () => {
expect(buildRegulatoryFindingQuery(finding({ sourceKind: 'environment-observation' }))).toBeUndefined();
expect(buildRegulatoryFindingQuery(finding({ title: ' ' }))).toBeUndefined();
});
it('passes only the prepared query and explicit filters to the retriever', async () => {
const retrieve = vi.fn(async (_request: RegulatoryRetrievalRequest) => [candidate]);
const service = new RegulatoryFindingRetrievalService({ retrieve });
const result = await service.retrieve(finding(), 5, { framework: 'Synthetic Framework' });
expect(result).toMatchObject({ sourceResultId: 'source-1', status: 'candidates', candidates: [candidate] });
expect(retrieve).toHaveBeenCalledOnce();
expect(retrieve.mock.calls[0][0]).toEqual({
query: result.preparedQuery?.query,
limit: 5,
filters: { framework: 'Synthetic Framework' }
});
for (const marker of [resultTableAsset, reporterEmail, ownerEmail]) {
expect(JSON.stringify(retrieve.mock.calls[0][0])).not.toContain(marker);
}
});
it('does not call retrieval for a finding without a check title', async () => {
const retrieve = vi.fn();
const service = new RegulatoryFindingRetrievalService({ retrieve });
await expect(service.retrieve(finding({ title: '' }), 5))
.resolves.toEqual({ sourceResultId: 'source-1', status: 'insufficient-public-context', candidates: [] });
expect(retrieve).not.toHaveBeenCalled();
});
});