Skip to content

feat: integration tests against real public testnet infrastructure#2

Merged
vrogojin merged 4 commits intomainfrom
feat/integration-tests
Apr 24, 2026
Merged

feat: integration tests against real public testnet infrastructure#2
vrogojin merged 4 commits intomainfrom
feat/integration-tests

Conversation

@vrogojin
Copy link
Copy Markdown
Contributor

What

Adds test/integration/ end-to-end tests that run the built CLI against live public Unicity testnet infrastructure — no mocks, no local services.

Endpoints exercised:

  • Nostr relay: wss://nostr-relay.testnet.unicity.network
  • Aggregator: https://goggregator-test.unicity.network
  • IPFS gateway: https://unicity-ipfs1.dyndns.org

Tests (7 total, ~6s):

  • cli-crypto — local crypto/util commands, deterministic
  • cli-walletsphere wallet init --network testnet against real aggregator + IPFS + Nostr
  • cli-dm — self-DM round-trip via the real Nostr relay (exercises NIP-17 gift-wrap publish + subscription)

Harness:

  • vitest.integration.config.ts — sequential single-fork execution with 120s timeouts
  • test/integration/helpers.ts — fresh tmp-dir profile per test, throwaway wallets
  • npm run test:integration — builds + runs the suite

Minor CLI addition: sphere wallet init / sphere wallet status now route to the legacy top-level init / status commands. Previously only wallet list/use/create/current/delete were wired. Unblocks the wallet-init integration test.

Test plan

  • npm test — 21/21 unit tests pass (integration tests excluded from default)
  • npm run test:integration — 7/7 integration tests pass (~6s)
  • SKIP_INTEGRATION=1 npm run test:integration — opt-out works for CI offline

Vladimir Rogojin added 4 commits April 24, 2026 08:47
…ture

Adds end-to-end integration tests under test/integration/ that exercise
the built CLI (bin/sphere.mjs) against the Unicity public testnet:

- wss://nostr-relay.testnet.unicity.network (Nostr relay)
- https://goggregator-test.unicity.network (aggregator JSON-RPC + trustbase)
- https://unicity-ipfs1.dyndns.org (IPFS gateway for identity publish)

Three test files:
- cli-crypto: local crypto/util commands (no network, deterministic)
- cli-wallet: `sphere wallet init --network testnet` against real aggregator
  + IPFS + Nostr; parses emitted identity JSON and asserts L1Address/
  directAddress/chainPubkey shape
- cli-dm: self-DM round-trip via the real Nostr relay; exercises Sphere.init
  + Nostr connect + NIP-17 encryption + gift-wrap publish + inbox retrieval

Harness:
- test/integration/helpers.ts: createSphereEnv() provisions a fresh tmp
  profile dir with seeded testnet config; runSphere() invokes the built
  CLI via spawnSync with generous 90-120s timeouts. integrationSkip via
  SKIP_INTEGRATION=1 for CI opt-out.
- vitest.integration.config.ts: dedicated config with 120s test timeout,
  sequential single-fork execution to avoid relay rate-limits.
- vitest.config.ts: excludes test/integration/** from the default run so
  unit tests stay fast (21/21 in ~1s).
- npm run test:integration: builds + runs integration suite.

Minor CLI addition: `sphere wallet init` and `sphere wallet status` now
route to legacy top-level `init` / `status` commands (previously only
wallet subcommands like list/use/create/current/delete were routed).
This unblocks the wallet-init integration test.

Local verification: all 7 integration tests pass in ~6s total against
the live testnet endpoints.
… verification

HIGH:
- helpers.ts: chmod 0700 on profile dir + config dir so secp256k1 material
  isn't world-readable on shared CI runners
- helpers.ts: startup sweep of stale sphere-cli-it-* dirs (>1h old) cleans
  up after crashed prior runs
- helpers.ts: process-level exit/SIGINT/SIGTERM/uncaughtException handlers
  shred all active SphereEnvs so killed tests don't leak wallet mnemonics
  under /tmp

MEDIUM:
- helpers.ts: env allowlist (PATH, HOME, USER, SHELL, LANG, etc.) instead
  of spreading full process.env — CI secrets (AWS_*, GITHUB_TOKEN, etc.)
  no longer reach the spawned CLI child
- helpers.ts: spawnSync uses killSignal: 'SIGKILL' — hung Nostr/IPFS
  children can't delay tests past the declared timeout
- cli-dm: inbox test now POLLS for the sent DM's nonce (20 tries × 3s)
  instead of just asserting "inbox command ran". Self-DM round-trip is
  now genuinely verified end-to-end via NIP-17 gift-wrap round-trip.
- NEW preflight.integration.test.ts: probes aggregator (HEAD),
  IPFS gateway (HEAD), Nostr relay (TLS handshake). Runs first. Failures
  tell operators whether a red CI is infra or code, rather than making
  them dig through stderr. Does NOT gate downstream tests (vitest
  evaluates skipIf at registration, before preflight's beforeAll) —
  serves as a diagnostic signal only.

LOW:
- helpers.ts: BIN_PATH resolved via import.meta.url at module load,
  removing process.cwd() fragility
- cli-dm: nonce includes random suffix (Math.random) so concurrent CI
  runs don't collide
- cli-dm: explicit expect(directAddress).toBeTruthy() in round-trip test
  prevents silent ordering-dependent pass

src/index.ts: buildLegacyArgv exported with explicit tail parameter so
it's unit-testable. The single live call site still passes
process.argv.slice(3). +28 unit tests covering every namespace branch:
  - wallet init/status remap (the round-1 change that had zero coverage)
  - wallet current/list/create/delete fall-through
  - 1:1 namespaces (balance, daemon, config, completions)
  - faucet → topup
  - nametag register/info/my/sync
  - payments/crypto/util namespace-strip
  - dm send/inbox/history
  - group/market/swap/invoice prefix
  - unknown namespace fall-through

Tests: 21 → 49 unit (+28), 7 → 10 integration (+3 preflight, DM inbox
now real). All pass in ~7s total.
MEDIUM:
- cli-dm: DM inbox polling now bounded to 10 attempts × 2s (max ~60s)
  with a 180s test budget. Round 2's 20×3s could exceed the 90s test
  timeout when each spawn re-inits Sphere (trustbase + Nostr + IPFS).

LOW:
- Rename preflight.integration.test.ts → 00-preflight.integration.test.ts
  so vitest's filename-sorted sequencer actually runs it first (p > c
  sorted after cli-*). Diagnostic signal now lands before the real
  tests, not after — operators see "aggregator is reachable" BEFORE
  digging into test stderr.
- helpers.ts sweep cutoff 1h → 24h. A long vitest --watch debug session
  or legit slow concurrent run cannot have its tmpdir swept mid-flight.
- 00-preflight.ts probeTls: outer hard setTimeout bounds DNS/SYN-drop
  paths that tls.connect's own timeout option does not cover.
- helpers.ts: remove dead `dirname` import + void-suppressor.
- helpers.ts: remove uncaughtException/unhandledRejection handlers —
  they were masking vitest's own failed-test reporting. The `exit` +
  SIGINT/SIGTERM handlers still catch realistic kill paths.

Tests still 10/10 integration + 49/49 unit. No behavior change beyond
the stated fixes.
Schedules the real-testnet integration suite (test/integration/*.integration.test.ts)
to run at 03:07 UTC nightly + on workflow_dispatch for manual triggers.
Kept out of the default push/PR CI because these tests:
  - hit shared public infra (Nostr relay, aggregator, IPFS gateway) and
    shouldn't be triggered on every commit
  - run for minutes end-to-end
  - can flake on transient upstream hiccups

Nightly failures surface in the Actions tab but never block PRs.
On failure, tmp wallet dirs are uploaded as an artifact for debugging.
@vrogojin vrogojin force-pushed the feat/integration-tests branch from e5d3b62 to d5ddab0 Compare April 24, 2026 06:47
@vrogojin vrogojin merged commit 83a9d16 into main Apr 24, 2026
2 checks passed
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