diff --git a/lib/sandbox/__tests__/createSandboxPostHandler.test.ts b/lib/sandbox/__tests__/createSandboxPostHandler.test.ts index 3ba597c3..87892c79 100644 --- a/lib/sandbox/__tests__/createSandboxPostHandler.test.ts +++ b/lib/sandbox/__tests__/createSandboxPostHandler.test.ts @@ -7,6 +7,7 @@ import { validateSandboxBody } from "@/lib/sandbox/validateSandboxBody"; import { createSandbox } from "@/lib/sandbox/createSandbox"; import { insertAccountSandbox } from "@/lib/supabase/account_sandboxes/insertAccountSandbox"; import { triggerRunSandboxCommand } from "@/lib/trigger/triggerRunSandboxCommand"; +import { triggerSetupSandbox } from "@/lib/trigger/triggerSetupSandbox"; import { selectAccountSnapshots } from "@/lib/supabase/account_snapshots/selectAccountSnapshots"; vi.mock("@/lib/sandbox/validateSandboxBody", () => ({ @@ -25,6 +26,10 @@ vi.mock("@/lib/trigger/triggerRunSandboxCommand", () => ({ triggerRunSandboxCommand: vi.fn(), })); +vi.mock("@/lib/trigger/triggerSetupSandbox", () => ({ + triggerSetupSandbox: vi.fn(), +})); + vi.mock("@/lib/supabase/account_snapshots/selectAccountSnapshots", () => ({ selectAccountSnapshots: vi.fn(), })); @@ -56,7 +61,7 @@ describe("createSandboxPostHandler", () => { expect(response.status).toBe(401); }); - it("returns 200 with sandboxes array including runId on success", async () => { + it("returns runId from command task when command is provided", async () => { vi.mocked(validateSandboxBody).mockResolvedValue({ accountId: "acc_123", orgId: null, @@ -79,6 +84,9 @@ describe("createSandboxPostHandler", () => { }, error: null, }); + vi.mocked(triggerSetupSandbox).mockResolvedValue({ + id: "setup_abc123", + }); vi.mocked(triggerRunSandboxCommand).mockResolvedValue({ id: "run_abc123", }); @@ -302,12 +310,11 @@ describe("createSandboxPostHandler", () => { }); }); - it("returns 200 without runId and skips trigger when command is not provided", async () => { + it("returns runId from setup task when no command is provided", async () => { vi.mocked(validateSandboxBody).mockResolvedValue({ accountId: "acc_123", orgId: null, authToken: "token", - // command is not provided (optional) }); vi.mocked(selectAccountSnapshots).mockResolvedValue([]); vi.mocked(createSandbox).mockResolvedValue({ @@ -325,6 +332,9 @@ describe("createSandboxPostHandler", () => { }, error: null, }); + vi.mocked(triggerSetupSandbox).mockResolvedValue({ + id: "setup_abc123", + }); const request = createMockRequest(); const response = await createSandboxPostHandler(request); @@ -339,12 +349,15 @@ describe("createSandboxPostHandler", () => { sandboxStatus: "running", timeout: 600000, createdAt: "2024-01-01T00:00:00.000Z", - // Note: runId is not included when command is not provided + runId: "setup_abc123", }, ], }); - // Verify triggerRunSandboxCommand was NOT called expect(triggerRunSandboxCommand).not.toHaveBeenCalled(); + expect(triggerSetupSandbox).toHaveBeenCalledWith({ + sandboxId: "sbx_123", + accountId: "acc_123", + }); }); it("returns 200 without runId when triggerRunSandboxCommand throws", async () => { @@ -370,12 +383,92 @@ describe("createSandboxPostHandler", () => { }, error: null, }); + vi.mocked(triggerSetupSandbox).mockResolvedValue({ + id: "setup_abc123", + }); vi.mocked(triggerRunSandboxCommand).mockRejectedValue(new Error("Task trigger failed")); const request = createMockRequest(); const response = await createSandboxPostHandler(request); - // Sandbox was created successfully, so return 200 even if trigger fails + // Sandbox was created successfully, so return 200 even if command trigger fails + expect(response.status).toBe(200); + const json = await response.json(); + expect(json).toEqual({ + status: "success", + sandboxes: [ + { + sandboxId: "sbx_123", + sandboxStatus: "running", + timeout: 600000, + createdAt: "2024-01-01T00:00:00.000Z", + }, + ], + }); + }); + + it("calls triggerSetupSandbox with sandboxId and accountId on every creation", async () => { + vi.mocked(validateSandboxBody).mockResolvedValue({ + accountId: "acc_123", + orgId: null, + authToken: "token", + }); + vi.mocked(selectAccountSnapshots).mockResolvedValue([]); + vi.mocked(createSandbox).mockResolvedValue({ + sandboxId: "sbx_789", + sandboxStatus: "running", + timeout: 600000, + createdAt: "2024-01-01T00:00:00.000Z", + }); + vi.mocked(insertAccountSandbox).mockResolvedValue({ + data: { + id: "record_123", + account_id: "acc_123", + sandbox_id: "sbx_789", + created_at: "2024-01-01T00:00:00.000Z", + }, + error: null, + }); + vi.mocked(triggerSetupSandbox).mockResolvedValue({ + id: "setup_xyz", + }); + + const request = createMockRequest(); + await createSandboxPostHandler(request); + + expect(triggerSetupSandbox).toHaveBeenCalledWith({ + sandboxId: "sbx_789", + accountId: "acc_123", + }); + }); + + it("returns 200 without runId when triggerSetupSandbox throws and no command", async () => { + vi.mocked(validateSandboxBody).mockResolvedValue({ + accountId: "acc_123", + orgId: null, + authToken: "token", + }); + vi.mocked(selectAccountSnapshots).mockResolvedValue([]); + vi.mocked(createSandbox).mockResolvedValue({ + sandboxId: "sbx_123", + sandboxStatus: "running", + timeout: 600000, + createdAt: "2024-01-01T00:00:00.000Z", + }); + vi.mocked(insertAccountSandbox).mockResolvedValue({ + data: { + id: "record_123", + account_id: "acc_123", + sandbox_id: "sbx_123", + created_at: "2024-01-01T00:00:00.000Z", + }, + error: null, + }); + vi.mocked(triggerSetupSandbox).mockRejectedValue(new Error("Setup trigger failed")); + + const request = createMockRequest(); + const response = await createSandboxPostHandler(request); + expect(response.status).toBe(200); const json = await response.json(); expect(json).toEqual({ @@ -386,7 +479,6 @@ describe("createSandboxPostHandler", () => { sandboxStatus: "running", timeout: 600000, createdAt: "2024-01-01T00:00:00.000Z", - // Note: runId is not included when trigger fails }, ], }); diff --git a/lib/sandbox/createSandboxPostHandler.ts b/lib/sandbox/createSandboxPostHandler.ts index 86611710..f545fa15 100644 --- a/lib/sandbox/createSandboxPostHandler.ts +++ b/lib/sandbox/createSandboxPostHandler.ts @@ -5,6 +5,7 @@ import { createSandbox } from "@/lib/sandbox/createSandbox"; import { validateSandboxBody } from "@/lib/sandbox/validateSandboxBody"; import { insertAccountSandbox } from "@/lib/supabase/account_sandboxes/insertAccountSandbox"; import { triggerRunSandboxCommand } from "@/lib/trigger/triggerRunSandboxCommand"; +import { triggerSetupSandbox } from "@/lib/trigger/triggerSetupSandbox"; import { selectAccountSnapshots } from "@/lib/supabase/account_snapshots/selectAccountSnapshots"; /** @@ -40,8 +41,19 @@ export async function createSandboxPostHandler(request: NextRequest): Promise ({ + tasks: { + trigger: vi.fn(), + }, +})); + +describe("triggerSetupSandbox", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("triggers setup-sandbox task with correct payload", async () => { + const mockHandle = { id: "run_123" }; + vi.mocked(tasks.trigger).mockResolvedValue(mockHandle); + + const payload = { + sandboxId: "sbx_456", + accountId: "acc_123", + }; + + const result = await triggerSetupSandbox(payload); + + expect(tasks.trigger).toHaveBeenCalledWith("setup-sandbox", payload); + expect(result).toEqual(mockHandle); + }); + + it("passes through the task handle from trigger", async () => { + const mockHandle = { id: "run_789", publicAccessToken: "token_abc" }; + vi.mocked(tasks.trigger).mockResolvedValue(mockHandle); + + const result = await triggerSetupSandbox({ + sandboxId: "sbx_999", + accountId: "acc_456", + }); + + expect(result).toBe(mockHandle); + }); +}); diff --git a/lib/trigger/triggerSetupSandbox.ts b/lib/trigger/triggerSetupSandbox.ts new file mode 100644 index 00000000..275ecb7a --- /dev/null +++ b/lib/trigger/triggerSetupSandbox.ts @@ -0,0 +1,17 @@ +import { tasks } from "@trigger.dev/sdk"; + +type SetupSandboxPayload = { + sandboxId: string; + accountId: string; +}; + +/** + * Triggers the setup-sandbox task to provision a newly created sandbox. + * + * @param payload - The task payload with sandboxId and accountId + * @returns The task handle with runId + */ +export async function triggerSetupSandbox(payload: SetupSandboxPayload) { + const handle = await tasks.trigger("setup-sandbox", payload); + return handle; +}