Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 68 additions & 17 deletions tests/scripting.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { beforeAll, describe, expect, test } from "bun:test";
import { createTestWorkspace, type TestWorkspace } from "./helpers.ts";
import { jsonMock } from "./mock-http.ts";
import { VERSION } from "../cli/src/version.ts";
import { createTestWorkspace, type TestWorkspace } from "@/../tests/helpers.ts";
import { jsonMock } from "@/../tests/mock-http.ts";

const UPDATE_TEST_VERSION = `${Number(VERSION.split(".")[0]) + 1}.0.0`;

const statusMocks = {
auth: [jsonMock("GET", "/whoami", { error: "invalid key" }, 401)],
forbidden: [jsonMock("GET", "/whoami", { error: "forbidden" }, 403)],
rate: [jsonMock("GET", "/whoami", { error: "rate limited" }, 429)],
server: [jsonMock("GET", "/whoami", { error: "internal server error" }, 500)],
missing: [jsonMock("GET", "/environments/production/connections/missing", { error: "not found" }, 404)],
missing: [
jsonMock("GET", "/environments/production/connections/missing", { error: "not found" }, 404),
],
conflict: [jsonMock("POST", "/service_accounts", { error: "conflict" }, 409)],
validation: [jsonMock("POST", "/service_accounts", { error: "validation failed" }, 422)],
};
Expand Down Expand Up @@ -58,11 +63,29 @@ describe("scriptable exit codes and JSON errors", () => {

test.each([
["auth", statusMocks.auth, "altertable --json api GET /whoami", 2, "auth_failed"],
["not found", statusMocks.missing, "altertable --json api GET /environments/production/connections/missing", 4, undefined],
[
"not found",
statusMocks.missing,
"altertable --json api GET /environments/production/connections/missing",
4,
undefined,
],
["forbidden", statusMocks.forbidden, "altertable --json api GET /whoami", 3, "forbidden"],
["conflict", statusMocks.conflict, "altertable --json api POST /service_accounts -f label=dup", 5, "conflict"],
[
"conflict",
statusMocks.conflict,
"altertable --json api POST /service_accounts -f label=dup",
5,
"conflict",
],
["rate limit", statusMocks.rate, "altertable --json api GET /whoami", 7, "rate_limited"],
["validation", statusMocks.validation, "altertable --json api POST /service_accounts -f label=bad", 6, "validation_error"],
[
"validation",
statusMocks.validation,
"altertable --json api POST /service_accounts -f label=bad",
6,
"validation_error",
],
["server error", statusMocks.server, "altertable --json api GET /whoami", 8, "server_error"],
])("%s failure emits JSON error envelope", async (_name, mock, command, exitCode, code) => {
await workspace.setupMockHttp(mock);
Expand All @@ -78,30 +101,50 @@ describe("scriptable exit codes and JSON errors", () => {
});

test("missing management credentials exits 10 with configuration_error", async () => {
const isolated = await createTestWorkspace({ ALTERTABLE_API_KEY: undefined, ALTERTABLE_ENV: undefined });
const isolated = await createTestWorkspace({
ALTERTABLE_API_KEY: undefined,
ALTERTABLE_ENV: undefined,
});
try {
const result = await isolated.runCommand("altertable --json api GET /whoami");
expect(result.exitCode).toBe(10);
expect(JSON.parse(result.stderr)).toMatchObject({ error: true, exit_code: 10, code: "configuration_error" });
expect(JSON.parse(result.stderr)).toMatchObject({
error: true,
exit_code: 10,
code: "configuration_error",
});
} finally {
await isolated.cleanup();
}
});

test("network errors exit 9 with network_error", async () => {
const result = await workspace.runCommand("altertable --json api GET /whoami", {
env: { ALTERTABLE_MANAGEMENT_API_BASE: "http://127.0.0.1:1", ALTERTABLE_MOCK_HTTP_FILE: undefined },
env: {
ALTERTABLE_MANAGEMENT_API_BASE: "http://127.0.0.1:1",
ALTERTABLE_MOCK_HTTP_FILE: undefined,
},
});

expect(result.exitCode).toBe(9);
expect(JSON.parse(result.stderr)).toMatchObject({ error: true, exit_code: 9, code: "network_error" });
expect(JSON.parse(result.stderr)).toMatchObject({
error: true,
exit_code: 9,
code: "network_error",
});
});

test("profile show missing uses configuration error semantics", async () => {
const result = await workspace.runCommand("altertable --json profile show --name missing-profile");
const result = await workspace.runCommand(
"altertable --json profile show --name missing-profile",
);

expect(result.exitCode).toBe(10);
expect(JSON.parse(result.stderr)).toMatchObject({ error: true, exit_code: 10, code: "configuration_error" });
expect(JSON.parse(result.stderr)).toMatchObject({
error: true,
exit_code: 10,
code: "configuration_error",
});
});

test("usage errors exit 1", async () => {
Expand All @@ -111,18 +154,26 @@ describe("scriptable exit codes and JSON errors", () => {
});

test.each(["update", "upgrade"])("%s checks an explicit version", async (command) => {
const result = await workspace.runCommand(`altertable ${command} 1.2.0 --check`);
const result = await workspace.runCommand(
`altertable ${command} ${UPDATE_TEST_VERSION} --check`,
);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("Target version v1.2.0 is already installed.");
expect(result.stdout).toContain(
`A new version of altertable is available: v${UPDATE_TEST_VERSION}`,
);
});

test("update accepts inherited global flags after its arguments", async () => {
const json = await workspace.runCommand("altertable update 1.2.0 --check --json");
const plain = await workspace.runCommand("altertable update 1.2.0 --check --no-color");
const json = await workspace.runCommand(
`altertable update ${UPDATE_TEST_VERSION} --check --json`,
);
const plain = await workspace.runCommand(
`altertable update ${UPDATE_TEST_VERSION} --check --no-color`,
);

expect(json.exitCode).toBe(0);
expect(JSON.parse(json.stdout).latest_version).toBe("1.2.0");
expect(JSON.parse(json.stdout).latest_version).toBe(UPDATE_TEST_VERSION);
expect(plain.exitCode).toBe(0);
expect(plain.stdout).not.toContain("\u001B[");
});
Expand Down
Loading