2026-08-22 02:05:11 -04:00
|
|
|
import test from "node:test";
|
|
|
|
|
import assert from "node:assert/strict";
|
|
|
|
|
import { once } from "node:events";
|
|
|
|
|
import { loadConfig } from "../src/config.js";
|
|
|
|
|
import { createApp } from "../src/app.js";
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Helpers: run the app on an ephemeral loopback port with an injected fetch,
|
|
|
|
|
// so no Qdrant/Ollama needs to be up.
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
const TEST_ENV = {
|
|
|
|
|
QDRANT_URL: "http://qdrant.test:6333",
|
|
|
|
|
OLLAMA_URL: "http://ollama.test:11436",
|
|
|
|
|
EMBED_MODEL: "nomic-embed-text",
|
|
|
|
|
PROBE_TIMEOUT_MS: "200",
|
|
|
|
|
UPSTREAM_TIMEOUT_MS: "500",
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-22 02:35:23 -04:00
|
|
|
async function withServer(fetchImpl, fn, envOverrides = {}) {
|
|
|
|
|
const config = loadConfig({ ...TEST_ENV, ...envOverrides });
|
2026-08-22 02:05:11 -04:00
|
|
|
const app = createApp(config, { fetchImpl });
|
|
|
|
|
const server = app.listen(0, "127.0.0.1");
|
|
|
|
|
await once(server, "listening");
|
|
|
|
|
const base = `http://127.0.0.1:${server.address().port}`;
|
|
|
|
|
try {
|
|
|
|
|
return await fn(base, config);
|
|
|
|
|
} finally {
|
|
|
|
|
server.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function jsonResponse(body, status = 200) {
|
|
|
|
|
return new Response(JSON.stringify(body), {
|
|
|
|
|
status,
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Fake fetch that dispatches on method+url via a routes table. */
|
|
|
|
|
function fakeFetch(routes) {
|
|
|
|
|
const calls = [];
|
|
|
|
|
const impl = async (url, init = {}) => {
|
|
|
|
|
const method = init.method ?? "GET";
|
|
|
|
|
const key = `${method} ${url}`;
|
2026-08-22 02:35:23 -04:00
|
|
|
calls.push({ key, url: String(url), method, body: init.body ? JSON.parse(init.body) : undefined });
|
2026-08-22 02:05:11 -04:00
|
|
|
for (const [pattern, handler] of Object.entries(routes)) {
|
|
|
|
|
if (key === pattern || key.startsWith(pattern)) {
|
|
|
|
|
return handler({ url, init, body: init.body ? JSON.parse(init.body) : undefined });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
throw new TypeError(`fetch failed: no route for ${key}`);
|
|
|
|
|
};
|
|
|
|
|
impl.calls = calls;
|
|
|
|
|
return impl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const DIM = 4;
|
|
|
|
|
const vecFor = (text) => [text.length, 1, 2, 3];
|
|
|
|
|
|
|
|
|
|
function happyRoutes() {
|
|
|
|
|
return {
|
|
|
|
|
// health probes
|
|
|
|
|
"GET http://qdrant.test:6333/readyz": () => new Response("all shards ready", { status: 200 }),
|
|
|
|
|
"GET http://ollama.test:11436/api/tags": () =>
|
|
|
|
|
jsonResponse({ models: [{ name: "nomic-embed-text:latest" }, { name: "qwen3:30b-a3b" }] }),
|
|
|
|
|
// embeddings
|
|
|
|
|
"POST http://ollama.test:11436/api/embeddings": ({ body }) => jsonResponse({ embedding: vecFor(body.prompt) }),
|
|
|
|
|
// qdrant collection lifecycle
|
|
|
|
|
"GET http://qdrant.test:6333/collections/notes": () => jsonResponse({ result: {} }, 404),
|
|
|
|
|
"PUT http://qdrant.test:6333/collections/notes/points": () =>
|
|
|
|
|
jsonResponse({ result: { operation_id: 1, status: "completed" } }),
|
|
|
|
|
"PUT http://qdrant.test:6333/collections/notes": () => jsonResponse({ result: true }),
|
|
|
|
|
"POST http://qdrant.test:6333/collections/notes/points/search": () =>
|
|
|
|
|
jsonResponse({
|
|
|
|
|
result: [
|
|
|
|
|
{ id: "a", score: 0.91, payload: { text: "alpha", src: "t" } },
|
|
|
|
|
{ id: "b", score: 0.42, payload: { text: "beta" } },
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// config
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
test("config defaults: port 5499, qdrant 6333, ollama 11436 (NOT 11434), nomic-embed-text", () => {
|
|
|
|
|
const c = loadConfig({});
|
|
|
|
|
assert.equal(c.port, 5499);
|
|
|
|
|
assert.equal(c.qdrantUrl, "http://127.0.0.1:6333");
|
|
|
|
|
assert.equal(c.ollamaUrl, "http://127.0.0.1:11436");
|
|
|
|
|
assert.ok(!c.ollamaUrl.includes("11434"), "must never default to the broken 11434 forward");
|
|
|
|
|
assert.equal(c.embedModel, "nomic-embed-text");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// /health — three-valued
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
test("/health reports ok with surfaces + observed_at when all deps respond", async () => {
|
|
|
|
|
await withServer(fakeFetch(happyRoutes()), async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/health`);
|
|
|
|
|
assert.equal(res.status, 200);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.status, "ok");
|
|
|
|
|
assert.equal(body.dependencies.qdrant.status, "ok");
|
|
|
|
|
assert.equal(body.dependencies.qdrant.measurement_surface, "http://qdrant.test:6333/readyz");
|
|
|
|
|
assert.equal(body.dependencies.ollama.status, "ok");
|
|
|
|
|
assert.equal(body.dependencies.ollama.measurement_surface, "http://ollama.test:11436/api/tags");
|
|
|
|
|
assert.equal(body.dependencies.embed_model.status, "ok");
|
|
|
|
|
for (const dep of Object.values(body.dependencies)) {
|
|
|
|
|
assert.ok(Date.parse(dep.observed_at), "every dep carries observed_at");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("/health reports unobservable (never healthy) when deps are down", async () => {
|
|
|
|
|
// fetch that always fails = both dependencies down
|
|
|
|
|
const deadFetch = async () => {
|
|
|
|
|
throw new TypeError("fetch failed: ECONNREFUSED");
|
|
|
|
|
};
|
|
|
|
|
await withServer(deadFetch, async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/health`);
|
|
|
|
|
assert.equal(res.status, 503);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.status, "degraded");
|
|
|
|
|
assert.notEqual(body.status, "ok", "must never report plain healthy while a dep is unobservable");
|
|
|
|
|
assert.equal(body.dependencies.qdrant.status, "unobservable");
|
|
|
|
|
assert.match(body.dependencies.qdrant.reason, /fetch failed/);
|
|
|
|
|
assert.equal(body.dependencies.ollama.status, "unobservable");
|
|
|
|
|
// model presence is ALSO unobservable — not "absent" — when ollama is down
|
|
|
|
|
assert.equal(body.dependencies.embed_model.status, "unobservable");
|
|
|
|
|
assert.equal(body.dependencies.qdrant.measurement_surface, "http://qdrant.test:6333/readyz");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("/health: dep returning HTTP 500 is unobservable, not ok and not absent", async () => {
|
|
|
|
|
const routes = happyRoutes();
|
|
|
|
|
routes["GET http://qdrant.test:6333/readyz"] = () => new Response("boom", { status: 500 });
|
|
|
|
|
await withServer(fakeFetch(routes), async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/health`);
|
|
|
|
|
assert.equal(res.status, 503);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.status, "degraded");
|
|
|
|
|
assert.equal(body.dependencies.qdrant.status, "unobservable");
|
|
|
|
|
assert.equal(body.dependencies.qdrant.http_status, 500);
|
|
|
|
|
assert.equal(body.dependencies.ollama.status, "ok");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("/health: embed model missing from a REACHABLE ollama is absent (a true gap)", async () => {
|
|
|
|
|
const routes = happyRoutes();
|
|
|
|
|
routes["GET http://ollama.test:11436/api/tags"] = () => jsonResponse({ models: [{ name: "qwen3:30b-a3b" }] });
|
|
|
|
|
await withServer(fakeFetch(routes), async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/health`);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.dependencies.ollama.status, "ok", "ollama itself was observable");
|
|
|
|
|
assert.equal(body.dependencies.embed_model.status, "absent");
|
|
|
|
|
assert.equal(body.status, "degraded");
|
|
|
|
|
assert.equal(res.status, 503);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-22 02:35:23 -04:00
|
|
|
test("/health: bare EMBED_MODEL matches installed tagged model (nomic-embed-text ~ :latest)", async () => {
|
|
|
|
|
// happyRoutes lists nomic-embed-text:latest; config asks for bare name.
|
|
|
|
|
await withServer(fakeFetch(happyRoutes()), async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/health`);
|
|
|
|
|
assert.equal(res.status, 200);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.dependencies.embed_model.status, "ok");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("/health: tagged EMBED_MODEL matches installed bare model (reverse direction)", async () => {
|
|
|
|
|
const routes = happyRoutes();
|
|
|
|
|
routes["GET http://ollama.test:11436/api/tags"] = () =>
|
|
|
|
|
jsonResponse({ models: [{ name: "nomic-embed-text" }] });
|
|
|
|
|
await withServer(
|
|
|
|
|
fakeFetch(routes),
|
|
|
|
|
async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/health`);
|
|
|
|
|
assert.equal(res.status, 200);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.dependencies.embed_model.status, "ok");
|
|
|
|
|
},
|
|
|
|
|
{ EMBED_MODEL: "nomic-embed-text:latest" }
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("/health: when BOTH sides carry tags, tags must match exactly (mismatch => absent)", async () => {
|
|
|
|
|
const routes = happyRoutes(); // lists nomic-embed-text:latest
|
|
|
|
|
await withServer(
|
|
|
|
|
fakeFetch(routes),
|
|
|
|
|
async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/health`);
|
|
|
|
|
assert.equal(res.status, 503);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.dependencies.ollama.status, "ok");
|
|
|
|
|
assert.equal(body.dependencies.embed_model.status, "absent");
|
|
|
|
|
},
|
|
|
|
|
{ EMBED_MODEL: "nomic-embed-text:v1.5" }
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("/health: a different model base name never matches (no false ok on shared tag)", async () => {
|
|
|
|
|
const routes = happyRoutes();
|
|
|
|
|
routes["GET http://ollama.test:11436/api/tags"] = () =>
|
|
|
|
|
jsonResponse({ models: [{ name: "mxbai-embed-large:latest" }] });
|
|
|
|
|
await withServer(fakeFetch(routes), async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/health`);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.dependencies.embed_model.status, "absent");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("/health: 200 tags response WITHOUT a models array is unobservable, never absent", async () => {
|
|
|
|
|
for (const wrongShape of [{}, { tags: [{ name: "nomic-embed-text" }] }, { models: "nope" }, null]) {
|
|
|
|
|
const routes = happyRoutes();
|
|
|
|
|
routes["GET http://ollama.test:11436/api/tags"] = () => jsonResponse(wrongShape);
|
|
|
|
|
await withServer(fakeFetch(routes), async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/health`);
|
|
|
|
|
assert.equal(res.status, 503);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.dependencies.ollama.status, "ok", "reachability verdict stays ok");
|
|
|
|
|
assert.equal(
|
|
|
|
|
body.dependencies.embed_model.status,
|
|
|
|
|
"unobservable",
|
|
|
|
|
`shape ${JSON.stringify(wrongShape)} cannot prove absence`
|
|
|
|
|
);
|
|
|
|
|
assert.notEqual(body.dependencies.embed_model.status, "absent");
|
|
|
|
|
assert.equal(body.status, "degraded");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("/health: non-JSON 200 tags body is unobservable", async () => {
|
|
|
|
|
const routes = happyRoutes();
|
|
|
|
|
routes["GET http://ollama.test:11436/api/tags"] = () => new Response("<html>proxy error</html>", { status: 200 });
|
|
|
|
|
await withServer(fakeFetch(routes), async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/health`);
|
|
|
|
|
assert.equal(res.status, 503);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.dependencies.embed_model.status, "unobservable");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("/health: top-level object carries its own observed_at", async () => {
|
|
|
|
|
await withServer(fakeFetch(happyRoutes()), async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/health`);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.ok(Date.parse(body.observed_at), "top-level observed_at is a valid timestamp");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-22 02:05:11 -04:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// /v1/embed
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
test("POST /v1/embed returns one vector per text via ollama", async () => {
|
|
|
|
|
const f = fakeFetch(happyRoutes());
|
|
|
|
|
await withServer(f, async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/v1/embed`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ texts: ["hello", "worlds!"] }),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 200);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.vectors.length, 2);
|
|
|
|
|
assert.deepEqual(body.vectors[0], vecFor("hello"));
|
|
|
|
|
assert.deepEqual(body.vectors[1], vecFor("worlds!"));
|
|
|
|
|
assert.equal(body.measurement_surface, "http://ollama.test:11436/api/embeddings");
|
|
|
|
|
assert.ok(Date.parse(body.observed_at));
|
|
|
|
|
const embedCalls = f.calls.filter((c) => c.key.includes("/api/embeddings"));
|
|
|
|
|
assert.equal(embedCalls.length, 2);
|
|
|
|
|
assert.equal(embedCalls[0].body.model, "nomic-embed-text");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("POST /v1/embed with ollama down returns 502 marked unobservable with surface", async () => {
|
|
|
|
|
const deadFetch = async () => {
|
|
|
|
|
throw new TypeError("fetch failed: ECONNREFUSED");
|
|
|
|
|
};
|
|
|
|
|
await withServer(deadFetch, async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/v1/embed`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ texts: ["hello"] }),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 502);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.status, "unobservable");
|
|
|
|
|
assert.equal(body.measurement_surface, "http://ollama.test:11436/api/embeddings");
|
|
|
|
|
assert.ok(Date.parse(body.observed_at));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("POST /v1/embed validates body", async () => {
|
|
|
|
|
await withServer(fakeFetch(happyRoutes()), async (base) => {
|
|
|
|
|
for (const bad of [{}, { texts: [] }, { texts: "hello" }, { texts: [1, 2] }]) {
|
|
|
|
|
const res = await fetch(`${base}/v1/embed`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify(bad),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 400);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// /v1/index
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
test("POST /v1/index embeds, auto-creates missing collection, upserts points", async () => {
|
|
|
|
|
const f = fakeFetch(happyRoutes());
|
|
|
|
|
await withServer(f, async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/v1/index`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
collection: "notes",
|
|
|
|
|
items: [
|
|
|
|
|
{ id: 1, text: "alpha", payload: { src: "t" } },
|
|
|
|
|
{ id: 2, text: "beta" },
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 200);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.indexed, 2);
|
|
|
|
|
assert.equal(body.collection, "notes");
|
|
|
|
|
// collection was created with the right vector size
|
|
|
|
|
const create = f.calls.find((c) => c.key === "PUT http://qdrant.test:6333/collections/notes");
|
|
|
|
|
assert.ok(create, "collection create issued");
|
|
|
|
|
assert.equal(create.body.vectors.size, DIM);
|
|
|
|
|
// points carried vectors and payload (text folded in)
|
|
|
|
|
const upsert = f.calls.find((c) => c.key.startsWith("PUT http://qdrant.test:6333/collections/notes/points"));
|
|
|
|
|
assert.equal(upsert.body.points.length, 2);
|
|
|
|
|
assert.deepEqual(upsert.body.points[0].vector, vecFor("alpha"));
|
|
|
|
|
assert.deepEqual(upsert.body.points[0].payload, { src: "t", text: "alpha" });
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("POST /v1/index with qdrant down returns 502 unobservable (never silently indexed:0)", async () => {
|
|
|
|
|
const routes = happyRoutes();
|
|
|
|
|
const qdrantDead = ({ url }) => {
|
|
|
|
|
throw new TypeError("fetch failed: ECONNREFUSED " + url);
|
|
|
|
|
};
|
|
|
|
|
routes["GET http://qdrant.test:6333/collections/notes"] = qdrantDead;
|
|
|
|
|
await withServer(fakeFetch(routes), async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/v1/index`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ collection: "notes", items: [{ id: 1, text: "alpha" }] }),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 502);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.status, "unobservable");
|
|
|
|
|
assert.equal(body.measurement_surface, "http://qdrant.test:6333/collections/notes");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-22 02:35:23 -04:00
|
|
|
test("POST /v1/index reports a measurement_surface byte-exact with the URL actually fetched", async () => {
|
|
|
|
|
const f = fakeFetch(happyRoutes());
|
|
|
|
|
await withServer(f, async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/v1/index`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ collection: "notes", items: [{ id: 1, text: "alpha" }] }),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 200);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
const upsertCall = f.calls.find((c) => c.method === "PUT" && c.url.includes("/points"));
|
|
|
|
|
assert.ok(upsertCall, "upsert call was made");
|
|
|
|
|
assert.equal(body.measurement_surface, upsertCall.url, "surface must byte-match the fetched URL");
|
|
|
|
|
assert.equal(body.measurement_surface, "http://qdrant.test:6333/collections/notes/points?wait=true");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("POST /v1/index surface stays byte-exact when the collection name needs URL-encoding", async () => {
|
|
|
|
|
const f = fakeFetch({
|
|
|
|
|
...happyRoutes(),
|
|
|
|
|
// NOTE: the /points route must precede the bare collection route (startsWith matching).
|
|
|
|
|
"GET http://qdrant.test:6333/collections/my%20notes": () => jsonResponse({ result: {} }, 404),
|
|
|
|
|
"PUT http://qdrant.test:6333/collections/my%20notes/points?wait=true": () =>
|
|
|
|
|
jsonResponse({ result: { operation_id: 7, status: "completed" } }),
|
|
|
|
|
"PUT http://qdrant.test:6333/collections/my%20notes": () => jsonResponse({ result: true }),
|
|
|
|
|
});
|
|
|
|
|
await withServer(f, async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/v1/index`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ collection: "my notes", items: [{ id: 1, text: "alpha" }] }),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 200);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
const upsertCall = f.calls.find((c) => c.method === "PUT" && c.url.includes("/points"));
|
|
|
|
|
assert.equal(body.measurement_surface, upsertCall.url);
|
|
|
|
|
assert.equal(body.measurement_surface, "http://qdrant.test:6333/collections/my%20notes/points?wait=true");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-22 02:05:11 -04:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// /v1/search
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
test("POST /v1/search embeds the query and returns scored payloads", async () => {
|
|
|
|
|
const f = fakeFetch(happyRoutes());
|
|
|
|
|
await withServer(f, async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/v1/search`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ collection: "notes", query: "find alpha", limit: 2 }),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 200);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.results.length, 2);
|
|
|
|
|
assert.deepEqual(body.results[0], { id: "a", score: 0.91, payload: { text: "alpha", src: "t" } });
|
|
|
|
|
const search = f.calls.find((c) => c.key.startsWith("POST http://qdrant.test:6333/collections/notes/points/search"));
|
|
|
|
|
assert.deepEqual(search.body.vector, vecFor("find alpha"));
|
|
|
|
|
assert.equal(search.body.limit, 2);
|
|
|
|
|
assert.equal(search.body.with_payload, true);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-22 02:35:23 -04:00
|
|
|
test("POST /v1/search reports a measurement_surface byte-exact with the URL actually fetched", async () => {
|
|
|
|
|
const f = fakeFetch(happyRoutes());
|
|
|
|
|
await withServer(f, async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/v1/search`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ collection: "notes", query: "find alpha" }),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 200);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
const searchCall = f.calls.find((c) => c.method === "POST" && c.url.includes("/points/search"));
|
|
|
|
|
assert.ok(searchCall, "search call was made");
|
|
|
|
|
assert.equal(body.measurement_surface, searchCall.url, "surface must byte-match the fetched URL");
|
|
|
|
|
assert.equal(body.measurement_surface, "http://qdrant.test:6333/collections/notes/points/search");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("POST /v1/search surface stays byte-exact when the collection name needs URL-encoding", async () => {
|
|
|
|
|
const f = fakeFetch({
|
|
|
|
|
...happyRoutes(),
|
|
|
|
|
"POST http://qdrant.test:6333/collections/my%20notes/points/search": () =>
|
|
|
|
|
jsonResponse({ result: [{ id: "a", score: 0.5, payload: { text: "alpha" } }] }),
|
|
|
|
|
});
|
|
|
|
|
await withServer(f, async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/v1/search`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ collection: "my notes", query: "alpha" }),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 200);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
const searchCall = f.calls.find((c) => c.method === "POST" && c.url.includes("/points/search"));
|
|
|
|
|
assert.equal(body.measurement_surface, searchCall.url);
|
|
|
|
|
assert.equal(body.measurement_surface, "http://qdrant.test:6333/collections/my%20notes/points/search");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("POST /v1/embed reports a measurement_surface byte-exact with the URL actually fetched", async () => {
|
|
|
|
|
const f = fakeFetch(happyRoutes());
|
|
|
|
|
await withServer(f, async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/v1/embed`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ texts: ["hello"] }),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 200);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
const embedCall = f.calls.find((c) => c.method === "POST" && c.url.includes("/api/embeddings"));
|
|
|
|
|
assert.equal(body.measurement_surface, embedCall.url, "surface must byte-match the fetched URL");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-22 02:05:11 -04:00
|
|
|
test("POST /v1/search with qdrant down returns 502 unobservable — never an empty result set", async () => {
|
|
|
|
|
const routes = happyRoutes();
|
|
|
|
|
routes["POST http://qdrant.test:6333/collections/notes/points/search"] = () => {
|
|
|
|
|
throw new TypeError("fetch failed: ECONNREFUSED");
|
|
|
|
|
};
|
|
|
|
|
await withServer(fakeFetch(routes), async (base) => {
|
|
|
|
|
const res = await fetch(`${base}/v1/search`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ collection: "notes", query: "anything" }),
|
|
|
|
|
});
|
|
|
|
|
assert.equal(res.status, 502);
|
|
|
|
|
const body = await res.json();
|
|
|
|
|
assert.equal(body.status, "unobservable");
|
|
|
|
|
assert.ok(!("results" in body), "an unobservable search must not look like an empty result");
|
|
|
|
|
});
|
|
|
|
|
});
|