Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 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
66 changes: 66 additions & 0 deletions apps/cli/src/legacy/commands/backups/list/list.live.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { expect, test } from "vitest";

import {
describeLive,
describeLiveProject,
requireLiveProjectRef,
runSupabaseLive,
} from "../../../../../tests/helpers/live.ts";

const LIVE_TIMEOUT_MS = 120_000;

// Project-scoped read-only scenario. Skipped unless SUPABASE_LIVE_PROJECT_REF is
// set — i.e. a project has been provisioned on the stack (the cli-e2e-ci runner
// does this; a control-plane-only stack, like local macOS, skips it).
//
// Backups are listed via the Management API control plane (no project DB query),
// so this runs against a freshly provisioned project regardless of data-plane
// health — a new project simply has an empty backups list.
describeLiveProject("supabase backups list (live)", () => {
test("lists backups for the project", { timeout: LIVE_TIMEOUT_MS }, async () => {
const ref = requireLiveProjectRef();
const { exitCode, stdout, stderr } = await runSupabaseLive([
"backups",
"list",
"--project-ref",
ref,
]);
expect(`${stdout}${stderr}`).not.toContain("Unauthorized");
expect(exitCode).toBe(0);
});

test("emits backups as machine-readable JSON", { timeout: LIVE_TIMEOUT_MS }, async () => {
const ref = requireLiveProjectRef();
const { exitCode, stdout } = await runSupabaseLive([
"backups",
"list",
"--project-ref",
ref,
"--output-format",
"json",
]);
expect(exitCode).toBe(0);
// Payload-only JSON shaped like { backups: [...], ... }. A fresh project may
// have zero backups, but the array must always be present.
const parsed = JSON.parse(stdout) as { backups: unknown[] };
expect(Array.isArray(parsed.backups)).toBe(true);
});
});

// Project-scoped error path needing NO provisioned project: a valid token with
// an unknown --project-ref must reach the live Management API, come back 404,
// and exit non-zero (not a crash, not "Unauthorized").
describeLive("supabase backups list — unknown project (live)", () => {
test("fails with a 404 for an unknown project ref", { timeout: LIVE_TIMEOUT_MS }, async () => {
const { exitCode, stdout, stderr } = await runSupabaseLive([
"backups",
"list",
"--project-ref",
"a".repeat(20),
]);
const out = `${stdout}${stderr}`;
expect(exitCode).not.toBe(0);
expect(out).not.toContain("Unauthorized");
expect(out).toContain("404");
});
});
29 changes: 29 additions & 0 deletions apps/cli/src/legacy/commands/branches/get/get.live.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect, test } from "vitest";

import { describeLive, runSupabaseLive } from "../../../../../tests/helpers/live.ts";

const LIVE_TIMEOUT_MS = 120_000;

// `branches get` resolves a branch within a project, so a stable success path
// needs a project that has branching enabled and a known branch — not
// guaranteed on a freshly provisioned project (branch lifecycle coverage is
// tracked separately in CLI-1834).
//
// The portable live signal is the request path + error mapping: a valid token
// with an unknown --project-ref must reach the live Management API, come back
// 404, and exit non-zero. Runs under `describeLive` so it needs no provisioned
// project.
describeLive("supabase branches get — unknown project (live)", () => {
test("fails with a 404 for an unknown project ref", { timeout: LIVE_TIMEOUT_MS }, async () => {
const { exitCode, stdout, stderr } = await runSupabaseLive([
"branches",
"get",
Comment thread
avallete marked this conversation as resolved.
"--project-ref",
"a".repeat(20), // well-formed (20 lowercase chars) but nonexistent ref
]);
const out = `${stdout}${stderr}`;
expect(exitCode).not.toBe(0);
expect(out).not.toContain("Unauthorized");
expect(out).toContain("404");
});
});
36 changes: 36 additions & 0 deletions apps/cli/src/legacy/commands/db/advisors/advisors.live.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { expect, test } from "vitest";

import {
describeLiveDb,
requireLiveDbUrl,
runSupabaseLive,
} from "../../../../../tests/helpers/live.ts";

const LIVE_TIMEOUT_MS = 180_000;

// Data-plane scenario: `db advisors` runs lint queries against the project
// Postgres via --db-url, so it is gated by `describeLiveDb` — it runs only when
// SUPABASE_LIVE_DB_URL is set (the cli-e2e-ci runner resolves the provisioned
// project's pooler URL). Skipped otherwise.
describeLiveDb("supabase db advisors (live)", () => {
test("emits advisor results as machine-readable JSON", { timeout: LIVE_TIMEOUT_MS }, async () => {
const dbUrl = requireLiveDbUrl();
// `--fail-on none` keeps the exit code 0 regardless of which advisories the
// project happens to have, so the test asserts the command path, not the
// project's current lint state.
const { exitCode, stdout } = await runSupabaseLive([
"db",
"advisors",
"--db-url",
dbUrl,
"--fail-on",
"none",
"--output-format",
"json",
]);
expect(exitCode).toBe(0);
// Payload-only JSON shaped like { results: [...] }.
const parsed = JSON.parse(stdout) as { results: unknown[] };
expect(Array.isArray(parsed.results)).toBe(true);
});
});
25 changes: 25 additions & 0 deletions apps/cli/src/legacy/commands/db/dump/dump.live.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { expect, test } from "vitest";

import {
describeLiveDb,
requireLiveDbUrl,
runSupabaseLive,
} from "../../../../../tests/helpers/live.ts";

const LIVE_TIMEOUT_MS = 180_000;

// Data-plane scenario: `db dump` connects to the project Postgres directly via
// --db-url (not the Management API), so it is gated by `describeLiveDb` — it
// runs only when SUPABASE_LIVE_DB_URL is set (the cli-e2e-ci runner resolves the
// provisioned project's pooler URL). Skipped otherwise.
describeLiveDb("supabase db dump (live)", () => {
test("dumps the project schema to stdout", { timeout: LIVE_TIMEOUT_MS }, async () => {
const dbUrl = requireLiveDbUrl();
const { exitCode, stdout, stderr } = await runSupabaseLive(["db", "dump", "--db-url", dbUrl]);
expect(stderr).not.toContain("Unauthorized");
expect(exitCode).toBe(0);
// A real pg_dump of a Supabase project emits SQL DDL to stdout; assert it is
// non-empty rather than pinning an exact header that varies by pg version.
expect(stdout.trim().length).toBeGreaterThan(0);
});
});
28 changes: 28 additions & 0 deletions apps/cli/src/legacy/commands/domains/get/get.live.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { expect, test } from "vitest";

import { describeLive, runSupabaseLive } from "../../../../../tests/helpers/live.ts";

const LIVE_TIMEOUT_MS = 120_000;

// `domains get` reads the custom-hostname config, which the Management API only
// returns once a custom hostname has been configured — a freshly provisioned
// project legitimately has none, so there is no stable success path to assert.
//
// The valuable live signal is the request path + error mapping: a valid token
// with an unknown --project-ref must reach the live Management API, come back
// 404, and exit non-zero (not a crash, not "Unauthorized"). Runs under
// `describeLive` so it needs no provisioned project.
describeLive("supabase domains get — unknown project (live)", () => {
test("fails with a 404 for an unknown project ref", { timeout: LIVE_TIMEOUT_MS }, async () => {
const { exitCode, stdout, stderr } = await runSupabaseLive([
"domains",
"get",
"--project-ref",
"a".repeat(20), // well-formed (20 lowercase chars) but nonexistent ref
]);
const out = `${stdout}${stderr}`;
expect(exitCode).not.toBe(0);
expect(out).not.toContain("Unauthorized");
expect(out).toContain("404");
});
});
30 changes: 30 additions & 0 deletions apps/cli/src/legacy/commands/migration/list/list.live.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect, test } from "vitest";

import {
describeLiveDb,
requireLiveDbUrl,
runSupabaseLive,
} from "../../../../../tests/helpers/live.ts";

const LIVE_TIMEOUT_MS = 180_000;

// Data-plane scenario: `migration list` reads the remote migration history table
// over a direct Postgres connection via --db-url (not the Management API), so it
// is gated by `describeLiveDb` — it runs only when SUPABASE_LIVE_DB_URL is set
// (the cli-e2e-ci runner resolves the provisioned project's pooler URL). Skipped
// otherwise.
describeLiveDb("supabase migration list (live)", () => {
test("lists remote migrations for the project", { timeout: LIVE_TIMEOUT_MS }, async () => {
const dbUrl = requireLiveDbUrl();
const { exitCode, stdout, stderr } = await runSupabaseLive([
"migration",
"list",
"--db-url",
dbUrl,
]);
expect(`${stdout}${stderr}`).not.toContain("Unauthorized");
// A freshly provisioned project may have no applied migrations; the command
// still exits 0 and prints the (possibly empty) history table.
expect(exitCode).toBe(0);
});
});
61 changes: 61 additions & 0 deletions apps/cli/src/legacy/commands/network-bans/get/get.live.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { expect, test } from "vitest";

import {
describeLive,
describeLiveProject,
requireLiveProjectRef,
runSupabaseLive,
} from "../../../../../tests/helpers/live.ts";

const LIVE_TIMEOUT_MS = 120_000;

// Project-scoped read-only scenario. Skipped unless SUPABASE_LIVE_PROJECT_REF is
// set (the cli-e2e-ci runner provisions a project; a control-plane-only stack
// skips it). Reads the project's network bans via the Management API control
// plane — a fresh project has none, which is a valid empty list.
describeLiveProject("supabase network-bans get (live)", () => {
test("gets network bans for the project", { timeout: LIVE_TIMEOUT_MS }, async () => {
const ref = requireLiveProjectRef();
const { exitCode, stdout, stderr } = await runSupabaseLive([
"network-bans",
"get",
"--project-ref",
ref,
]);
expect(`${stdout}${stderr}`).not.toContain("Unauthorized");
expect(exitCode).toBe(0);
});

test("emits network bans as machine-readable JSON", { timeout: LIVE_TIMEOUT_MS }, async () => {
const ref = requireLiveProjectRef();
const { exitCode, stdout } = await runSupabaseLive([
"network-bans",
"get",
"--project-ref",
ref,
"--output-format",
"json",
]);
expect(exitCode).toBe(0);
// Payload-only JSON shaped like { banned_ipv4_addresses: [...] }.
const parsed = JSON.parse(stdout) as { banned_ipv4_addresses: string[] };
expect(Array.isArray(parsed.banned_ipv4_addresses)).toBe(true);
});
});

// Error path needing NO provisioned project: unknown --project-ref → 404 → exit
// non-zero, exercising the request path + error mapping on a control-plane stack.
describeLive("supabase network-bans get — unknown project (live)", () => {
test("fails with a 404 for an unknown project ref", { timeout: LIVE_TIMEOUT_MS }, async () => {
const { exitCode, stdout, stderr } = await runSupabaseLive([
"network-bans",
"get",
"--project-ref",
"a".repeat(20),
]);
const out = `${stdout}${stderr}`;
expect(exitCode).not.toBe(0);
expect(out).not.toContain("Unauthorized");
expect(out).toContain("404");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { expect, test } from "vitest";

import {
describeLive,
describeLiveProject,
requireLiveProjectRef,
runSupabaseLive,
} from "../../../../../tests/helpers/live.ts";

const LIVE_TIMEOUT_MS = 120_000;

// Project-scoped read-only scenario. Skipped unless SUPABASE_LIVE_PROJECT_REF is
// set (the cli-e2e-ci runner provisions a project; a control-plane-only stack
// skips it). Reads the project's network restrictions config via the Management
// API control plane — every project has a config object.
describeLiveProject("supabase network-restrictions get (live)", () => {
test("gets network restrictions for the project", { timeout: LIVE_TIMEOUT_MS }, async () => {
const ref = requireLiveProjectRef();
const { exitCode, stdout, stderr } = await runSupabaseLive([
"network-restrictions",
"get",
"--project-ref",
ref,
]);
expect(`${stdout}${stderr}`).not.toContain("Unauthorized");
expect(exitCode).toBe(0);
});

test(
"emits network restrictions as machine-readable JSON",
{ timeout: LIVE_TIMEOUT_MS },
async () => {
const ref = requireLiveProjectRef();
const { exitCode, stdout } = await runSupabaseLive([
"network-restrictions",
"get",
"--project-ref",
ref,
"--output-format",
"json",
]);
expect(exitCode).toBe(0);
// Payload-only JSON: the restrictions config is a single object, not an array.
const parsed: unknown = JSON.parse(stdout);
expect(typeof parsed).toBe("object");
expect(parsed).not.toBeNull();
expect(Array.isArray(parsed)).toBe(false);
},
);
});

// Error path needing NO provisioned project: unknown --project-ref → 404.
describeLive("supabase network-restrictions get — unknown project (live)", () => {
test("fails with a 404 for an unknown project ref", { timeout: LIVE_TIMEOUT_MS }, async () => {
const { exitCode, stdout, stderr } = await runSupabaseLive([
"network-restrictions",
"get",
"--project-ref",
"a".repeat(20),
]);
const out = `${stdout}${stderr}`;
expect(exitCode).not.toBe(0);
expect(out).not.toContain("Unauthorized");
expect(out).toContain("404");
Comment thread
avallete marked this conversation as resolved.
Outdated
});
});
35 changes: 20 additions & 15 deletions apps/cli/src/legacy/commands/orgs/list/list.live.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,26 @@ describeLive("supabase orgs list (live)", () => {
},
);

test(
"emits machine-readable JSON with --output-format json",
{ timeout: LIVE_TIMEOUT_MS },
async () => {
const { exitCode, stdout } = await runSupabaseLive([
"orgs",
"list",
"--output-format",
"json",
]);
expect(exitCode).toBe(0);
// stdout must be payload-only valid JSON in json mode (no spinner/log noise).
expect(() => JSON.parse(stdout)).not.toThrow();
},
);
test("emits organizations as machine-readable JSON", { timeout: LIVE_TIMEOUT_MS }, async () => {
const { exitCode, stdout } = await runSupabaseLive(["orgs", "list", "--output-format", "json"]);
expect(exitCode).toBe(0);
// Payload-only JSON (no spinner/log noise) shaped like the Go CLI's
// { organizations: [{ id, slug, name }], message }. The live token always
// belongs to at least one org (supabox seeds one), so assert a real row —
// not merely that the output parses.
const parsed = JSON.parse(stdout) as {
organizations: Array<{ id: string; slug: string; name: string }>;
};
expect(Array.isArray(parsed.organizations)).toBe(true);
expect(parsed.organizations.length).toBeGreaterThan(0);
expect(parsed.organizations[0]).toEqual(
expect.objectContaining({
id: expect.any(String),
slug: expect.any(String),
name: expect.any(String),
}),
);
});

// Negative path: a bad token must round-trip to the real Management API, come
// back 401, and surface as a non-zero exit with the upstream "Unauthorized"
Expand Down
Loading
Loading