99 lines
3.2 KiB
TypeScript
99 lines
3.2 KiB
TypeScript
import { expect, test } from "vitest";
|
|
import { ReadinessManager } from "../src/runtime/readiness-manager.js";
|
|
|
|
const workspace = {
|
|
workspace: { schema_version: 4, id: "psd", name: "PSD", language: "it" },
|
|
dwh: {
|
|
engine: "postgres", database: "warehouse", schema: "public",
|
|
supported_transports: ["postgres_direct"],
|
|
},
|
|
} as const;
|
|
|
|
function deferred<T>() {
|
|
let resolve!: (value: T) => void;
|
|
const promise = new Promise<T>((r) => { resolve = r; });
|
|
return { promise, resolve };
|
|
}
|
|
|
|
test("readiness is single-flight per workspace and caches a short-lived success", async () => {
|
|
let now = 1_000;
|
|
let calls = 0;
|
|
const first = deferred<any>();
|
|
const tht = {
|
|
ollamaEnsure: async () => {
|
|
calls += 1;
|
|
return first.promise;
|
|
},
|
|
} as any;
|
|
const readiness = new ReadinessManager(tht, 60, 500, () => now);
|
|
|
|
const a = readiness.ensure("psd");
|
|
const b = readiness.ensure("psd");
|
|
expect(calls).toBe(1);
|
|
first.resolve({ ok: true, model_name: "embed" });
|
|
await expect(a).resolves.toMatchObject({ ok: true });
|
|
await expect(b).resolves.toMatchObject({ ok: true });
|
|
|
|
await readiness.ensure("psd");
|
|
expect(calls).toBe(1);
|
|
now += 501;
|
|
tht.ollamaEnsure = async () => { calls += 1; return { ok: true }; };
|
|
await readiness.ensure("psd");
|
|
expect(calls).toBe(2);
|
|
});
|
|
|
|
test("readiness keys workspaces independently", async () => {
|
|
const seen: string[] = [];
|
|
const tht = {
|
|
ollamaEnsure: async (workspace: string) => {
|
|
seen.push(workspace);
|
|
return { ok: true };
|
|
},
|
|
} as any;
|
|
const readiness = new ReadinessManager(tht, 60);
|
|
await Promise.all([readiness.ensure("a"), readiness.ensure("b")]);
|
|
expect(seen.sort()).toEqual(["a", "b"]);
|
|
});
|
|
|
|
test("readiness does not cache failed results", async () => {
|
|
let calls = 0;
|
|
const tht = {
|
|
ollamaEnsure: async () => {
|
|
calls += 1;
|
|
return calls === 1 ? { ok: false, error: "down" } : { ok: true };
|
|
},
|
|
} as any;
|
|
const readiness = new ReadinessManager(tht, 60);
|
|
await expect(readiness.ensure("psd")).resolves.toMatchObject({ ok: false });
|
|
await expect(readiness.ensure("psd")).resolves.toMatchObject({ ok: true });
|
|
expect(calls).toBe(2);
|
|
});
|
|
|
|
test("readiness checks Qdrant before Ollama and skips Ollama on semantic incompatibility", async () => {
|
|
let ollamaCalls = 0;
|
|
const tht = {
|
|
qdrantEnsure: async () => ({ ok: false, code: "semantic_index_incompatible" }),
|
|
ollamaEnsure: async () => { ollamaCalls += 1; return { ok: true }; },
|
|
} as any;
|
|
const readiness = new ReadinessManager(tht, 60);
|
|
|
|
await expect(readiness.ensure("/registry/psd.yaml", undefined, workspace as any)).resolves.toEqual({
|
|
ok: false,
|
|
code: "semantic_index_incompatible",
|
|
});
|
|
expect(ollamaCalls).toBe(0);
|
|
});
|
|
|
|
test("readiness returns a sanitized activation code when a semantic probe throws", async () => {
|
|
const tht = {
|
|
qdrantEnsure: async () => { throw new Error("dial http://qdrant:6333/private"); },
|
|
ollamaEnsure: async () => ({ ok: true }),
|
|
} as any;
|
|
const readiness = new ReadinessManager(tht, 60);
|
|
|
|
await expect(readiness.ensure("/registry/psd.yaml", undefined, workspace as any)).resolves.toEqual({
|
|
ok: false,
|
|
code: "workspace_not_activatable",
|
|
});
|
|
});
|