test(search): cover cursor, snippet, and searchNotes query body
Some checks failed
CI / check (push) Failing after 10s

This commit is contained in:
Julien Calixte
2026-06-07 22:24:21 +02:00
parent 02f3139f46
commit 365deb305a

204
tests/opensearch_test.ts Normal file
View File

@@ -0,0 +1,204 @@
import { assertEquals, assertRejects } from "@std/assert";
import {
decodeCursor,
encodeCursor,
firstSnippet,
searchNotes,
} from "../src/search/opensearch.ts";
import { installFetchStub, jsonResponse } from "./_helpers/fetch_stub.ts";
const withOpenSearchEnv = (fn: () => Promise<void> | void) => {
return async () => {
const previousUrl = Deno.env.get("OPENSEARCH_URL");
const previousIndex = Deno.env.get("OPENSEARCH_INDEX");
Deno.env.set("OPENSEARCH_URL", "http://opensearch.test");
Deno.env.set("OPENSEARCH_INDEX", "notes");
try {
await fn();
} finally {
if (previousUrl === undefined) Deno.env.delete("OPENSEARCH_URL");
else Deno.env.set("OPENSEARCH_URL", previousUrl);
if (previousIndex === undefined) Deno.env.delete("OPENSEARCH_INDEX");
else Deno.env.set("OPENSEARCH_INDEX", previousIndex);
}
};
};
Deno.test("encodeCursor and decodeCursor round-trip", () => {
const cursor = encodeCursor(3.14, "abc123");
assertEquals(decodeCursor(cursor), [3.14, "abc123"]);
});
Deno.test("decodeCursor returns undefined on garbage input", () => {
assertEquals(decodeCursor("not-base64-at-all!!!"), undefined);
assertEquals(decodeCursor(btoa("not-json")), undefined);
assertEquals(decodeCursor(btoa(JSON.stringify([1]))), undefined);
assertEquals(
decodeCursor(btoa(JSON.stringify(["wrong", "type"]))),
undefined,
);
});
Deno.test("firstSnippet prefers highlight.content > title > source content > title fallback", () => {
assertEquals(
firstSnippet(
{ content: ["hl-content"], title: ["hl-title"] },
{ title: "src-title", content: "src-content" },
),
"hl-content",
);
assertEquals(
firstSnippet({ title: ["hl-title"] }, {
title: "src-title",
content: "src-content",
}),
"hl-title",
);
const longContent = "a".repeat(500);
assertEquals(
firstSnippet(undefined, { content: longContent, title: "ignored" }).length,
200,
);
assertEquals(firstSnippet(undefined, { title: "only-title" }), "only-title");
});
Deno.test(
"searchNotes returns empty when OPENSEARCH_URL is unset",
async () => {
const previousUrl = Deno.env.get("OPENSEARCH_URL");
Deno.env.delete("OPENSEARCH_URL");
try {
const stub = installFetchStub(() =>
jsonResponse({ should: "not be called" })
);
try {
const result = await searchNotes({
q: "hello",
discoverableOnly: true,
limit: 10,
});
assertEquals(result, { results: [] });
assertEquals(stub.calls.length, 0);
} finally {
stub.restore();
}
} finally {
if (previousUrl !== undefined) {
Deno.env.set("OPENSEARCH_URL", previousUrl);
}
}
},
);
Deno.test(
"searchNotes builds multi_match body with filters and cursor",
withOpenSearchEnv(async () => {
const stub = installFetchStub(() =>
jsonResponse({
hits: {
hits: [
{
_source: {
did: "did:plc:a",
rkey: "rk-a",
title: "T",
content: "c",
},
_score: 1.5,
highlight: { content: ["snippet"] },
},
{
_source: {
did: "did:plc:b",
rkey: "rk-b",
title: "U",
content: "d",
},
_score: 1.0,
},
],
},
})
);
try {
const result = await searchNotes({
q: "hello world",
discoverableOnly: true,
did: "did:plc:a",
limit: 2,
cursor: encodeCursor(2.0, "rk-prev"),
});
assertEquals(result.results.length, 2);
assertEquals(result.results[0].snippet, "snippet");
assertEquals(result.results[1].snippet, "d");
assertEquals(result.cursor, encodeCursor(1.0, "rk-b"));
assertEquals(stub.calls.length, 1);
const sent = JSON.parse(stub.calls[0].body) as Record<string, unknown>;
assertEquals(sent.size, 2);
assertEquals(sent.search_after, [2.0, "rk-prev"]);
const query = sent.query as {
bool: { must: unknown[]; filter: unknown[] };
};
const multi =
(query.bool.must[0] as { multi_match: Record<string, unknown> })
.multi_match;
assertEquals(multi.query, "hello world");
assertEquals(multi.fields, ["title^2", "content"]);
assertEquals(multi.fuzziness, "AUTO");
assertEquals(query.bool.filter, [
{ term: { discoverable: true } },
{ term: { did: "did:plc:a" } },
]);
} finally {
stub.restore();
}
}),
);
Deno.test(
"searchNotes returns cursor only when the page is full",
withOpenSearchEnv(async () => {
const stub = installFetchStub(() =>
jsonResponse({
hits: {
hits: [
{
_source: { did: "did:plc:a", rkey: "rk", title: "T" },
_score: 1,
},
],
},
})
);
try {
const result = await searchNotes({
q: "hi",
discoverableOnly: false,
limit: 5,
});
assertEquals(result.results.length, 1);
assertEquals(result.cursor, undefined);
} finally {
stub.restore();
}
}),
);
Deno.test(
"searchNotes throws on non-ok response",
withOpenSearchEnv(async () => {
const stub = installFetchStub(() => new Response("boom", { status: 500 }));
try {
await assertRejects(() =>
searchNotes({
q: "hi",
discoverableOnly: false,
limit: 5,
})
);
} finally {
stub.restore();
}
}),
);