import { describe, test, expect, vi, beforeEach, afterEach } from "vitest"; // @inquirer/testing/vitest must be imported before modules that use @inquirer/* import { screen } from "@inquirer/testing/vitest"; import { typedPlatformClient, typedBigBrainClient, selectRegion, logNoDefaultRegionMessage, } from "./lib/utils/utils.js"; import { PlatformProjectDetails } from "@convex-dev/platform/managementApi"; import { getDeploymentSelection, getProjectDetails, deploymentNameFromSelection, } from "./lib/deploymentSelection.js"; import { saveSelectedDeployment } from "./deploymentSelect.js"; import { deploymentCreate, resolveRegionDetails, resolveClassDetails, } from "./deploymentCreate.js"; import { ensureBackendBinaryDownloaded } from "./lib/localDeployment/download.js"; import { loadProjectLocalConfig, saveDeploymentConfig, } from "./lib/localDeployment/filePaths.js"; import { chooseLocalBackendPorts, LOCAL_BACKEND_INSTANCE_SECRET, } from "./lib/localDeployment/utils.js"; import { bigBrainStart } from "./lib/localDeployment/bigBrain.js"; vi.mock("@sentry/node", () => ({ captureException: vi.fn(), close: vi.fn(), })); vi.mock("./lib/utils/utils.js", () => ({ typedPlatformClient: vi.fn(), typedBigBrainClient: vi.fn(), selectRegion: vi.fn(), logNoDefaultRegionMessage: vi.fn(), })); vi.mock("./lib/deploymentSelection.js", () => ({ initializeBigBrainAuth: vi.fn(), getDeploymentSelection: vi.fn(), getProjectDetails: vi.fn(), deploymentNameFromSelection: vi.fn().mockReturnValue(null), })); vi.mock("./deploymentSelect.js", () => ({ saveSelectedDeployment: vi.fn(), })); vi.mock("./lib/localDeployment/download.js", () => ({ ensureBackendBinaryDownloaded: vi.fn(), })); vi.mock("./lib/localDeployment/filePaths.js", () => ({ loadProjectLocalConfig: vi.fn(), saveDeploymentConfig: vi.fn(), })); vi.mock("./lib/localDeployment/utils.js", () => ({ chooseLocalBackendPorts: vi.fn(), LOCAL_BACKEND_INSTANCE_SECRET: "MockSecret123", })); vi.mock("./lib/localDeployment/bigBrain.js", () => ({ bigBrainStart: vi.fn(), })); const mockRegions = [ { name: "aws-us-east-1" as const, displayName: "US East (Virginia)", available: true, }, { name: "aws-eu-west-1" as const, displayName: "EU West (Ireland)", available: true, }, ]; const mockClasses = [ { type: "s16" as const, available: true, }, { type: "s256" as const, available: true, }, { type: "d1024" as const, available: false, }, ]; const mockPlatformGet = vi.fn(); const mockPlatformPost = vi.fn(); const mockBigBrainGet = vi.fn(); function setupPlatformClient() { vi.mocked(typedPlatformClient).mockReturnValue({ GET: mockPlatformGet, POST: mockPlatformPost, } as any); vi.mocked(typedBigBrainClient).mockReturnValue({ GET: mockBigBrainGet, } as any); } // Suppress process.exit and stderr beforeEach(() => { vi.spyOn(process, "exit").mockImplementation((() => { throw new Error("process.exit called"); }) as any); vi.spyOn(process.stderr, "write").mockImplementation(() => true); mockPlatformGet.mockReset(); mockPlatformPost.mockReset(); mockBigBrainGet.mockReset(); }); afterEach(() => { vi.restoreAllMocks(); }); const fakeProject = { id: 123, teamId: 456, slug: "my-project", createTime: 0, name: "My Project", teamSlug: "my-team", } satisfies PlatformProjectDetails; const createdDeployment = { kind: "cloud" as const, reference: "dev/my-deployment", deploymentType: "dev" as const, isDefault: false, }; function setupPlatformForCreate(overrides?: Record) { setupPlatformClient(); mockPlatformGet.mockImplementation((path: string) => { if (path === "/teams/{team_id}/list_deployment_regions") { return { data: { items: mockRegions } }; } if (path === "/teams/{team_id}/list_deployment_classes") { return { data: { items: mockClasses } }; } throw new Error(`Unmocked GET route: ${path}`); }); mockPlatformPost.mockResolvedValue({ data: { ...createdDeployment, ...overrides }, }); } describe("non-interactive create flow", () => { beforeEach(() => { vi.mocked(getDeploymentSelection).mockReset(); vi.mocked(getProjectDetails).mockReset(); vi.mocked(saveSelectedDeployment).mockReset(); vi.mocked(deploymentNameFromSelection).mockReset(); vi.mocked(deploymentNameFromSelection).mockReturnValue(null); }); describe("validation errors", () => { test("crashes when no ref and no --default", async () => { await expect( deploymentCreate.parseAsync(["--type", "dev"], { from: "user" }), ).rejects.toThrow(); expect(process.stderr.write).toHaveBeenCalledWith( expect.stringContaining("Specify a deployment ref"), ); }); test("crashes when --type is missing", async () => { await expect( deploymentCreate.parseAsync(["my-deployment"], { from: "user" }), ).rejects.toThrow(); expect(process.stderr.write).toHaveBeenCalledWith( expect.stringContaining("--type is required"), ); }); test("creates a local deployment: downloads binary, chooses ports, registers with Big Brain, saves config", async () => { vi.mocked(getDeploymentSelection).mockResolvedValue({ kind: "existingDeployment", deploymentToActOn: { url: "https://joyful-capybara-123.convex.cloud", adminKey: "admin-key", deploymentFields: { deploymentName: "joyful-capybara-123", deploymentType: "dev", teamSlug: "my-team", projectSlug: "my-project", }, source: "deployKey" as const, }, }); vi.mocked(getProjectDetails).mockResolvedValue(fakeProject); vi.mocked(loadProjectLocalConfig).mockReturnValue(null); vi.mocked(ensureBackendBinaryDownloaded).mockResolvedValue({ binaryPath: "/path", version: "1.0.0", }); vi.mocked(chooseLocalBackendPorts).mockResolvedValue({ cloudPort: 3210, sitePort: 3211, }); vi.mocked(bigBrainStart).mockResolvedValue({ deploymentName: "local-test-123", adminKey: "test-key", }); setupPlatformClient(); mockPlatformGet.mockResolvedValue({ data: { items: [] } }); await deploymentCreate.parseAsync(["local"], { from: "user" }); expect(saveDeploymentConfig).toHaveBeenCalledWith( expect.anything(), "local", "local-test-123", { backendVersion: "1.0.0", ports: { cloud: 3210, site: 3211 }, adminKey: "test-key", instanceSecret: LOCAL_BACKEND_INSTANCE_SECRET, }, ); expect(mockPlatformPost).not.toHaveBeenCalled(); }); test("creates a local deployment with --select and selects it", async () => { vi.mocked(getDeploymentSelection).mockResolvedValue({ kind: "existingDeployment", deploymentToActOn: { url: "https://joyful-capybara-123.convex.cloud", adminKey: "admin-key", deploymentFields: { deploymentName: "joyful-capybara-123", deploymentType: "dev", teamSlug: "my-team", projectSlug: "my-project", }, source: "deployKey" as const, }, }); vi.mocked(getProjectDetails).mockResolvedValue(fakeProject); vi.mocked(loadProjectLocalConfig).mockReturnValue(null); vi.mocked(ensureBackendBinaryDownloaded).mockResolvedValue({ binaryPath: "/path", version: "1.0.0", }); vi.mocked(chooseLocalBackendPorts).mockResolvedValue({ cloudPort: 3210, sitePort: 3211, }); vi.mocked(bigBrainStart).mockResolvedValue({ deploymentName: "local-test-123", adminKey: "test-key", }); setupPlatformClient(); mockPlatformGet.mockResolvedValue({ data: { items: [] } }); await deploymentCreate.parseAsync(["local", "--select"], { from: "user", }); expect(saveSelectedDeployment).toHaveBeenCalledWith( expect.anything(), "local", { kind: "deploymentWithinProject", targetProject: { kind: "deploymentName", deploymentName: "local-test-123", deploymentType: "local", }, selectionWithinProject: { kind: "deploymentSelector", selector: "local", }, }, null, ); }); test("crashes when creating a local deployment with --type", async () => { await expect( deploymentCreate.parseAsync(["local", "--type", "dev"], { from: "user", }), ).rejects.toThrow(); expect(process.stderr.write).toHaveBeenCalledWith( expect.stringContaining( "--type cannot be used when creating a local deployment", ), ); expect(mockPlatformPost).not.toHaveBeenCalled(); }); test("errors when local deployment already exists", async () => { vi.mocked(getDeploymentSelection).mockResolvedValue({ kind: "existingDeployment", deploymentToActOn: { url: "https://joyful-capybara-123.convex.cloud", adminKey: "admin-key", deploymentFields: { deploymentName: "joyful-capybara-123", deploymentType: "dev", teamSlug: "my-team", projectSlug: "my-project", }, source: "deployKey" as const, }, }); vi.mocked(loadProjectLocalConfig).mockReturnValue({ deploymentName: "existing-local-123", config: {} as any, }); await expect( deploymentCreate.parseAsync(["local"], { from: "user" }), ).rejects.toThrow(); expect(process.stderr.write).toHaveBeenCalledWith( expect.stringContaining("A local deployment already exists"), ); }); test.each(["region", "class", "default", "expiration"] as const)( "rejects --%s with local", async (flag) => { const args = ["local", `--${flag}`]; if (flag === "region") args.push("us"); if (flag === "class") args.push("s16"); if (flag === "expiration") args.push("none"); await expect( deploymentCreate.parseAsync(args, { from: "user" }), ).rejects.toThrow(); expect(process.stderr.write).toHaveBeenCalledWith( expect.stringContaining( `--${flag} cannot be used when creating a local deployment`, ), ); }, ); }); describe("with project configured", () => { beforeEach(() => { vi.mocked(getDeploymentSelection).mockResolvedValue({ kind: "existingDeployment", deploymentToActOn: { url: "https://joyful-capybara-123.convex.cloud", adminKey: "admin-key", deploymentFields: { deploymentName: "joyful-capybara-123", deploymentType: "dev", teamSlug: "my-team", projectSlug: "my-project", }, source: "deployKey" as const, }, }); vi.mocked(getProjectDetails).mockResolvedValue(fakeProject); }); test("creates a dev deployment with ref and --type dev", async () => { setupPlatformForCreate(); await deploymentCreate.parseAsync(["my-deployment", "--type", "dev"], { from: "user", }); expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ params: { path: { project_id: 123 } }, body: { type: "dev", region: null, reference: "my-deployment", isDefault: null, }, }), ); }); test("creates a prod deployment with ref and --type prod", async () => { setupPlatformForCreate({ deploymentType: "prod", reference: "staging", }); await deploymentCreate.parseAsync(["staging", "--type", "prod"], { from: "user", }); expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ type: "prod", reference: "staging", }), }), ); }); test("creates a deployment with --default flag", async () => { setupPlatformForCreate({ isDefault: true }); await deploymentCreate.parseAsync( ["my-deployment", "--type", "dev", "--default"], { from: "user" }, ); expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ isDefault: true, }), }), ); }); test("creates a default deployment without a ref", async () => { setupPlatformForCreate({ isDefault: true, reference: null }); await deploymentCreate.parseAsync(["--type", "dev", "--default"], { from: "user", }); expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: { type: "dev", region: null, reference: null, isDefault: true, }, }), ); }); test("creates a deployment with --region full name", async () => { setupPlatformForCreate(); await deploymentCreate.parseAsync( ["my-deployment", "--type", "dev", "--region", "aws-eu-west-1"], { from: "user" }, ); expect(mockPlatformGet).toHaveBeenCalledWith( "/teams/{team_id}/list_deployment_regions", expect.objectContaining({ params: { path: { team_id: "456" } }, }), ); expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ region: "aws-eu-west-1", }), }), ); }); test("creates a deployment with --region alias", async () => { setupPlatformForCreate(); await deploymentCreate.parseAsync( ["my-deployment", "--type", "dev", "--region", "us"], { from: "user" }, ); expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ region: "aws-us-east-1", }), }), ); }); test("fails with invalid --region", async () => { setupPlatformForCreate(); await expect( deploymentCreate.parseAsync( ["my-deployment", "--type", "dev", "--region", "invalid-region"], { from: "user" }, ), ).rejects.toThrow(); expect(process.stderr.write).toHaveBeenCalledWith( expect.stringContaining('Invalid region "invalid-region"'), ); }); test("creates a deployment with --class", async () => { setupPlatformForCreate(); await deploymentCreate.parseAsync( ["my-deployment", "--type", "dev", "--class", "s256"], { from: "user" }, ); expect(mockPlatformGet).toHaveBeenCalledWith( "/teams/{team_id}/list_deployment_classes", expect.objectContaining({ params: { path: { team_id: "456" } }, }), ); expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ class: "s256", }), }), ); }); test("fails with invalid --class", async () => { setupPlatformForCreate(); await expect( deploymentCreate.parseAsync( ["my-deployment", "--type", "dev", "--class", "invalid-class"], { from: "user" }, ), ).rejects.toThrow(); expect(process.stderr.write).toHaveBeenCalledWith( expect.stringContaining('Invalid class "invalid-class"'), ); }); test("fails with unavailable --class", async () => { setupPlatformForCreate(); await expect( deploymentCreate.parseAsync( ["my-deployment", "--type", "dev", "--class", "d1024"], { from: "user" }, ), ).rejects.toThrow(); expect(process.stderr.write).toHaveBeenCalledWith( expect.stringContaining('Invalid class "d1024"'), ); }); test("no --class flag does not include class in body", async () => { setupPlatformForCreate(); await deploymentCreate.parseAsync(["my-deployment", "--type", "dev"], { from: "user", }); const call = mockPlatformPost.mock.calls[0]; expect(call[1].body).not.toHaveProperty("class"); }); test("creates a deployment with --select calls saveSelectedDeployment", async () => { setupPlatformForCreate({ reference: "dev/my-deployment", }); await deploymentCreate.parseAsync( ["my-deployment", "--type", "dev", "--select"], { from: "user" }, ); expect(saveSelectedDeployment).toHaveBeenCalledWith( expect.anything(), "dev/my-deployment", { kind: "deploymentWithinProject", targetProject: { kind: "teamAndProjectSlugs", teamSlug: "my-team", projectSlug: "my-project", }, selectionWithinProject: { kind: "deploymentSelector", selector: "dev/my-deployment", }, }, null, ); }); }); describe("with team:project:ref syntax", () => { test("uses getProjectDetails with teamAndProjectSlugs", async () => { vi.mocked(getProjectDetails).mockResolvedValue({ ...fakeProject, slug: "other-project", teamSlug: "other-team", }); setupPlatformForCreate(); await deploymentCreate.parseAsync( ["other-team:other-project:my-deployment", "--type", "dev"], { from: "user" }, ); expect(getProjectDetails).toHaveBeenCalledWith(expect.anything(), { kind: "teamAndProjectSlugs", teamSlug: "other-team", projectSlug: "other-project", }); expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ reference: "my-deployment", }), }), ); }); }); describe("--expiration flag", () => { beforeEach(() => { vi.mocked(getDeploymentSelection).mockResolvedValue({ kind: "existingDeployment", deploymentToActOn: { url: "https://joyful-capybara-123.convex.cloud", adminKey: "admin-key", deploymentFields: { deploymentName: "joyful-capybara-123", deploymentType: "dev", teamSlug: "my-team", projectSlug: "my-project", }, source: "deployKey" as const, }, }); vi.mocked(getProjectDetails).mockResolvedValue(fakeProject); }); test("--expiration none sends expiresAt: null", async () => { setupPlatformForCreate(); await deploymentCreate.parseAsync( ["my-deployment", "--type", "dev", "--expiration", "none"], { from: "user" }, ); expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ expiresAt: null, }), }), ); }); test("--expiration with timestamp sends correct ms value", async () => { setupPlatformForCreate(); // Use a timestamp far enough in the future to pass validation const futureMs = Date.now() + 2 * 60 * 60 * 1000; // 2 hours from now const futureSec = Math.floor(futureMs / 1000); await deploymentCreate.parseAsync( [ "my-deployment", "--type", "dev", "--expiration", futureSec.toString(), ], { from: "user" }, ); expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ expiresAt: futureSec * 1000, }), }), ); }); test("no --expiration flag does not include expiresAt in body", async () => { setupPlatformForCreate(); await deploymentCreate.parseAsync(["my-deployment", "--type", "dev"], { from: "user", }); const call = mockPlatformPost.mock.calls[0]; expect(call[1].body).not.toHaveProperty("expiresAt"); }); }); describe("without project configured", () => { beforeEach(() => { vi.mocked(getDeploymentSelection).mockResolvedValue({ kind: "chooseProject", } as any); }); test("fails with bare ref when no project context", async () => { await expect( deploymentCreate.parseAsync(["my-deployment", "--type", "dev"], { from: "user", }), ).rejects.toThrow(); expect(process.stderr.write).toHaveBeenCalledWith( expect.stringContaining("No project configured yet"), ); }); test("succeeds with team:project:ref syntax", async () => { vi.mocked(getProjectDetails).mockResolvedValue(fakeProject); setupPlatformForCreate(); await deploymentCreate.parseAsync( ["my-team:my-project:my-deployment", "--type", "dev"], { from: "user" }, ); expect(getProjectDetails).toHaveBeenCalledWith(expect.anything(), { kind: "teamAndProjectSlugs", teamSlug: "my-team", projectSlug: "my-project", }); expect(mockPlatformPost).toHaveBeenCalled(); }); test("hint includes full team:project:ref when no project configured in current directory", async () => { vi.mocked(getProjectDetails).mockResolvedValue(fakeProject); setupPlatformForCreate(); await deploymentCreate.parseAsync( ["my-team:my-project:my-deployment", "--type", "dev"], { from: "user" }, ); expect(process.stderr.write).toHaveBeenCalledWith( expect.stringContaining( "npx convex deployment select my-team:my-project:dev/my-deployment", ), ); }); }); }); describe("interactive create flow", () => { beforeEach(() => { process.stdin.isTTY = true; vi.mocked(getDeploymentSelection).mockReset(); vi.mocked(getProjectDetails).mockReset(); vi.mocked(saveSelectedDeployment).mockReset(); vi.mocked(selectRegion).mockReset(); vi.mocked(deploymentNameFromSelection).mockReset(); vi.mocked(deploymentNameFromSelection).mockReturnValue(null); // Default: project configured via deployment selection vi.mocked(getDeploymentSelection).mockResolvedValue({ kind: "existingDeployment", deploymentToActOn: { url: "https://joyful-capybara-123.convex.cloud", adminKey: "admin-key", deploymentFields: { deploymentName: "joyful-capybara-123", deploymentType: "dev", teamSlug: "my-team", projectSlug: "my-project", }, source: "deployKey" as const, }, }); vi.mocked(getProjectDetails).mockResolvedValue(fakeProject); }); afterEach(() => { process.stdin.isTTY = false; }); function setupPlatformRoutes(routes: Record any>) { setupPlatformClient(); mockPlatformGet.mockImplementation((path: string, args: any) => { for (const [routePath, handler] of Object.entries(routes)) { if (path === routePath || path.startsWith(routePath)) { return { data: handler(args) }; } } throw new Error(`Unmocked GET route: ${path}`); }); mockPlatformPost.mockResolvedValue({ data: { ...createdDeployment }, }); } function setupDefaultRoutes() { setupPlatformRoutes({ "/teams/{team_id}/list_deployment_regions": () => ({ items: mockRegions, }), "/teams/{team_id}/list_deployment_classes": () => ({ items: mockClasses, }), }); mockBigBrainGet.mockResolvedValue({ data: [{ id: 456, slug: "my-team", defaultRegion: "aws-us-east-1" }], }); } test.each([ { deploymentType: "dev" as const, downPresses: 0 }, { deploymentType: "preview" as const, downPresses: 1 }, { deploymentType: "prod" as const, downPresses: 2 }, ])( "selecting $deploymentType calls endpoint with type=$deploymentType", async ({ deploymentType, downPresses }) => { setupDefaultRoutes(); const promise = deploymentCreate.parseAsync([], { from: "user" }); // Type prompt (select) await screen.next(); expect(screen.getScreen()).toContain("Deployment type?"); for (let i = 0; i < downPresses; i++) { screen.keypress("down"); } screen.keypress("enter"); // Ref prompt (input) await screen.next(); expect(screen.getScreen()).toContain( "What do you want to call this deployment", ); screen.type("my-feature"); screen.keypress("enter"); await promise; expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ type: deploymentType, }), }), ); }, ); test("full prompt flow: select type, enter ref", async () => { setupDefaultRoutes(); const promise = deploymentCreate.parseAsync([], { from: "user" }); // Type prompt (select) — "dev" is first choice, just press enter await screen.next(); expect(screen.getScreen()).toContain("Deployment type?"); screen.keypress("enter"); // Ref prompt (input) await screen.next(); expect(screen.getScreen()).toContain( "What do you want to call this deployment", ); screen.type("my-feature"); screen.keypress("enter"); await promise; expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: { type: "dev", region: "aws-us-east-1", reference: "my-feature", isDefault: null, }, }), ); }); test("partial flags: --type dev and ref provided, no prompts needed", async () => { setupDefaultRoutes(); const promise = deploymentCreate.parseAsync( ["my-feature", "--type", "dev"], { from: "user" }, ); await promise; expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ type: "dev", reference: "my-feature", isDefault: null, }), }), ); }); test("invalid ref retry: user enters invalid ref, then valid ref", async () => { setupDefaultRoutes(); const promise = deploymentCreate.parseAsync(["--type", "dev"], { from: "user", }); // Ref prompt — enter invalid ref "dev" await screen.next(); expect(screen.getScreen()).toContain( "What do you want to call this deployment", ); screen.type("dev"); screen.keypress("enter"); // Inline validation error, then edit input await screen.next(); expect(screen.getScreen()).toContain( '"dev" is reserved as an alias for your default dev deployment.', ); screen.type("/my-feature"); screen.keypress("enter"); await promise; expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ reference: "dev/my-feature", isDefault: null, }), }), ); }); test("interactive ref prompt rejects 'local' as a deployment reference", async () => { setupDefaultRoutes(); const promise = deploymentCreate.parseAsync(["--type", "dev"], { from: "user", }); // Ref prompt — enter "local" await screen.next(); expect(screen.getScreen()).toContain( "What do you want to call this deployment", ); screen.type("local"); screen.keypress("enter"); // Inline validation error await screen.next(); expect(screen.getScreen()).toContain( '"local" is reserved as an alias for your local deployment', ); // Fix the input screen.type("ization-improvements"); screen.keypress("enter"); await promise; expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ reference: "localization-improvements", }), }), ); }); test("--region invalid crashes", async () => { setupDefaultRoutes(); await expect( deploymentCreate.parseAsync( ["my-feature", "--type", "dev", "--region", "invalid-region"], { from: "user" }, ), ).rejects.toThrow(); expect(process.stderr.write).toHaveBeenCalledWith( expect.stringContaining('Invalid region "invalid-region"'), ); }); test("no team default region: falls through to selectRegion", async () => { setupDefaultRoutes(); // Override BigBrain to return team without defaultRegion mockBigBrainGet.mockResolvedValue({ data: [{ id: 456, slug: "my-team" }], }); vi.mocked(selectRegion).mockResolvedValue("aws-us-east-1"); await deploymentCreate.parseAsync(["my-feature", "--type", "dev"], { from: "user", }); expect(selectRegion).toHaveBeenCalledWith(expect.anything(), 456, "dev"); expect(logNoDefaultRegionMessage).toHaveBeenCalledWith("my-team"); }); test("uses team default region when no --region provided", async () => { setupDefaultRoutes(); const promise = deploymentCreate.parseAsync( ["my-feature", "--type", "dev"], { from: "user" }, ); await promise; expect(selectRegion).not.toHaveBeenCalled(); expect(mockPlatformPost).toHaveBeenCalledWith( "/projects/{project_id}/create_deployment", expect.objectContaining({ body: expect.objectContaining({ region: "aws-us-east-1", }), }), ); }); }); const availableRegions = mockRegions.filter((r) => r.available); describe("resolveRegionDetails", () => { test("resolves region by alias", () => { const result = resolveRegionDetails(availableRegions, "us"); expect(result).not.toBeNull(); expect(result!.name).toBe("aws-us-east-1"); expect(result!.displayName).toBe("US East (Virginia)"); }); test("resolves region by full name", () => { const result = resolveRegionDetails(availableRegions, "aws-eu-west-1"); expect(result).not.toBeNull(); expect(result!.name).toBe("aws-eu-west-1"); expect(result!.displayName).toBe("EU West (Ireland)"); }); test("returns null on unknown region", () => { const result = resolveRegionDetails(availableRegions, "invalid-region"); expect(result).toBeNull(); }); test("returns null on unavailable region", () => { const result = resolveRegionDetails(availableRegions, "aws-ap-southeast-1"); expect(result).toBeNull(); }); }); const availableClasses = mockClasses.filter((c) => c.available); describe("resolveClassDetails", () => { test("resolves class by type", () => { const result = resolveClassDetails(availableClasses, "s16"); expect(result).not.toBeNull(); expect(result!.type).toBe("s16"); }); test("resolves another class by type", () => { const result = resolveClassDetails(availableClasses, "s256"); expect(result).not.toBeNull(); expect(result!.type).toBe("s256"); }); test("returns null on unknown class", () => { const result = resolveClassDetails(availableClasses, "invalid-class"); expect(result).toBeNull(); }); test("returns null on unavailable class", () => { const result = resolveClassDetails(availableClasses, "d1024"); expect(result).toBeNull(); }); });