Two suites, deliberately separated
Unit -- bun test test/unit
40 tests, 0 network calls, ~200ms total. Runs on every change.
E2e -- bun test test/e2e
9 tests, real testnet transactions, ~2 minutes. Run on demand.
What the unit suite actually guards against
Every hand-rolled Soroban XDR encoder insrc/scval-encoders.ts was wrong on at least one real attempt during development — each bug produced a silent on-chain wasm trap, not a TypeScript error, because the SDK happily accepts a structurally-valid-but-semantically-wrong ScVal tree. The unit tests decode the actual produced structure and assert its exact shape:
test/unit/scval-encoders.test.ts
Symbol("Ed25519") sorts before Symbol("Policy"), lexicographically — get this wrong and the signatures map is silently malformed), the MCP tool schema generator’s type mapping, and the address auto-fill heuristic.
What the e2e suite proves live
- policy-gate.e2e.test.ts
- mcp-protocol.e2e.test.ts
Deploys one fresh environment, then drives the real allow/reject sequence directly through
invokeAsWallet:- An allowed, uncapped deposit succeeds
- An allowed withdrawal within the spend cap succeeds
- A withdrawal exceeding the cumulative cap is rejected on-chain (
Auth, InvalidAction) - A call to a method off the allow-list is rejected on-chain
- The wallet owner (unrestricted) can still call the same disallowed method — proving the restriction is agent-signer-specific, not contract-wide
Every e2e test generates and funds its own fresh testnet keys — none of them read
.env, and none of them depend on the state of any previous run. Two runs of the same test file never interfere with each other.