test(integration): #156 — swap (Docker e2e) + crypto/util + init-nametag + group + market#14
Merged
Merged
Conversation
added 6 commits
May 15, 2026 17:14
Pins the binary-level CLI plumbing between users and SwapModule for all
8 swap-* commands: namespace bridge, arg parsing, help-text shape, and
pre-getSphere() validation paths.
- Help-shape pins for 7 commands (Usage line + per-flag regex)
- swap-ping HELP_TEXT gap pin (no help entry; locks current behaviour)
- Arg-validation pins: 6 swap-* commands that require args[1] before
wallet load; refactor moving the check below getSphere() flips red
- swap-propose multi-flag guard: missing flags, partial flags,
out-of-range --timeout (60-86400 sec)
18 tests, all offline, ~4.7s total. No infrastructure dependencies.
Live swap lifecycle (Docker escrow + funded wallets) ships in a
follow-up commit on this branch.
Refs #156
… + faucet) Adds end-to-end coverage of the swap CLI surface against a real escrow. Pairs with the offline tier in 744afa7 to give complete pin coverage of the sphere-cli ↔ SwapModule ↔ escrow protocol path. What lands: - test/integration/local-infra/docker-compose.yml — local NIP-29 relay (port 7778; reserved for group/market follow-up — swap uses public testnet relay) - test/integration/local-infra/relay.ts — relay lifecycle helper, ported from /home/vrogojin/trader-service/test/e2e-live/local-infra - test/integration/local-infra/escrow.ts — escrow container spawn + log-poll for `sphere_initialized` direct address. Image defaults to `escrow:local-uxf` (must be locally built against integration/all-fixes — see docstring). Override with SPHERE_CLI_ESCROW_IMAGE. - test/integration/helpers.ts — `createSphereEnv` now accepts `{ extraEnv }` for callers that need to inject env vars (e.g. SPHERE_NOSTR_RELAYS). - test/integration/cli-swap-e2e.integration.test.ts — gated by E2E_RUN_SWAP=1. Two scenarios on the default tier: 1. swap-ping → "Escrow is online" 2. swap-propose → swap-list on counterparty → swap-cancel → confirm absent from default open list Plus a stretch full-settlement scenario behind E2E_RUN_SWAP_FULL=1 (known fragility: deposit-conclude often stalls because bob's --deposit --no-wait submission may not complete before alice's 300s status budget; tracking as follow-up). Why public testnet relay instead of local: adding a local relay to the wallet's SPHERE_NOSTR_RELAYS list (alongside testnet) caused the faucet gift-wrap to never land in the wallet's inbox. Root cause unclear — under investigation. The simpler architecture (everyone on testnet relay) gives a working 2.5-min default e2e tier. Why the local image: ghcr.io/vrogojin/agentic-hosting/escrow:v0.1 (2026-04-25) predates UXF protocol (PR #105), swap race fixes (#115), verifyPayout OVER_COVERAGE (#119), getTokenIdsForInvoice exposure (#128). Building against integration/all-fixes was required. Tests: - Default tier: 2 scenarios pass (~2.5 min) - Skipped without E2E_RUN_SWAP=1 (default CI fast tier) Refs #156
Expands the 49-line cli-crypto.integration.test.ts to a 37-test
table-driven suite covering all 12 crypto/util commands with
HELP_TEXT entries:
generate-key, validate-key, hex-to-wif, derive-pubkey,
derive-address, base58-encode, base58-decode,
to-smallest, to-human, format, encrypt, decrypt
Three layers, all offline (~10s total):
1. Help-shape pins for all 12 (Usage line + per-flag regexes).
2. Arg-validation pins: 11 commands pre-validate args[1] before
getSphere(). Bare invocation → usage hint + non-zero exit.
3. Behaviour pins:
- generate-key emits pubkey + alpha1 address; secrets hidden
- --unsafe-print SECURITY guard: refuses non-TTY (prevents
leaking the freshly-minted privkey into vitest log buffers)
- validate-key true/false JSON output shape + exit code
- hex-to-wif deterministic WIF for stable test privkey
- derive-pubkey is deterministic (literal pin)
- derive-address is deterministic AND index-sensitive
- base58-encode/decode roundtrip ("Hello" ↔ 9Ajdvzr)
- to-smallest/to-human roundtrip via 8-decimal default
- encrypt → OpenSSL "U2FsdGVkX1" magic header
- decrypt JSON-quoted ciphertext → plaintext
- decrypt wrong-pw does NOT yield original plaintext
(CLI exits 0 — CryptoJS AES-CBC has no HMAC; pin documents
current behaviour and catches pathological keystream collisions)
Refs #156
Adds 2 e2e tests pinning the init-time nametag registration in
cli-wallet-lifecycle:
- `sphere init --nametag it_<hex>` returns identity JSON with the
nametag field populated (proves Sphere.init's nametag option
minted on-chain)
- `sphere nametag my` confirms the binding persisted locally
(proves the registerNametag → storage write path)
The combined flow differs from `init` then `nametag register` in
its failure mode: a mid-mint failure can leave wallet stored but
nametag unregistered. Sphere.init handles this defensively; we pin
the happy path so a regression that drops nametag persistence
during init becomes visible.
Note: `payments validate` was in the follow-up gap list but does
NOT actually exist as a command — only `verify-balance` does (which
cli-wallet-state already pins). No action needed there.
Refs #156
Adds 26 tests pinning the namespace-bridge → dispatcher glue for the
two remaining unaddressed CLI namespaces:
group (9 commands, 17 tests):
create / list / my / join / leave / send / messages / members / info
— help-shape + arg-validation pins for the NIP-29 group chat surface.
market (5 commands, 9 tests):
post / search / my / close / feed
— help-shape + arg-validation pins for the P2P bulletin-board surface.
Including market-post's two-step pre-getSphere() guard (description
positional, then --type required flag).
Live e2e tiers deliberately deferred:
- group: would need a NIP-29-capable local relay (the unicity-tokens-
relay used by the swap suite is generic nostr-rs-relay; not
confirmed to handle NIP-29 moderation events). SDK-level coverage
exists in sphere-sdk tests/relay/groupchat-relay.test.ts.
- market: would need long-form NIP-23 relay + broadcast network.
SDK-level coverage covers the module mechanics.
The offline tier here is enough to catch the failure modes that hurt
users most: a refactor renames a flag (silent break), or moves the
arg check below getSphere() (turning "did I type the command right?"
into a 10-second wallet load). Live roundtrips are SDK-team territory.
Refs #156
Addresses code-reviewer feedback before merging into integration/all-fixes.
BLOCKER fix:
- escrow.ts:175 — UNICITY_RELAYS → UNICITY_NOSTR_RELAYS. The escrow
service's acp-adapter reads UNICITY_NOSTR_RELAYS (with
SPHERE_NOSTR_RELAYS fallback). The old var name was silently
ignored, so the container fell back to network defaults regardless
of opts.relayUrl. Worked today only because the e2e suite targets
the public testnet relay (which is also the network default);
pointing at a local Nostr relay would have failed silently.
IMPORTANT fixes:
- escrow.ts:168 — UNICITY_MANAGER_DIRECT_ADDRESS now uses
`DIRECT://<pubkey>` form, not raw pubkey hex. The current escrow
code only checks for non-empty, but a future routing change would
dereference it as a transport address; the placeholder needs to
be syntactically correct.
- helpers.ts — MaxListenersExceededWarning from accumulating exit
handlers (3 per re-evaluation × 14 test files = 42 vs default
limit of 10). Guard against re-registration via a global symbol
so the three process.once handlers fire at most once per worker
regardless of how many test files import this module.
NIT fixes:
- escrow.ts materializeWalletDir — 0700 chmod on the wallet dir
+ mkdir mode hardening. Matches the helpers.ts pattern. Paranoia
for non-POSIX platforms where mkdtemp may inherit DACLs.
- cli-swap-e2e.integration.test.ts — added inline comment on the
full-settlement describe.skipIf block documenting why the tier is
gated, the known failure mode, and likely fix paths.
- cli-crypto.integration.test.ts — replaced "known shortcoming"
wording with explicit TODO(security) marker that flags the
decrypt-wrong-pw-exits-0 path as a CLI defect to be addressed in
a separate PR (move to AES-GCM with backward-compat shim).
Test plan:
- Typecheck: clean
- Offline tier: 162 passing / 55 skipped / 0 failed (44s)
- E2E_RUN_SWAP=1: ping + propose/list/cancel pass (~2.2 min)
- Sigint/sigterm cleanup for the escrow Docker container is NOT
addressed in this commit (reviewer's lowest-severity nit; the
container is visible via `docker ps` and easy to clean up
manually).
Refs #156
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fills the remaining CLI test gaps documented in the PR #13 follow-up comment on issue #156. 5 commits, +1,749 lines, +89 tests across 5 namespaces.
E2E_RUN_SWAP_FULL=1Infrastructure additions
test/integration/local-infra/docker-compose.yml— local Nostr relay (port 7778, image pinned). Reserved for future group/market e2e.test/integration/local-infra/relay.ts— relay lifecycle helper (ported from trader-service).test/integration/local-infra/escrow.ts— escrow container spawn + log-poll forsphere_initialized. Defaults toescrow:local-uxf; override viaSPHERE_CLI_ESCROW_IMAGE.test/integration/helpers.ts—createSphereEnvnow accepts{ extraEnv }for callers needingSPHERE_NOSTR_RELAYSetc.Critical finding — escrow image staleness
The published `ghcr.io/vrogojin/agentic-hosting/escrow:v0.1` image (2026-04-25) is missing:
For the swap e2e to work against current sphere-sdk, the escrow image must be rebuilt locally. Build steps documented in `escrow.ts`:
```
mkdir /tmp/escrow-uxf-build && cd /tmp/escrow-uxf-build
rsync -a --exclude=node_modules --exclude=dist --exclude=.git \
/home/vrogojin/escrow-service/ ./escrow-service/
rsync -a --exclude=node_modules --exclude=dist --exclude=.git \
--exclude=tests --exclude=.claude --exclude=docs --exclude=examples \
/home/vrogojin/uxf/ ./sphere-sdk/
docker build -f escrow-service/Dockerfile -t escrow:local-uxf .
```
Downstream TODO (not blocking this PR): publish `ghcr.io/vrogojin/agentic-hosting/escrow:v0.2` from updated agentic_hosting against current uxf so external CI runs don't need the local-build step.
Test plan
Open follow-ups (not blocking)
Refs #156