evOo-local/apps/api/src/search.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

76 lines
3.1 KiB
TypeScript

import type { Actor } from "@rakazo/contracts";
import type { PrismaClient } from "@rakazo/db";
import { describe, expect, it, vi } from "vitest";
import { querySpaceSearch } from "./search.js";
const actor: Actor = { spaceId: "space-test", userId: "user-test" };
function fixture(count: number) {
const artifacts = Array.from({ length: count }, (_, index) => ({
id: `artifact-${index}`,
botId: `bot-${index}`,
bot: { name: "Bot" },
name: "report.txt",
mimeType: "text/plain",
size: 10,
}));
const groupArtifacts = artifacts.map((artifact) => ({
...artifact,
id: `group-${artifact.id}`,
botId: null,
bot: null,
groupId: "group-test",
group: { name: "Group" },
}));
const prisma = {
bot: { findMany: vi.fn().mockResolvedValue([]) },
chatGroup: { findMany: vi.fn().mockResolvedValue([]) },
artifact: {
findMany: vi.fn().mockResolvedValueOnce(artifacts).mockResolvedValueOnce(groupArtifacts),
},
routine: { findMany: vi.fn().mockResolvedValue([]) },
$queryRaw: vi.fn().mockResolvedValue([]),
};
return { prisma, artifacts, groupArtifacts };
}
describe("artifact search batching", () => {
it.each([1, 25])("uses one lookup per destination for %i matching artifacts", async (count) => {
const { prisma } = fixture(count);
await querySpaceSearch(prisma as unknown as PrismaClient, actor, "report");
// Two artifact batches and the existing direct/group message searches.
expect(prisma.$queryRaw).toHaveBeenCalledTimes(4);
const direct = prisma.$queryRaw.mock.calls[0]?.[0];
const group = prisma.$queryRaw.mock.calls[1]?.[0];
expect(direct.text).toContain('t."botId" = candidate."targetId"');
expect(group.text).toContain('t."groupId" = candidate."targetId"');
for (const query of [direct, group]) {
expect(query.values).toContain(actor.spaceId);
expect(query.values).toContain(actor.userId);
expect(query.text).toContain('ORDER BY m."createdAt" DESC');
}
});
it("preserves artifact priority and skips missing messages regardless of batch row order", async () => {
const { prisma } = fixture(3);
prisma.$queryRaw
.mockResolvedValueOnce([
{ artifactId: "artifact-2", id: "message-2", seq: 8 },
{ artifactId: "artifact-0", id: "message-0", seq: 6 },
])
.mockResolvedValueOnce([{ artifactId: "group-artifact-1", id: "group-message", seq: 3 }]);
const hits = await querySpaceSearch(prisma as unknown as PrismaClient, actor, "report");
expect(hits.map(({ artifactId, messageId, seq }) => ({ artifactId, messageId, seq }))).toEqual([
{ artifactId: "artifact-0", messageId: "message-0", seq: 6 },
{ artifactId: "artifact-2", messageId: "message-2", seq: 8 },
{ artifactId: "group-artifact-1", messageId: "group-message", seq: 3 },
]);
});
it("does not issue artifact queries for empty batches", async () => {
const { prisma } = fixture(0);
expect(await querySpaceSearch(prisma as unknown as PrismaClient, actor, "report")).toEqual([]);
expect(prisma.$queryRaw).toHaveBeenCalledTimes(2);
});
});