import { describe, expect, test } from "bun:test"; import type { SQL } from "drizzle-orm"; import { SQLiteSyncDialect } from "drizzle-orm/sqlite-core"; import type { CapabilityContext } from "./context"; import { searchTranscripts } from "./transcripts"; // Renders a captured Drizzle `where()` condition to raw SQL text so tests can // assert on which columns/conditions were actually included (e.g. the org // filter), without depending on drizzle's internal SQL-node shape. const dialect = new SQLiteSyncDialect(); function renderWhere(condition: SQL): string { return dialect.sqlToQuery(condition).sql; } // Chainable Drizzle stand-in: captures the condition passed to `where()` and // resolves with a fixed row set, mirroring the pattern in billing.test.ts / // timeline.test.ts. function makeDb(rows: unknown[]) { const captured: { where?: SQL; limitArg?: number } = {}; // biome-ignore lint/suspicious/noExplicitAny: minimal chainable test double const chain: any = { select: () => chain, from: () => chain, innerJoin: () => chain, where: (condition: SQL) => { captured.where = condition; return chain; }, orderBy: () => chain, limit: (n: number) => { captured.limitArg = n; return Promise.resolve(rows.slice(0, n)); }, }; return { chain, captured }; } function ctxWith( db: unknown, organizationId: string | null | undefined, ): CapabilityContext { return { db: db as CapabilityContext["db"], userId: "user1", organizationId, }; } const row = { sessionId: "session-1", title: "Debugging the auth flow", providerId: "claude", role: "assistant", timestamp: new Date("2026-06-01T10:00:00Z"), contentText: "We looked at the OAuth callback and found the token refresh bug.", }; describe("searchTranscripts", () => { test("adds the evidence_threads.organization_id filter when organizationId is set (cross-tenant safety)", async () => { const { chain, captured } = makeDb([row]); await searchTranscripts.execute(ctxWith(chain, "org1"), { query: "OAuth", limit: 20, }); const sql = renderWhere(captured.where as SQL); expect(sql).toContain('"evidence_threads"."organization_id" = ?'); expect(sql).toContain('"evidence_messages"."content_text" like ?'); }); test("omits the org filter when organizationId is null (desktop, single-tenant)", async () => { const { chain, captured } = makeDb([row]); await searchTranscripts.execute(ctxWith(chain, null), { query: "OAuth", limit: 20, }); const sql = renderWhere(captured.where as SQL); expect(sql).not.toContain("organization_id"); }); test("adds a provider filter when provider is given", async () => { const { chain, captured } = makeDb([row]); await searchTranscripts.execute(ctxWith(chain, null), { query: "OAuth", provider: "claude", limit: 20, }); const sql = renderWhere(captured.where as SQL); expect(sql).toContain('"evidence_messages"."provider_id" = ?'); }); test("returns the pinned url shape and result fields", async () => { const { chain } = makeDb([row]); const result = await searchTranscripts.execute(ctxWith(chain, "org1"), { query: "OAuth", limit: 20, }); expect(result.count).toBe(1); expect(result.results).toEqual([ { sessionId: "session-1", provider: "claude", title: "Debugging the auth flow", role: "assistant", timestamp: row.timestamp.toISOString(), snippet: row.contentText, url: "/timeline?conversation=session-1&provider=claude", }, ]); }); test("respects the requested limit", async () => { const rows = Array.from({ length: 5 }, (_, i) => ({ ...row, sessionId: `session-${i}`, })); const { chain, captured } = makeDb(rows); const result = await searchTranscripts.execute(ctxWith(chain, "org1"), { query: "OAuth", limit: 3, }); expect(captured.limitArg).toBe(3); expect(result.count).toBe(3); }); });