evOo-local/packages/db/src/repos.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

384 lines
12 KiB
TypeScript

import type { Actor } from "@rakazo/contracts";
import { describe, expect, it, vi } from "vitest";
import type { PrismaClient } from "./client.js";
import { createRepos } from "./repos.js";
import { IsolationError } from "./scope.js";
const actor: Actor = {
userId: "user-1",
spaceId: "ws-1",
email: "test@example.com",
isDeploymentOwner: false,
};
const baseBot = {
id: "bot-1",
spaceId: "ws-1",
userId: "user-1",
name: "Test Bot",
title: "",
description: "",
instructions: "",
color: "#000",
notifyOnFinish: true,
pinned: false,
position: 0,
sectionId: null,
archivedAt: null,
parentBotId: null,
memoryScope: null as string | null,
createdAt: new Date("2026-08-19T00:00:00.000Z"),
updatedAt: new Date("2026-08-19T00:00:00.000Z"),
thread: { id: "thread-1", unread: false, messages: [] },
runs: [],
computer: null,
};
function reposFor(memoryScope: string | null) {
const prisma = {
bot: {
findMany: vi.fn(async () => [{ ...baseBot, memoryScope }]),
},
run: {
findMany: vi.fn(async () => []),
},
};
return createRepos(prisma as unknown as PrismaClient);
}
describe("createRepos.listBots", () => {
it("passes memoryScope through as null when unset", async () => {
await expect(reposFor(null).listBots(actor)).resolves.toEqual([
expect.objectContaining({ memoryScope: null }),
]);
});
it("passes memoryScope through when set to shared", async () => {
await expect(reposFor("shared").listBots(actor)).resolves.toEqual([
expect.objectContaining({ memoryScope: "shared" }),
]);
});
it("classifies initial preview runs once across bots, including non-peer results", async () => {
const prisma = {
bot: {
findMany: vi.fn(async () =>
["one", "two"].map((id) => ({
...baseBot,
id,
thread: {
...baseBot.thread,
id: `thread-${id}`,
messages: [{ runId: `run-${id}`, blocks: [{ kind: "text", text: `Answer ${id}` }] }],
},
})),
),
},
run: { findMany: vi.fn(async () => []) },
};
const bots = await createRepos(prisma as unknown as PrismaClient).listBots(actor);
expect(bots.map((bot) => bot.preview)).toEqual(["Answer one", "Answer two"]);
expect(prisma.run.findMany).toHaveBeenCalledExactlyOnceWith({
where: { id: { in: ["run-one", "run-two"] }, trigger: "bot_message" },
select: { id: true },
});
});
it("classifies unseen runs in older pages and retains negative results between pages", async () => {
const prisma = {
bot: {
findMany: vi.fn(async () => [
{
...baseBot,
thread: {
...baseBot.thread,
messages: [
{ seq: 30, runId: "peer-new", blocks: [{ kind: "text", text: "Hidden" }] },
],
},
},
]),
},
run: {
findMany: vi
.fn()
.mockResolvedValueOnce([{ id: "peer-new" }])
.mockResolvedValueOnce([{ id: "peer-old" }]),
},
message: {
findMany: vi
.fn()
.mockResolvedValueOnce([
{ seq: 20, runId: "peer-old", blocks: [{ kind: "text", text: "Also hidden" }] },
{ seq: 19, runId: "user-old", blocks: [] },
])
.mockResolvedValueOnce([
{ seq: 10, runId: "peer-old", blocks: [{ kind: "text", text: "Still hidden" }] },
{ seq: 9, runId: "user-old", blocks: [{ kind: "text", text: "Visible answer" }] },
]),
},
};
const bots = await createRepos(prisma as unknown as PrismaClient).listBots(actor);
expect(bots[0]?.preview).toBe("Visible answer");
expect(prisma.run.findMany).toHaveBeenCalledTimes(2);
expect(prisma.run.findMany).toHaveBeenNthCalledWith(2, {
where: { id: { in: ["peer-old", "user-old"] }, trigger: "bot_message" },
select: { id: true },
});
expect(prisma.message.findMany).toHaveBeenCalledTimes(2);
});
it("keeps bot-to-bot run output out of sidebar previews", async () => {
const findMany = vi.fn(async () => [
{
...baseBot,
thread: {
...baseBot.thread,
messages: [
{
runId: "run-peer",
blocks: [{ kind: "text", text: "Echoed peer reply" }],
},
{
runId: "run-peer",
blocks: [
{
kind: "bot_message_received",
fromBotId: "bot-2",
fromBotName: "Coder",
text: "Peer result",
},
],
},
{ runId: "run-user", blocks: [{ kind: "text", text: "Visible answer" }] },
],
},
},
]);
const prisma = {
bot: {
findMany,
},
run: {
findMany: vi.fn(async () => [{ id: "run-peer" }]),
},
};
await expect(createRepos(prisma as unknown as PrismaClient).listBots(actor)).resolves.toEqual([
expect.objectContaining({ preview: "Visible answer" }),
]);
expect(findMany).toHaveBeenCalledWith(
expect.objectContaining({
include: expect.objectContaining({
thread: {
include: {
messages: { orderBy: { seq: "desc" }, take: 16 },
},
},
}),
}),
);
});
it("skips a peer-run preview tail when the receipt is outside the window", async () => {
const prisma = {
bot: {
findMany: vi.fn(async () => [
{
...baseBot,
thread: {
...baseBot.thread,
messages: [
{
runId: "run-peer",
blocks: [{ kind: "text", text: "Echoed peer reply" }],
},
{ runId: "run-user", blocks: [{ kind: "text", text: "Visible answer" }] },
],
},
},
]),
},
run: {
findMany: vi.fn(async () => [{ id: "run-peer" }]),
},
message: {
findMany: vi.fn(async () => []),
},
};
await expect(createRepos(prisma as unknown as PrismaClient).listBots(actor)).resolves.toEqual([
expect.objectContaining({ preview: "Visible answer" }),
]);
});
it("scans older messages when the newest window is only peer output", async () => {
const messageFindMany = vi.fn(async () => [
{ seq: 1, runId: "run-user", blocks: [{ kind: "text", text: "Older visible answer" }] },
]);
const prisma = {
bot: {
findMany: vi.fn(async () => [
{
...baseBot,
thread: {
...baseBot.thread,
messages: [
{
seq: 20,
runId: "run-peer",
blocks: [{ kind: "text", text: "Echoed peer reply" }],
},
],
},
},
]),
},
run: {
findMany: vi.fn(async () => [{ id: "run-peer" }]),
},
message: {
findMany: messageFindMany,
},
};
await expect(createRepos(prisma as unknown as PrismaClient).listBots(actor)).resolves.toEqual([
expect.objectContaining({ preview: "Older visible answer" }),
]);
expect(messageFindMany).toHaveBeenCalledWith({
where: { threadId: "thread-1", seq: { lt: 20 } },
orderBy: { seq: "desc" },
take: 16,
});
});
it("uses a visible message from the fourth older window for preview", async () => {
const peerWindows = [
[{ seq: 80, runId: "run-peer", blocks: [{ kind: "text", text: "peer 80" }] }],
[{ seq: 60, runId: "run-peer", blocks: [{ kind: "text", text: "peer 60" }] }],
[{ seq: 40, runId: "run-peer", blocks: [{ kind: "text", text: "peer 40" }] }],
[{ seq: 20, runId: "run-peer", blocks: [{ kind: "text", text: "peer 20" }] }],
[{ seq: 1, runId: "run-user", blocks: [{ kind: "text", text: "Fourth-window answer" }] }],
];
let windowIndex = 0;
const messageFindMany = vi.fn(async () => {
windowIndex += 1;
return peerWindows[windowIndex] ?? [];
});
const prisma = {
bot: {
findMany: vi.fn(async () => [
{
...baseBot,
thread: {
...baseBot.thread,
messages: peerWindows[0],
},
},
]),
},
run: {
findMany: vi.fn(async () => [{ id: "run-peer" }]),
},
message: {
findMany: messageFindMany,
},
};
await expect(createRepos(prisma as unknown as PrismaClient).listBots(actor)).resolves.toEqual([
expect.objectContaining({ preview: "Fourth-window answer" }),
]);
expect(messageFindMany).toHaveBeenCalledTimes(4);
});
});
describe("createRepos.listSpaceBotsForSpaces", () => {
it("loads and maps only the compact cross-space sidebar fields", async () => {
const findMany = vi.fn(async (_query: { where: unknown; select: Record<string, unknown> }) => [
{
id: "bot-2",
spaceId: "ws-2",
name: "Support",
title: "Customer support",
color: "#123456",
notifyOnFinish: false,
pinned: true,
sectionId: null,
updatedAt: new Date("2026-08-20T00:00:00.000Z"),
thread: {
unread: true,
messages: [{ blocks: [{ kind: "text", text: "Waiting for a reply" }] }],
},
runs: [{ status: "running" }],
},
]);
const repos = createRepos({ bot: { findMany } } as unknown as PrismaClient);
await expect(repos.listSpaceBotsForSpaces(actor, ["ws-2"])).resolves.toEqual([
{
id: "bot-2",
spaceId: "ws-2",
name: "Support",
title: "Customer support",
color: "#123456",
notifyOnFinish: false,
pinned: true,
sectionId: null,
unread: true,
preview: "Waiting for a reply",
status: "running",
updatedAt: "2026-08-20T00:00:00.000Z",
},
]);
const query = findMany.mock.calls[0]![0];
expect(query.where).toEqual(
expect.objectContaining({ spaceId: { in: ["ws-2"] }, userId: actor.userId }),
);
expect(query.select).not.toHaveProperty("description");
expect(query.select).not.toHaveProperty("instructions");
expect(query.select).not.toHaveProperty("computer");
});
});
describe("createRepos.reorderBots", () => {
function reorderRepos(ids: string[]) {
const update = vi.fn().mockResolvedValue({});
const tx = {
bot: {
findMany: vi.fn().mockResolvedValue(ids.map((id) => ({ id }))),
update,
},
};
const prisma = {
$transaction: vi.fn((run: (client: typeof tx) => Promise<void>) => run(tx)),
};
return { repos: createRepos(prisma as unknown as PrismaClient), update };
}
it("writes each owned bot's requested position", async () => {
const { repos, update } = reorderRepos(["bot-1", "bot-2"]);
await repos.reorderBots(actor, ["bot-2", "bot-1"]);
expect(update).toHaveBeenNthCalledWith(1, {
where: { id: "bot-2" },
data: { position: 0 },
});
expect(update).toHaveBeenNthCalledWith(2, {
where: { id: "bot-1" },
data: { position: 1 },
});
});
it("rejects partial or foreign bot lists before writing", async () => {
const { repos, update } = reorderRepos(["bot-1", "bot-2"]);
await expect(repos.reorderBots(actor, ["bot-1"])).rejects.toBeInstanceOf(IsolationError);
await expect(repos.reorderBots(actor, ["bot-1", "foreign"])).rejects.toBeInstanceOf(
IsolationError,
);
expect(update).not.toHaveBeenCalled();
});
});