Skip to content

test: add commands tests infrastructure - #1083

Open
pdesoyres-cc wants to merge 16 commits into
masterfrom
experiment/command-tests
Open

test: add commands tests infrastructure#1083
pdesoyres-cc wants to merge 16 commits into
masterfrom
experiment/command-tests

Conversation

@pdesoyres-cc

@pdesoyres-cc pdesoyres-cc commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Context

The CLI ships without automated command-level coverage — every release leans on manual
smoke testing, so per-command regressions only
surface once users hit them. This branch lays down the infrastructure to drive the CLI
end-to-end against mocked API / KV / Git backends, seeds the first wave of command
tests, and wires both the suite and a per-PR coverage report into CI.

Changes

Test harness

  • Subprocess-based runCli harness (test/cli-runner.ts) with PTY support, interactive
    prompt scripting, and per-interaction ANSI-stripped output segments.
  • Reusable fixtures and mocks (test/fixtures/*, test/mocks/* covering filesystem,
    Git server, Redis, in-process API doublure).
  • CLEVER_BIN switch so the same suite runs against the source tree locally and against
    the packaged binary in CI — catching bundling / pkg regressions a source-tree-only
    suite would miss.
  • test/tests-checklist.md documents the conventions so new command tests stay
    consistent.

Custom test reporter

Node's bundled reporters don't fit a large command-level suite well: TAP buffers output
and the spec reporter prints noisy per-file summaries. A small custom reporter at
test/reporter/reporter.ts, wired into the test npm script, gives:

  • one line per test as it completes (✓ / ✗ / - / !) with relative path and duration —
    progress is visible while a long run is in flight,
  • a single totals line at the end (pass / fail / skip / cancelled, elapsed seconds),
  • a deferred failures block at the very end of the run with each failing test's location
    and full stack trace, so diagnostics aren't buried mid-stream.

First-wave command coverage

Tests added for emails, env import, login, logs, ssh, ssh-keys add,
tokens create, kv, link, deploy, curl, database backups download,
notify-email, and profile switch.

Fixes uncovered while writing the tests

  • ssh: surface pre-marker errors instead of exiting silently.
  • ssh-keys: don't report success when the add call fails; remove a leftover debug log.
  • tokens: read the error code from responseBody directly.
  • cli: clear FORCE_COLOR when disabling colors.
  • git: pick the agent matching the URL scheme.

Refactors that fell out of the test work

  • ssh: extract the marker protocol into its own module.
  • config: centralize config file paths in the schema.
  • login: use the shared openBrowser helper.

CI

  • New tests.yml workflow builds + packages the CLI, then runs the suite against that
    binary on Linux, macOS and Windows.
  • setup-node action caches node_modules so the test job skips npm ci on hit.
  • New per-PR coverage workflow publishes the c8 report to Cellar and posts a sticky
    comment with the link on the PR.
  • scripts/lib/cellar-client.js gains a bounded-concurrency uploadFiles helper used
    by the coverage publisher.

How to review

  1. Start with test/cli-runner.ts and test/cli-hooks.ts — these are the load-bearing
    pieces every command test builds on.
  2. Read test/tests-checklist.md for the conventions, then sample one of the per-command
    test files (e.g. src/commands/database/database.backups.download.command.test.ts)
    to see them applied.
  3. Run locally: npm test for the source tree, or
    npm run build && CLEVER_BIN=<built-bin> npm test for the packaged path.
  4. The per-PR coverage workflow needs the Cellar secrets to be available on the target
    remote — the sticky Code coverage comment is where the report link will appear.

@github-actions

github-actions Bot commented Apr 21, 2026

Copy link
Copy Markdown

🔎 A preview has been automatically published!

If you created the alias to the preview script, you can run this command to download and install this preview:

clever-preview update experiment-command-tests

You can also run it from your local repository:

./scripts/preview.js update experiment-command-tests
OS SHA256 checksum
🐧 linux 5a63a1a1c42e47822dc530dddfa986693db028bf283dca74f0b6bcc0989886ae
🍏 macos d0324f3ff3a65c6b7ae10074e32a179b1744cb3417a007b10f9cc355edf3fed8

This preview will be deleted once this PR is closed.

@pdesoyres-cc
pdesoyres-cc force-pushed the experiment/command-tests branch 3 times, most recently from 689c72a to bee950b Compare April 21, 2026 14:47
@pdesoyres-cc
pdesoyres-cc force-pushed the experiment/command-tests branch from b7736ed to a0b45b7 Compare May 7, 2026 15:43
@pdesoyres-cc
pdesoyres-cc force-pushed the experiment/command-tests branch 5 times, most recently from 809f465 to 93b622c Compare May 21, 2026 19:41
@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown

📊 A code coverage report has been automatically published!

Metric Coverage Covered / Total
Statements 60.25% 9633 / 15986
Branches 87.38% 838 / 959
Functions 25.71% 152 / 591
Lines 60.25% 9633 / 15986

Browse the full HTML report

This report will be deleted once this PR is closed.

@CleverCloud CleverCloud deleted a comment from github-actions Bot May 21, 2026
@pdesoyres-cc
pdesoyres-cc force-pushed the experiment/command-tests branch 2 times, most recently from b34c1f6 to dc4f88f Compare May 22, 2026 15:16
@pdesoyres-cc pdesoyres-cc self-assigned this May 22, 2026
@pdesoyres-cc
pdesoyres-cc force-pushed the experiment/command-tests branch from dc4f88f to 8640490 Compare May 22, 2026 15:42
@pdesoyres-cc
pdesoyres-cc marked this pull request as ready for review May 22, 2026 15:45
@pdesoyres-cc
pdesoyres-cc requested a review from a team as a code owner May 22, 2026 15:45
@pdesoyres-cc
pdesoyres-cc force-pushed the experiment/command-tests branch from 8640490 to a7e7b2a Compare May 22, 2026 15:56
Align login with the rest of the CLI by routing the browser launch through
the shared `openBrowser` helper instead of importing `open` directly. This
keeps the "🌐 Opening …" prefix and debug logging consistent across commands
and removes a duplicated integration point with the `open` package.
The success log sat after the try/catch, so it printed even when `addSshKey` threw — the
catch block returned silently for any error other than id 505. Move the log inside the try
and rethrow unrecognized errors so failures surface to the user.
The check for `invalid-credential` never matched because `responseBody` lives on the error
itself, not under `cause` — same access pattern used elsewhere (ssh-keys, emails, k8s).
Without this fix the generic error message leaked through instead of the friendly
"Invalid credentials" hint.
When ssh fails before printing the start marker (auth failure, connection refused, bad key
permissions, …) the noise filter swallowed every byte and the user got a silent exit-255.
Buffer stderr too and flush both buffers on non-zero exit when the marker was never reached,
so the actual ssh error reaches the terminal.
The plugin always handed isomorphic-git an https.Agent, so requests against plain HTTP
servers couldn't reuse the keep-alive agent and effectively bypassed our timeout config.
Switching on the URL scheme unblocks the deploy command tests, which exercise the git
push path against a local git-http-mock-server served over plain HTTP.
NO_COLOR alone doesn't override FORCE_COLOR — libraries like chalk give FORCE_COLOR
precedence, so colors leaked through whenever the caller's environment set both. Clear
FORCE_COLOR on the no-color path so the suppression actually takes effect.
Move IDS_CACHE_FILE into the shared config schema alongside CONFIGURATION_FILE and
EXPERIMENTAL_FEATURES_FILE, and convert their defaults to lazy factories. Callers in cache.js
and features.js now read the path from config instead of resolving it at import time.

This lets tests point each file at a temporary location through env vars without having to
patch internals, which the upcoming command test suites rely on.
The marker-based stdout filtering used to drive single-command SSH sessions lived inline in
the command handler, which made it impossible to cover with unit tests — every path went
through a real ssh subprocess and `process.stdout`/`process.stderr`. Lift the logic into
`src/models/ssh-marker-protocol.js` with the child process, marker, and output streams
injected, so the protocol (marker handshake, pre-marker buffering, failure-mode flush) can
be exercised in isolation.

No behavior change for the ssh command itself; the handler now just spawns ssh and hands
the subprocess to `runMarkerProtocol`.
Picks up the upstream fix "Ignore stdio on Windows when not waiting for process", which is a
prerequisite for the incoming Windows unit tests — the old behavior interferes with detached
child processes spawned by the test runner.
`validate` calls `typecheck`; defining the dependency before its caller makes the script
block easier to scan.
Introduce a CLI runner harness (test/cli-runner.ts) plus mocks (filesystem, git server,
redis) and fixtures (app config, profile, IDs, errors) so command modules can be exercised
end-to-end without hitting real services. Extend tsconfig to type-check the suite.

Adds first-pass coverage for deploy, login, logs, kv, env import, ssh, ssh-keys, tokens,
emails, notify-email, profile switch, link, curl, and database backups.
Mainly a Windows optimization: NTFS is very slow at writing the thousands of tiny files
`npm ci` produces, so each run paid a heavy install cost. `cache: 'npm'` only caches the
npm download cache, so the slow `node_modules` materialization still happened every time.
Cache `node_modules` directly, keyed on Node version and `package-lock.json`, so a hit
short-circuits install entirely. On miss, run `npm ci` with `--prefer-offline --no-audit
--no-fund` to trim the install further.
…/win

Pair the new command-level test harness with a CI job that exercises the actual built
binary on every supported OS rather than the source tree. The harness reads `CLEVER_BIN`,
so packaging regressions (missing files, broken bundling) surface in tests instead of
only at release smoke-check time.
The upcoming code-coverage workflow needs to push the full HTML report (hundreds of small
files) to Cellar. Issuing them all via Promise.all hits the per-host connection ceiling and
can throttle or fail under load, so route both bulk upload and bulk delete through a shared
worker pool capped at 8 in flight.
Builds on the test harness work to give reviewers visibility into coverage changes: each PR
gets its own report uploaded under a per-branch prefix and a sticky comment summarizing the
totals, while closed PRs and pushes to master keep the bucket tidy.
@pdesoyres-cc
pdesoyres-cc force-pushed the experiment/command-tests branch from a7e7b2a to 9d92711 Compare July 28, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant