Assessor_2.0/tests/server/qdrant-regulatory-index.int...

65 lines
3.3 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { createQdrantRegulatoryIndexFromEnvironment, REGULATORY_QDRANT_COLLECTION } from '../../server/regulatory/qdrant-regulatory-index';
import { DenseRegulatoryCandidateRetriever, RegulatoryIngestionService } from '../../server/regulatory/regulatory-rag-service';
import type { RegulatoryControlDocument } from '../../src/domain/regulatory-rag';
import type { TextEmbeddingClient } from '../../server/regulatory/embedding-client';
const runIntegration = process.env.ASSESSOR_QDRANT_INTEGRATION === '1';
const describeQdrant = runIntegration ? describe : describe.skip;
describeQdrant('Qdrant regulatory index integration', () => {
it('creates, replaces, filters, and retrieves a synthetic control', async () => {
const dimensions = Number(process.env.ASSESSOR_RAG_EMBEDDING_DIMENSIONS);
const model = process.env.ASSESSOR_RAG_EMBEDDING_MODEL ?? '';
if (!Number.isInteger(dimensions) || dimensions < 64 || !model) throw new Error('Configured embedding dimensions and model are required.');
const vector = Array.from({ length: dimensions }, (_, index) => index === 0 ? 1 : 0);
const embeddings: TextEmbeddingClient = { model, dimensions, embed: async texts => texts.map(() => vector) };
const index = createQdrantRegulatoryIndexFromEnvironment(dimensions, model);
const ingestion = new RegulatoryIngestionService(embeddings, index);
const document: RegulatoryControlDocument = {
schemaVersion: 1,
corpusId: 'assessor-qdrant-integration',
corpusVersion: '1',
documentId: 'synthetic-live-control-de',
framework: 'ASSESSOR Synthetic Controls',
frameworkVersion: '1',
controlId: 'SYN-LIVE',
language: 'de',
title: 'Synthetische Livekontrolle',
text: 'Dieser synthetische Text prueft ausschliesslich den lokalen rebuildbaren Qdrant-Index.'
};
try {
await expect(ingestion.ingest([document])).resolves.toMatchObject({ documentCount: 1, chunkCount: 1, embeddingModel: model });
const retrieval = new DenseRegulatoryCandidateRetriever(embeddings, index);
const candidates = await retrieval.retrieveCandidates({
query: 'synthetic local index check',
limit: 3,
filters: { corpusId: document.corpusId, framework: document.framework, language: 'de' }
});
expect(candidates).toHaveLength(1);
expect(candidates[0]).toMatchObject({ chunk: {
corpusId: document.corpusId,
documentId: document.documentId,
controlId: document.controlId,
language: 'de'
} });
} finally {
await deleteSyntheticCorpus(document.corpusId);
}
});
});
async function deleteSyntheticCorpus(corpusId: string): Promise<void> {
const baseUrl = process.env.ASSESSOR_QDRANT_URL;
const apiKey = process.env.ASSESSOR_QDRANT_API_KEY;
if (!baseUrl) return;
const response = await fetch(new URL(`/collections/${REGULATORY_QDRANT_COLLECTION}/points/delete?wait=true`, baseUrl), {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...(apiKey ? { 'api-key': apiKey } : {}) },
body: JSON.stringify({ filter: { must: [{ key: 'corpus_id', match: { value: corpusId } }] } })
});
if (!response.ok) throw new Error(`Synthetic Qdrant integration cleanup failed with HTTP ${response.status}.`);
}