import { afterEach, beforeAll, describe, expect, it } from "vitest"; import superjson from "superjson"; import { ObjectId } from "mongodb"; import { collections, ready } from "$lib/server/database"; import { models } from "$lib/server/models"; import { cleanupTestData, createTestUser } from "./testHelpers"; import { POST as createConversation } from "../../../../routes/conversation/+server"; import type { WorkspaceView } from "$lib/types/Workspace"; function requestFor(payload: unknown): Request { return new Request("http://localhost:5173/conversation", { method: "POST", headers: { "content-type": "application/json", "user-agent": "workspace-test" }, body: JSON.stringify(payload), }); } async function post(locals: App.Locals, payload: unknown): Promise { return createConversation({ locals, request: requestFor(payload) } as never); } async function expectBadRequest(locals: App.Locals, payload: unknown) { try { await post(locals, payload); expect.fail("Expected request to fail"); } catch (error) { expect((error as { status?: number }).status).toBe(400); } } beforeAll(async () => { await ready; }, 30_000); describe.sequential("POST /conversation agent workspace persistence", () => { afterEach(async () => { await cleanupTestData(); }); it("persists legacy creates as chat mode without a workspace", async () => { const { locals } = await createTestUser(); const res = await post(locals, { model: models[0].id }); expect(res.status).toBe(200); const payload = (await res.json()) as { conversationId: string; mode: string; workspaceId?: string; conversation: string; }; expect(payload.mode).toBe("chat"); expect(payload.workspaceId).toBeUndefined(); const embedded = superjson.parse<{ mode: string; workspaceId?: string }>(payload.conversation); expect(embedded.mode).toBe("chat"); expect(embedded.workspaceId).toBeUndefined(); const conversation = await collections.conversations.findOne({ _id: new ObjectId(payload.conversationId), }); expect(conversation?.mode).toBe("chat"); expect(conversation?.workspaceId).toBeUndefined(); expect(await collections.workspaces.countDocuments()).toBe(0); }); it("atomically links one normalized, owner-scoped workspace for agent mode", async () => { const { locals, user } = await createTestUser(); const res = await post(locals, { model: models[0].id, mode: "agent", workspace: { repo: { kind: "hf", ref: "test-user/test-space", repoType: "space" }, branch: "agents/first-run", flavor: "cpu-upgrade", model: models[1].id, thinkingLevel: "xhigh", policy: "ask", }, }); const payload = (await res.json()) as { conversationId: string; mode: string; workspaceId: string; workspace: WorkspaceView; conversation: string; }; expect(payload.mode).toBe("agent"); expect(payload.workspace.id).toBe(payload.workspaceId); expect(payload.workspace.repo).toEqual({ kind: "hf", ref: "test-user/test-space", repoType: "space", }); expect(payload.workspace.sandbox).toMatchObject({ status: "unprovisioned", flavor: "cpu-upgrade", }); expect(payload.workspace.sandbox).not.toHaveProperty("nonce"); const conversation = await collections.conversations.findOne({ userId: user._id, }); expect(conversation?.mode).toBe("agent"); expect(conversation?.workspaceId?.toString()).toBe(payload.workspaceId); const workspaces = await collections.workspaces.find({ userId: user._id }).toArray(); expect(workspaces).toHaveLength(1); expect(workspaces[0].conversationId.toString()).toBe(payload.conversationId); expect(workspaces[0]).toMatchObject({ branch: "agents/first-run", flavor: "cpu-upgrade", model: models[1].id, thinkingLevel: "xhigh", policy: "ask", spend: { accruedUsd: 0 }, }); expect(workspaces[0].llmProxy).toBeUndefined(); const embedded = superjson.parse<{ mode: string; workspaceId: string; workspace: WorkspaceView; }>(payload.conversation); expect(embedded.mode).toBe("agent"); expect(embedded.workspaceId).toBe(payload.workspaceId); expect(embedded.workspace.thinkingLevel).toBe("xhigh"); }); it("creates a blank cpu-basic workspace when agent setup is omitted", async () => { const { locals } = await createTestUser(); await post(locals, { model: models[0].id, mode: "agent" }); const workspace = await collections.workspaces.findOne({}); expect(workspace).toMatchObject({ repo: { kind: "blank" }, flavor: "cpu-basic", model: models[0].id, thinkingLevel: "medium", policy: "auto", sandbox: { status: "unprovisioned", flavor: "cpu-basic" }, }); }); it("rejects malformed or mode-incompatible workspace setup", async () => { const { locals } = await createTestUser(); const model = models[0].id; await expectBadRequest(locals, { model, mode: "chat", workspace: { repo: { kind: "blank" } }, }); await expectBadRequest(locals, { model, mode: "agent", workspace: { repo: { kind: "hf", ref: "owner/repo" } }, }); await expectBadRequest(locals, { model, mode: "agent", workspace: { repo: { kind: "git", ref: "http://example.com/repo.git" } }, }); for (const ref of [ "https://user:secret@example.com/repo.git", "https://example.com/repo.git?token=secret", "https://example.com/repo.git#private", ]) { await expectBadRequest(locals, { model, mode: "agent", workspace: { repo: { kind: "git", ref } }, }); } await expectBadRequest(locals, { model, mode: "agent", workspace: { branch: "../escape" }, }); await expectBadRequest(locals, { model, mode: "agent", workspace: { flavor: "not-a-flavor" }, }); expect(await collections.conversations.countDocuments()).toBe(0); expect(await collections.workspaces.countDocuments()).toBe(0); }); });