Skip to content
Draft
Show file tree
Hide file tree
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
76 changes: 71 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,66 @@ jobs:
continue-on-error: true
run: pnpm run check:config-api

test-unit:
# Path gate for the test jobs. Docs-only and release-notes-only changes skip
# every test job; Go-only changes skip unit and integration but keep e2e,
# which shells out to the Go sidecar binary; anything under .github or a
# TypeScript workspace runs everything. `check` is never gated. The summary
# jobs fail if this job does not succeed, so a broken gate cannot turn into
# a false green (ADR 0024).
changes:
if: |
!startsWith(github.head_ref, 'release-notes/') &&
(github.event_name == 'merge_group' ||
inputs.force ||
github.event.pull_request.draft == false)
name: Detect changed areas
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
ts: ${{ steps.filter.outputs.ts }}
go: ${{ steps.filter.outputs.go }}
ci: ${{ steps.filter.outputs.ci }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Classify changed paths
id: filter
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
with:
filters: |
ts:
- "apps/cli/**"
- "apps/cli-e2e/**"
- "packages/**"
- "tools/**"
- "patches/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "turbo.json"
- "tsconfig.json"
- "mise.toml"
- "mise.lock"
- ".bun-version"
- "vitest.*"
go:
- "apps/cli-go/**"
ci:
- ".github/**"

test-unit:
needs: changes
if: |
!startsWith(github.head_ref, 'release-notes/') &&
(github.event_name == 'merge_group' ||
inputs.force ||
github.event.pull_request.draft == false) &&
(needs.changes.outputs.ts == 'true' || needs.changes.outputs.ci == 'true')
name: Run unit tests
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
Expand All @@ -101,11 +155,13 @@ jobs:
run: pnpm run test:unit --coverage.enabled

test-integration:
needs: changes
if: |
!startsWith(github.head_ref, 'release-notes/') &&
(github.event_name == 'merge_group' ||
inputs.force ||
github.event.pull_request.draft == false)
github.event.pull_request.draft == false) &&
(needs.changes.outputs.ts == 'true' || needs.changes.outputs.ci == 'true')
name: Run integration tests
runs-on: blacksmith-8vcpu-ubuntu-2404
steps:
Expand All @@ -130,11 +186,15 @@ jobs:
inputs.force ||
github.event.pull_request.draft == false)
name: Run unit and integration tests
needs: [test-unit, test-integration]
needs: [changes, test-unit, test-integration]
runs-on: ubuntu-latest
steps:
- name: Verify unit and integration tests succeeded
run: |
if [ "${{ needs.changes.result }}" != "success" ]; then
echo "::error ::Path gate did not succeed: changes=${{ needs.changes.result }}"
exit 1
fi
if [ "${{ needs.test-unit.result }}" = "failure" ] || [ "${{ needs.test-unit.result }}" = "cancelled" ] || \
[ "${{ needs.test-integration.result }}" = "failure" ] || [ "${{ needs.test-integration.result }}" = "cancelled" ]; then
echo "::error ::Unit or integration tests failed: unit=${{ needs.test-unit.result }}, integration=${{ needs.test-integration.result }}"
Expand All @@ -143,11 +203,13 @@ jobs:
echo "Unit and integration tests reported: unit=${{ needs.test-unit.result }}, integration=${{ needs.test-integration.result }}"

test-e2e:
needs: changes
if: |
!startsWith(github.head_ref, 'release-notes/') &&
(github.event_name == 'merge_group' ||
inputs.force ||
github.event.pull_request.draft == false)
github.event.pull_request.draft == false) &&
(needs.changes.outputs.ts == 'true' || needs.changes.outputs.go == 'true' || needs.changes.outputs.ci == 'true')
name: Run end-to-end tests (shard ${{ matrix.shard }}/3)
runs-on: blacksmith-8vcpu-ubuntu-2404
strategy:
Expand Down Expand Up @@ -204,11 +266,15 @@ jobs:
inputs.force ||
github.event.pull_request.draft == false)
name: Run end-to-end tests
needs: test-e2e
needs: [changes, test-e2e]
runs-on: ubuntu-latest
steps:
- name: Verify all shards succeeded
run: |
if [ "${{ needs.changes.result }}" != "success" ]; then
echo "::error ::Path gate did not succeed: changes=${{ needs.changes.result }}"
exit 1
fi
if [ "${{ needs.test-e2e.result }}" = "failure" ] || [ "${{ needs.test-e2e.result }}" = "cancelled" ]; then
echo "::error ::One or more e2e shards failed: ${{ needs.test-e2e.result }}"
exit 1
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ See `apps/cli/src/commands/login/` as the canonical example.
- `*.unit.test.ts` — unit tests, colocated next to source
- `*.integration.test.ts` — integration tests, colocated next to source
- `*.e2e.test.ts` — end-to-end tests, colocated next to source
- `*.stack.e2e.test.ts` — stack-backed end-to-end tests: e2e files that start a local Supabase stack or run Docker containers. Still e2e, but they run one file at a time while plain e2e files run in parallel (ADR 0024)
- `tests/` — shared test helpers (for example `tests/helpers/cli.ts`)

### Testing pyramid for CLI commands
Expand All @@ -309,6 +310,7 @@ See `apps/cli/src/commands/login/` as the canonical example.
- Do not use e2e tests for help text, argument normalization, dry-run payloads, schema rendering, projection formatting, or similar detail coverage unless the real subprocess boundary itself is the thing being validated.
- If an assertion can be expressed faithfully in an integration test, it should generally live there instead of in e2e.
- When in doubt, move coverage down the pyramid: e2e -> integration -> unit.
- An e2e test that starts a local stack (`supabase start`, `db start`, `createStack`) or runs a Docker container must use the `*.stack.e2e.test.ts` suffix so it joins the serial `e2e-stack` project. A test that only spawns the CLI against an isolated temporary home keeps the plain suffix and runs in parallel.

### Test execution policy

Expand Down
7 changes: 5 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,11 @@ The root unit and integration scripts use Turbo to fan out the package-local
workspace remains package-local because its tests run directly through Go:
`pnpm --dir apps/cli-go run test:unit`. Go tests are covered by the dedicated
Go CI workflow. Unit and integration tasks are uncached for now; e2e tasks are
also uncached and run one package at a time. Forward a Vitest shard to every
e2e package with `pnpm run test:e2e --shard=1/3`.
also uncached and run one package at a time. Within a package, plain
`*.e2e.test.ts` files run in parallel while `*.stack.e2e.test.ts` files (those
that start a local stack or run Docker containers) run serially in the
`e2e-stack` project. Forward a Vitest shard to every e2e package with
`pnpm run test:e2e --shard=1/3`.

## E2E Compatibility Test Suite

Expand Down
1 change: 1 addition & 0 deletions apps/cli/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ Read https://www.effect.solutions/testing for Effect testing patterns. Note that
- `*.unit.test.ts` belongs to the `unit` Vitest project and is the default for unit-style and other fast in-process tests.
- `*.integration.test.ts` belongs to the `integration` project and is for in-process integration tests that exercise real handler or service behavior with layered dependency replacement.
- `*.e2e.test.ts` belongs to the `e2e` Vitest project and is for black-box CLI subprocess tests.
- `*.stack.e2e.test.ts` belongs to the `e2e-stack` Vitest project: black-box CLI subprocess tests that start a local stack or run Docker containers. They run one file at a time; plain `e2e` files run in parallel. Use this suffix whenever the test invokes `start`, `db start`, `stop`, `status`, or otherwise needs a running stack or a container (ADR 0024).
- `*.live.test.ts` belongs to the `live` Vitest project and is for black-box CLI subprocess tests whose asserted command reaches a real Supabase platform or project data plane — see "Live tests" below.

### Testing policy
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"test:integration": "pnpm exec turbo run supabase#test:integration:run --",
"test:integration:run": "bun --bun vitest run --project integration --coverage.reportsDirectory=coverage/integration",
"test:e2e": "pnpm exec turbo run supabase#build && pnpm exec turbo run supabase#test:e2e:run --only --",
"test:e2e:run": "bun --bun vitest run --project e2e",
"test:e2e:run": "bun --bun vitest run --project e2e --project e2e-stack",
"test:live": "bun --bun vitest run --project live",
"test:smoke": "bun run tests/smoke-test.ts",
"types:check": "tsc --noEmit"
Expand Down
22 changes: 22 additions & 0 deletions apps/cli/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,30 @@ export default defineConfig({
ssr: { resolve: workspacePackageSsrResolve },
plugins: [dockerfileTextPlugin()],
test: {
// Stackless e2e: black-box CLI subprocesses against an isolated
// temporary home. Safe under file-level parallelism (see the flake
// policy in AGENTS.md), so Vitest's default parallelism applies.
name: "e2e",
include: ["**/*.e2e.test.ts"],
exclude: ["**/*.stack.e2e.test.ts", "**/node_modules/**"],
globalSetup: ["tests/e2e-global-setup.ts"],
setupFiles: ["tests/e2e-setup.ts"],
testTimeout: 120_000,
hookTimeout: 120_000,
},
},
{
resolve: workspacePackageResolve,
ssr: { resolve: workspacePackageSsrResolve },
plugins: [dockerfileTextPlugin()],
test: {
// Stack-backed e2e: files that start a local Supabase stack or run
// Docker containers claim machine-level resources, so they run one
// file at a time. Vitest schedules this serial group after the
// parallel projects finish. Opt in with the `*.stack.e2e.test.ts`
// suffix (ADR 0024).
name: "e2e-stack",
include: ["**/*.stack.e2e.test.ts"],
fileParallelism: false,
maxWorkers: 1,
globalSetup: ["tests/e2e-global-setup.ts"],
Expand Down
Loading
Loading