98 lines
5.0 KiB
TypeScript
98 lines
5.0 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import type { RegulatoryChunk } from '../../src/domain/regulatory-rag';
|
|
import {
|
|
createQdrantRegulatoryIndexFromEnvironment,
|
|
QdrantClientError,
|
|
QdrantRegulatoryIndex,
|
|
REGULATORY_QDRANT_COLLECTION
|
|
} from '../../server/regulatory/qdrant-regulatory-index';
|
|
|
|
const chunk: RegulatoryChunk = {
|
|
schemaVersion: 1,
|
|
corpusId: 'iso-27001', corpusVersion: '2022-de-1', documentId: 'a-5-1-de',
|
|
framework: 'ISO/IEC 27001', frameworkVersion: '2022', controlId: 'A.5.1',
|
|
chunkId: 'a-5-1-de:0:abcdef', chunkIndex: 0, language: 'de', title: 'Policies',
|
|
text: 'Synthetic regulatory text.', textSha256: 'a'.repeat(64)
|
|
};
|
|
|
|
function json(value: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(value), { status, headers: { 'Content-Type': 'application/json' } });
|
|
}
|
|
|
|
describe('Qdrant regulatory index', () => {
|
|
it('creates the versioned dense/sparse collection and payload indexes', async () => {
|
|
const fetcher = vi.fn(async (urlValue: string | URL | Request, init?: RequestInit) => {
|
|
const url = String(urlValue);
|
|
if (init?.method === 'GET' && url.endsWith(`/collections/${REGULATORY_QDRANT_COLLECTION}`)) {
|
|
const getCount = fetcher.mock.calls.filter(call => call[1]?.method === 'GET').length;
|
|
if (getCount === 1) return json({}, 404);
|
|
return json({ result: { config: { params: {
|
|
vectors: { dense: { size: 2, distance: 'Cosine' } }, sparse_vectors: { sparse: {} }
|
|
} }, payload_schema: {} } });
|
|
}
|
|
return json({ result: true, status: 'ok' });
|
|
});
|
|
const index = new QdrantRegulatoryIndex({
|
|
baseUrl: new URL('http://127.0.0.1:6333'), apiKey: 'secret', dimensions: 2,
|
|
embeddingModel: 'multilingual-model', timeoutMs: 1_000, fetcher
|
|
});
|
|
|
|
await index.ensureCollection();
|
|
|
|
const bodies = fetcher.mock.calls.map(call => call[1]?.body && JSON.parse(String(call[1]?.body))).filter(Boolean);
|
|
expect(bodies[0]).toMatchObject({ vectors: { dense: { size: 2, distance: 'Cosine' } }, sparse_vectors: { sparse: {} } });
|
|
expect(bodies.filter(body => body.field_name)).toHaveLength(12);
|
|
expect(fetcher.mock.calls.every(call => new Headers(call[1]?.headers).get('api-key') === 'secret')).toBe(true);
|
|
});
|
|
|
|
it('replaces document points and performs filtered dense retrieval', async () => {
|
|
const requests: Array<{ url: string; body?: any }> = [];
|
|
const fetcher = vi.fn(async (urlValue: string | URL | Request, init?: RequestInit) => {
|
|
const body = init?.body ? JSON.parse(String(init.body)) : undefined;
|
|
requests.push({ url: String(urlValue), body });
|
|
if (String(urlValue).endsWith('/points/query')) return json({ result: { points: [{ score: 0.91, payload: {
|
|
schema_version: 1, corpus_id: chunk.corpusId, corpus_version: chunk.corpusVersion,
|
|
document_id: chunk.documentId, framework: chunk.framework, framework_version: chunk.frameworkVersion,
|
|
control_id: chunk.controlId, chunk_id: chunk.chunkId, chunk_index: chunk.chunkIndex,
|
|
language: chunk.language, title: chunk.title, text: chunk.text, text_sha256: chunk.textSha256
|
|
} }] } });
|
|
return json({ result: true, status: 'ok' });
|
|
});
|
|
const index = new QdrantRegulatoryIndex({
|
|
baseUrl: new URL('http://127.0.0.1:6333'), apiKey: 'secret', dimensions: 2,
|
|
embeddingModel: 'multilingual-model', timeoutMs: 1_000, fetcher
|
|
});
|
|
|
|
await index.replaceDocuments([{ chunk, embedding: [0.2, 0.8] }]);
|
|
await expect(index.searchDense([0.4, 0.6], 5, { framework: 'ISO/IEC 27001', language: 'DE' }))
|
|
.resolves.toEqual([{ score: 0.91, chunk }]);
|
|
|
|
expect(requests[0].body.filter.must).toEqual([
|
|
{ key: 'corpus_id', match: { value: 'iso-27001' } },
|
|
{ key: 'document_id', match: { value: 'a-5-1-de' } }
|
|
]);
|
|
expect(requests[1].body.points[0]).toMatchObject({ vector: { dense: [0.2, 0.8] }, payload: {
|
|
framework: 'ISO/IEC 27001', control_id: 'A.5.1', language: 'de', embedding_model: 'multilingual-model'
|
|
} });
|
|
expect(requests[2].body.filter.must).toEqual(expect.arrayContaining([
|
|
{ key: 'embedding_model', match: { value: 'multilingual-model' } },
|
|
{ key: 'framework', match: { value: 'ISO/IEC 27001' } },
|
|
{ key: 'language', match: { value: 'de' } }
|
|
]));
|
|
});
|
|
|
|
it('fails closed for an incompatible collection and unsafe remote HTTP', async () => {
|
|
const index = new QdrantRegulatoryIndex({
|
|
baseUrl: new URL('http://127.0.0.1:6333'), apiKey: 'secret', dimensions: 2,
|
|
embeddingModel: 'model', timeoutMs: 1_000,
|
|
fetcher: async () => json({ result: { config: { params: {
|
|
vectors: { dense: { size: 3, distance: 'Cosine' } }, sparse_vectors: { sparse: {} }
|
|
} }, payload_schema: {} } })
|
|
});
|
|
await expect(index.ensureCollection()).rejects.toMatchObject({ code: 'qdrant_collection_incompatible' } satisfies Partial<QdrantClientError>);
|
|
expect(() => createQdrantRegulatoryIndexFromEnvironment(2, 'model', {
|
|
ASSESSOR_QDRANT_URL: 'http://qdrant.example', ASSESSOR_QDRANT_API_KEY: 'secret'
|
|
})).toThrow(/must use HTTPS/);
|
|
});
|
|
});
|