Files
ThothII/backend/test/tht-qdrant-readiness.test.ts
T

94 lines
3.2 KiB
TypeScript

import { expect, test, vi } from "vitest";
import { ThtRunner } from "../src/tht/tht-runner.js";
import type { CanonicalWorkspace } from "../src/workspaces/schema.js";
const keywordIndexes = [
"content_hash", "document_id", "kind", "record_key", "record_kind",
"vector_generation", "workspace_id", "workspace_revision",
];
const workspace: CanonicalWorkspace = {
workspace: { schema_version: 4, id: "psd", name: "PSD", language: "it" },
dwh: {
engine: "postgres", database: "warehouse", schema: "public",
supported_transports: ["postgres_direct"],
},
};
function runner(request: (...args: any[]) => Promise<any>) {
return new ThtRunner({
thtBin: "tht",
harnessDir: "/harness",
configPath: "config/tht.yaml",
semanticRuntime: {
internalQdrantUrl: "http://qdrant:6333",
internalEmbeddingUrl: "http://embedding:11434",
internalEmbeddingModel: "qwen3-embedding:0.6b",
internalEmbeddingDimensions: 1024,
},
qdrantRequest: request,
});
}
function response(status: number, body: unknown) {
return {
ok: status >= 200 && status < 300,
status,
json: async () => body,
};
}
function collection(overrides: Record<string, unknown> = {}) {
return {
result: {
config: { params: { vectors: { size: 1024, distance: "Cosine" } } },
payload_schema: Object.fromEntries(keywordIndexes.map((field) => [field, { data_type: "keyword" }])),
...overrides,
},
};
}
test("Qdrant readiness uses only the internal URL and accepts the exact collection contract", async () => {
const request = vi.fn(async () => response(200, collection()));
await expect(runner(request).qdrantEnsure(workspace, 3)).resolves.toEqual({ ok: true, state: "ready" });
expect(request).toHaveBeenCalledOnce();
expect(request.mock.calls[0][0]).toBe("http://qdrant:6333/collections/psd");
expect(request.mock.calls[0][1]).toMatchObject({ method: "GET", signal: expect.any(AbortSignal) });
});
test("Qdrant readiness classifies a missing collection as semantic incompatibility", async () => {
const request = vi.fn(async () => response(404, { status: "error", detail: "secret" }));
await expect(runner(request).qdrantEnsure(workspace, 3)).resolves.toEqual({
ok: false,
code: "semantic_index_incompatible",
});
});
test.each([
["dimensions", collection({
config: { params: { vectors: { size: 768, distance: "Cosine" } } },
})],
["distance", collection({
config: { params: { vectors: { size: 1024, distance: "Dot" } } },
})],
["payload indexes", collection({ payload_schema: { workspace_id: { data_type: "keyword" } } })],
])("Qdrant readiness rejects incompatible %s", async (_label, body) => {
const request = vi.fn(async () => response(200, body));
await expect(runner(request).qdrantEnsure(workspace, 3)).resolves.toEqual({
ok: false,
code: "semantic_index_incompatible",
});
});
test("Qdrant readiness sanitizes unreachable internal service failures", async () => {
const request = vi.fn(async () => { throw new Error("connect http://qdrant:6333/private"); });
await expect(runner(request).qdrantEnsure(workspace, 3)).resolves.toEqual({
ok: false,
code: "workspace_not_activatable",
});
});