File
Blob: tests/worker/queues/search-indexer.workers.test.ts
| 1 | import { env } from "cloudflare:workers"; |
| 2 | import { ulid } from "ulid"; |
| 3 | import { beforeEach, describe, expect, it } from "vitest"; |
| 4 | |
| 5 | import { handleSearchIndexMessage } from "@/worker/queues/search-indexer"; |
| 6 | import { resetD1Tables } from "@tests/worker/helpers/db"; |
| 7 | import { runInWorkspaceIndexer } from "@tests/worker/helpers/do"; |
| 8 | import { seedPage, seedUser, seedWorkspace } from "@tests/worker/helpers/seeds"; |
| 9 | |
| 10 | async 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 | |
| 18 | async 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 | |
| 25 | describe("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 | }); |