diff --git a/lib/sandbox/__tests__/getSandboxesHandler.test.ts b/lib/sandbox/__tests__/getSandboxesHandler.test.ts index b957163d..3c762bde 100644 --- a/lib/sandbox/__tests__/getSandboxesHandler.test.ts +++ b/lib/sandbox/__tests__/getSandboxesHandler.test.ts @@ -6,6 +6,7 @@ import { getSandboxesHandler } from "../getSandboxesHandler"; import { validateGetSandboxesRequest } from "../validateGetSandboxesRequest"; import { selectAccountSandboxes } from "@/lib/supabase/account_sandboxes/selectAccountSandboxes"; import { getSandboxStatus } from "../getSandboxStatus"; +import { selectAccountSnapshots } from "@/lib/supabase/account_snapshots/selectAccountSnapshots"; vi.mock("../validateGetSandboxesRequest", () => ({ validateGetSandboxesRequest: vi.fn(), @@ -19,6 +20,10 @@ vi.mock("../getSandboxStatus", () => ({ getSandboxStatus: vi.fn(), })); +vi.mock("@/lib/supabase/account_snapshots/selectAccountSnapshots", () => ({ + selectAccountSnapshots: vi.fn(), +})); + /** * Creates a mock NextRequest for testing. * @@ -34,6 +39,8 @@ function createMockRequest(): NextRequest { describe("getSandboxesHandler", () => { beforeEach(() => { vi.clearAllMocks(); + // Default mock for selectAccountSnapshots - no snapshot exists + vi.mocked(selectAccountSnapshots).mockResolvedValue([]); }); it("returns error response when validation fails", async () => { @@ -61,6 +68,8 @@ describe("getSandboxesHandler", () => { expect(json).toEqual({ status: "success", sandboxes: [], + snapshot_id: null, + github_repo: null, }); }); @@ -98,6 +107,8 @@ describe("getSandboxesHandler", () => { createdAt: "2024-01-01T00:00:00.000Z", }, ], + snapshot_id: null, + github_repo: null, }); }); @@ -238,4 +249,108 @@ describe("getSandboxesHandler", () => { const minEndIndex = Math.min(...endIndices); expect(maxStartIndex).toBeLessThan(minEndIndex); }); + + describe("snapshot_id and github_repo fields", () => { + it("returns snapshot_id and github_repo when account has a snapshot", async () => { + vi.mocked(validateGetSandboxesRequest).mockResolvedValue({ + accountIds: ["acc_123"], + }); + vi.mocked(selectAccountSandboxes).mockResolvedValue([]); + vi.mocked(selectAccountSnapshots).mockResolvedValue([ + { + account_id: "acc_123", + snapshot_id: "snap_abc123", + github_repo: "https://github.com/user/repo", + created_at: "2024-01-01T00:00:00.000Z", + expires_at: "2024-01-08T00:00:00.000Z", + }, + ]); + + const request = createMockRequest(); + const response = await getSandboxesHandler(request); + + expect(response.status).toBe(200); + const json = await response.json(); + expect(json.snapshot_id).toBe("snap_abc123"); + expect(json.github_repo).toBe("https://github.com/user/repo"); + }); + + it("returns null for snapshot_id and github_repo when account has no snapshot", async () => { + vi.mocked(validateGetSandboxesRequest).mockResolvedValue({ + accountIds: ["acc_123"], + }); + vi.mocked(selectAccountSandboxes).mockResolvedValue([]); + vi.mocked(selectAccountSnapshots).mockResolvedValue([]); + + const request = createMockRequest(); + const response = await getSandboxesHandler(request); + + expect(response.status).toBe(200); + const json = await response.json(); + expect(json.snapshot_id).toBeNull(); + expect(json.github_repo).toBeNull(); + }); + + it("returns null github_repo when snapshot exists but has no github_repo", async () => { + vi.mocked(validateGetSandboxesRequest).mockResolvedValue({ + accountIds: ["acc_123"], + }); + vi.mocked(selectAccountSandboxes).mockResolvedValue([]); + vi.mocked(selectAccountSnapshots).mockResolvedValue([ + { + account_id: "acc_123", + snapshot_id: "snap_abc123", + github_repo: null, + created_at: "2024-01-01T00:00:00.000Z", + expires_at: "2024-01-08T00:00:00.000Z", + }, + ]); + + const request = createMockRequest(); + const response = await getSandboxesHandler(request); + + expect(response.status).toBe(200); + const json = await response.json(); + expect(json.snapshot_id).toBe("snap_abc123"); + expect(json.github_repo).toBeNull(); + }); + + it("returns snapshot info for org keys using orgId as accountId", async () => { + vi.mocked(validateGetSandboxesRequest).mockResolvedValue({ + orgId: "org_123", + }); + vi.mocked(selectAccountSandboxes).mockResolvedValue([]); + vi.mocked(selectAccountSnapshots).mockResolvedValue([ + { + account_id: "org_123", + snapshot_id: "snap_org_abc", + github_repo: "https://github.com/org/repo", + created_at: "2024-01-01T00:00:00.000Z", + expires_at: "2024-01-08T00:00:00.000Z", + }, + ]); + + const request = createMockRequest(); + const response = await getSandboxesHandler(request); + + expect(response.status).toBe(200); + const json = await response.json(); + expect(json.snapshot_id).toBe("snap_org_abc"); + expect(json.github_repo).toBe("https://github.com/org/repo"); + expect(selectAccountSnapshots).toHaveBeenCalledWith("org_123"); + }); + + it("calls selectAccountSnapshots with the account ID", async () => { + vi.mocked(validateGetSandboxesRequest).mockResolvedValue({ + accountIds: ["acc_123"], + }); + vi.mocked(selectAccountSandboxes).mockResolvedValue([]); + vi.mocked(selectAccountSnapshots).mockResolvedValue([]); + + const request = createMockRequest(); + await getSandboxesHandler(request); + + expect(selectAccountSnapshots).toHaveBeenCalledWith("acc_123"); + }); + }); }); diff --git a/lib/sandbox/getSandboxesHandler.ts b/lib/sandbox/getSandboxesHandler.ts index af472666..e1baaed4 100644 --- a/lib/sandbox/getSandboxesHandler.ts +++ b/lib/sandbox/getSandboxesHandler.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; import { validateGetSandboxesRequest } from "./validateGetSandboxesRequest"; import { selectAccountSandboxes } from "@/lib/supabase/account_sandboxes/selectAccountSandboxes"; +import { selectAccountSnapshots } from "@/lib/supabase/account_snapshots/selectAccountSnapshots"; import { getSandboxStatus } from "./getSandboxStatus"; import type { SandboxCreatedResponse } from "./createSandbox"; @@ -16,7 +17,7 @@ import type { SandboxCreatedResponse } from "./createSandbox"; * - sandbox_id: Filter to a specific sandbox (must belong to account/org) * * @param request - The request object. - * @returns A NextResponse with array of sandbox statuses. + * @returns A NextResponse with array of sandbox statuses, plus snapshot_id and github_repo. */ export async function getSandboxesHandler(request: NextRequest): Promise { const validated = await validateGetSandboxesRequest(request); @@ -24,8 +25,15 @@ export async function getSandboxesHandler(request: NextRequest): Promise status !== null, ); + // Extract snapshot info + const snapshot_id = snapshots[0]?.snapshot_id ?? null; + const github_repo = snapshots[0]?.github_repo ?? null; + return NextResponse.json( { status: "success", sandboxes, + snapshot_id, + github_repo, }, { status: 200, diff --git a/types/database.types.ts b/types/database.types.ts index e22ee933..27d5db1a 100644 --- a/types/database.types.ts +++ b/types/database.types.ts @@ -305,18 +305,21 @@ export type Database = { account_id: string created_at: string | null expires_at: string + github_repo: string | null snapshot_id: string } Insert: { account_id: string created_at?: string | null expires_at: string + github_repo?: string | null snapshot_id: string } Update: { account_id?: string created_at?: string | null expires_at?: string + github_repo?: string | null snapshot_id?: string } Relationships: [