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

181 lines
6.0 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import type { PrismaClient } from "./client.js";
import { createGroupRepos } from "./groups.js";
import { IsolationError } from "./scope.js";
describe("listSpaceGroupsForSpaces", () => {
it("loads and maps compact cross-space group fields", async () => {
const findMany = vi.fn(async (_query: { where: unknown; select: Record<string, unknown> }) => [
{
id: "group-1",
spaceId: "workspace-2",
name: "Support crew",
pinned: true,
sectionId: null,
updatedAt: new Date("2026-08-20T00:00:00.000Z"),
thread: {
unread: true,
messages: [{ blocks: [{ kind: "text", text: "Escalation pending" }] }],
},
members: [
{ bot: { id: "bot-1", name: "Triage", color: "#111", runs: [] } },
{
bot: {
id: "bot-2",
name: "Responder",
color: "#222",
runs: [{ status: "running" }],
},
},
],
},
]);
const repos = createGroupRepos({ chatGroup: { findMany } } as unknown as PrismaClient);
const actor = {
spaceId: "workspace-1",
userId: "user-1",
email: "user@example.test",
isDeploymentOwner: false,
};
await expect(repos.listSpaceGroupsForSpaces(actor, ["workspace-2"])).resolves.toEqual([
{
id: "group-1",
spaceId: "workspace-2",
name: "Support crew",
pinned: true,
sectionId: null,
members: [
{ botId: "bot-1", name: "Triage", color: "#111", status: "idle" },
{ botId: "bot-2", name: "Responder", color: "#222", status: "running" },
],
preview: "Escalation pending",
unread: true,
updatedAt: "2026-08-20T00:00:00.000Z",
},
]);
const query = findMany.mock.calls[0]![0];
expect(query.select).not.toHaveProperty("userId");
expect(query.select).not.toHaveProperty("archivedAt");
expect(query.select).not.toHaveProperty("createdAt");
});
});
describe("archiveGroup", () => {
const actor = {
spaceId: "workspace-1",
userId: "user-1",
email: "user@example.com",
isDeploymentOwner: true,
};
let queryRaw: ReturnType<typeof vi.fn>;
let findFirst: ReturnType<typeof vi.fn>;
let findManyRuns: ReturnType<typeof vi.fn>;
let findManyComputers: ReturnType<typeof vi.fn>;
let runUpdateMany: ReturnType<typeof vi.fn>;
let attemptUpdateMany: ReturnType<typeof vi.fn>;
let taskUpdateMany: ReturnType<typeof vi.fn>;
let leaseUpdateMany: ReturnType<typeof vi.fn>;
let leaseFindMany: ReturnType<typeof vi.fn>;
let computerUpdateMany: ReturnType<typeof vi.fn>;
let eventDeleteMany: ReturnType<typeof vi.fn>;
let groupUpdate: ReturnType<typeof vi.fn>;
let prisma: PrismaClient;
beforeEach(() => {
queryRaw = vi.fn().mockResolvedValue([{ id: "group-1" }]);
findFirst = vi.fn().mockResolvedValue({ thread: { id: "thread-1" } });
findManyRuns = vi.fn().mockResolvedValue([{ id: "run-1", taskId: "task-1" }]);
findManyComputers = vi.fn().mockResolvedValue([
{
id: "computer-1",
homeKey: "home-1",
kind: "fake",
providerRef: "computer-1",
executionBotId: "bot-1",
executionRunId: "run-1",
},
]);
runUpdateMany = vi.fn();
attemptUpdateMany = vi.fn();
taskUpdateMany = vi.fn();
leaseUpdateMany = vi.fn();
leaseFindMany = vi
.fn()
.mockResolvedValue([{ computerId: "computer-1", runId: "run-1", fence: 3 }]);
computerUpdateMany = vi.fn();
eventDeleteMany = vi.fn();
groupUpdate = vi.fn();
const tx = {
$queryRaw: queryRaw,
chatGroup: { findFirst, update: groupUpdate },
run: { findMany: findManyRuns, updateMany: runUpdateMany },
attempt: { updateMany: attemptUpdateMany },
task: { updateMany: taskUpdateMany },
computerExecutionLease: { findMany: leaseFindMany, updateMany: leaseUpdateMany },
computer: { findMany: findManyComputers, updateMany: computerUpdateMany },
event: { deleteMany: eventDeleteMany },
};
prisma = {
$transaction: vi.fn(async (callback: (client: typeof tx) => unknown) => callback(tx)),
} as unknown as PrismaClient;
});
it("locks the group, archives it, and cancels only that thread's runs", async () => {
const repos = createGroupRepos(prisma);
await expect(repos.archiveGroup(actor, "group-1")).resolves.toEqual({
cancelledRunIds: ["run-1"],
computers: [
{
id: "computer-1",
homeKey: "home-1",
kind: "fake",
providerRef: "computer-1",
executionBotId: "bot-1",
executionRunId: "run-1",
executionFence: 3,
},
],
});
expect(queryRaw).toHaveBeenCalled();
expect(findManyRuns).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({ threadId: "thread-1" }),
}),
);
expect(findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
id: "group-1",
archivedAt: null,
}),
}),
);
expect(leaseUpdateMany).toHaveBeenCalledWith({
where: { runId: { in: ["run-1"] } },
data: { expiresAt: new Date(0) },
});
expect(computerUpdateMany).toHaveBeenCalledWith({
where: { executionRunId: { in: ["run-1"] } },
data: {
executionRunId: null,
executionBotId: null,
executionLeaseExpiresAt: null,
},
});
expect(groupUpdate).toHaveBeenCalledWith({
where: { id: "group-1" },
data: expect.objectContaining({ pinned: false, archivedAt: expect.any(Date) }),
});
});
it("rejects when the group is already archived or missing", async () => {
findFirst.mockResolvedValue(null);
const repos = createGroupRepos(prisma);
await expect(repos.archiveGroup(actor, "group-1")).rejects.toBeInstanceOf(IsolationError);
expect(groupUpdate).not.toHaveBeenCalled();
});
});