refactor(api): modularize assistant router workflow

This commit is contained in:
2026-03-31 10:30:28 +02:00
parent 45c90438ba
commit f08b47171c
13 changed files with 2186 additions and 1875 deletions
@@ -0,0 +1,166 @@
import { vi } from "vitest";
export const TEST_USER_ID = "assistant-test-user";
export const TEST_CONVERSATION_ID = "assistant-test-conversation";
export function createApprovalStoreMock() {
const records = new Map<string, {
id: string;
userId: string;
conversationId: string;
toolName: string;
toolArguments: string;
summary: string;
status: "PENDING" | "APPROVED" | "CANCELLED" | "EXPIRED";
approvedAt: Date | null;
cancelledAt: Date | null;
createdAt: Date;
expiresAt: Date;
updatedAt: Date;
}>();
return {
assistantApproval: {
findFirst: vi.fn(async ({
where,
orderBy,
}: {
where: {
id?: string;
userId?: string;
conversationId?: string;
status?: "PENDING" | "APPROVED" | "CANCELLED" | "EXPIRED";
};
orderBy?: { createdAt: "desc" | "asc" };
}) => {
const matches = [...records.values()]
.filter((record) => (
(!where.id || record.id === where.id)
&& (!where.userId || record.userId === where.userId)
&& (!where.conversationId || record.conversationId === where.conversationId)
&& (!where.status || record.status === where.status)
))
.sort((a, b) => (
orderBy?.createdAt === "asc"
? a.createdAt.getTime() - b.createdAt.getTime()
: b.createdAt.getTime() - a.createdAt.getTime()
));
return matches[0] ?? null;
}),
findMany: vi.fn(async ({
where,
orderBy,
}: {
where: {
userId?: string;
conversationId?: string;
status?: "PENDING" | "APPROVED" | "CANCELLED" | "EXPIRED";
expiresAt?: { lte?: Date; gt?: Date };
};
orderBy?: { createdAt: "desc" | "asc" };
}) => (
[...records.values()]
.filter((record) => (
(!where.userId || record.userId === where.userId)
&& (!where.conversationId || record.conversationId === where.conversationId)
&& (!where.status || record.status === where.status)
&& (!where.expiresAt?.lte || record.expiresAt <= where.expiresAt.lte)
&& (!where.expiresAt?.gt || record.expiresAt > where.expiresAt.gt)
))
.sort((a, b) => (
orderBy?.createdAt === "asc"
? a.createdAt.getTime() - b.createdAt.getTime()
: b.createdAt.getTime() - a.createdAt.getTime()
))
)),
create: vi.fn(async ({
data,
}: {
data: {
userId: string;
conversationId: string;
toolName: string;
toolArguments: string;
summary: string;
createdAt: Date;
expiresAt: Date;
};
}) => {
const record = {
id: `approval-${records.size + 1}`,
...data,
status: "PENDING" as const,
approvedAt: null,
cancelledAt: null,
updatedAt: data.createdAt,
};
records.set(record.id, record);
return record;
}),
updateMany: vi.fn(async ({
where,
data,
}: {
where: {
id?: string;
userId?: string;
conversationId?: string;
status?: "PENDING" | "APPROVED" | "CANCELLED" | "EXPIRED";
expiresAt?: { lte?: Date; gt?: Date };
};
data: Partial<{
status: "APPROVED" | "CANCELLED" | "EXPIRED";
cancelledAt: Date;
approvedAt: Date;
}>;
}) => {
let count = 0;
for (const [id, record] of records.entries()) {
if (where.id && record.id !== where.id) continue;
if (where.userId && record.userId !== where.userId) continue;
if (where.conversationId && record.conversationId !== where.conversationId) continue;
if (where.status && record.status !== where.status) continue;
if (where.expiresAt?.lte && record.expiresAt > where.expiresAt.lte) continue;
if (where.expiresAt?.gt && record.expiresAt <= where.expiresAt.gt) continue;
records.set(id, {
...record,
...data,
updatedAt: new Date(),
});
count += 1;
}
return { count };
}),
update: vi.fn(async ({
where,
data,
}: {
where: { id: string };
data: {
status: "APPROVED";
approvedAt: Date;
};
}) => {
const record = records.get(where.id);
if (!record) throw new Error("Record not found");
const next = {
...record,
...data,
updatedAt: new Date(),
};
records.set(where.id, next);
return next;
}),
},
};
}
export function createMissingApprovalTableError() {
return Object.assign(
new Error("The table `public.assistant_approvals` does not exist in the current database."),
{
code: "P2021",
meta: { table: "public.assistant_approvals" },
},
);
}