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.
384 lines
12 KiB
TypeScript
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();
|
|
});
|
|
});
|