Skip to content
File

Blob: tests/worker/queues/search-indexer.workers.test.ts

typescript79 lines
1import { env } from "cloudflare:workers";
2import { ulid } from "ulid";
3import { beforeEach, describe, expect, it } from "vitest";
4 
5import { handleSearchIndexMessage } from "@/worker/queues/search-indexer";
6import { resetD1Tables } from "@tests/worker/helpers/db";
7import { runInWorkspaceIndexer } from "@tests/worker/helpers/do";
8import { seedPage, seedUser, seedWorkspace } from "@tests/worker/helpers/seeds";
9 
10async function searchIndexerHas(workspaceId: string, query: string, pageId: string): Promise<boolean> {
11 return runInWorkspaceIndexer(workspaceId, async (instance) => {
12 const stub = instance as { search(query: string, limit: number): Promise<{ items: { pageId: string }[] }> };
13 const res = await stub.search(query, 10);
14 return res.items.some((item) => item.pageId === pageId);
15 });
16}
17 
18async function clearIndexer(workspaceId: string): Promise<void> {
19 await runInWorkspaceIndexer(workspaceId, async (instance) => {
20 const stub = instance as { clear(): Promise<unknown> };
21 await stub.clear();
22 });
23}
24 
25describe("handleSearchIndexMessage", () => {
26 beforeEach(async () => {
27 await resetD1Tables();
28 });
29 
30 it("returns retry with delay when the page is not yet visible in D1", async () => {
31 const result = await handleSearchIndexMessage({ type: "index-page", pageId: ulid() }, env);
32 expect(result).toEqual({ kind: "retry", delaySeconds: 2, reason: "page_not_yet_visible" });
33 });
34 
35 it("returns ok and removes the page from the index when archived", async () => {
36 const owner = await seedUser();
37 const ws = await seedWorkspace({ owner_id: owner.id });
38 const page = await seedPage({
39 workspace_id: ws.id,
40 created_by: owner.id,
41 title: "Soon archived",
42 });
43 
44 // Pre-seed an entry to prove removal happens.
45 await runInWorkspaceIndexer(ws.id, async (instance) => {
46 const stub = instance as { indexPage(id: string, title: string, body: string): Promise<unknown> };
47 await stub.indexPage(page.id, "Soon archived", "soon archived body");
48 });
49 expect(await searchIndexerHas(ws.id, "Soon archived", page.id)).toBe(true);
50 
51 // Now mark archived in D1 and run the consumer.
52 await env.DB.prepare("UPDATE pages SET archived_at = ? WHERE id = ?")
53 .bind("2026-04-23T00:00:00.000Z", page.id)
54 .run();
55 
56 const result = await handleSearchIndexMessage({ type: "index-page", pageId: page.id }, env);
57 expect(result).toEqual({ kind: "ok" });
58 expect(await searchIndexerHas(ws.id, "Soon archived", page.id)).toBe(false);
59 
60 await clearIndexer(ws.id);
61 });
62 
63 it("returns ok and indexes by D1 title when the DocSync snapshot is missing", async () => {
64 const owner = await seedUser();
65 const ws = await seedWorkspace({ owner_id: owner.id });
66 const page = await seedPage({
67 workspace_id: ws.id,
68 created_by: owner.id,
69 title: "Unique indexed title",
70 });
71 
72 const result = await handleSearchIndexMessage({ type: "index-page", pageId: page.id }, env);
73 expect(result).toEqual({ kind: "ok" });
74 expect(await searchIndexerHas(ws.id, "Unique indexed title", page.id)).toBe(true);
75 
76 await clearIndexer(ws.id);
77 });
78});