Skip to content

test: property-based fuzzing, CLI+contract integration, full E2E jour… - #353

Merged
ALLEN-AYODEJI merged 2 commits into
Trellis-Ecosystem:masterfrom
CodePrincess360:feature/testing-and-mobile-responsive
Aug 30, 2026
Merged

test: property-based fuzzing, CLI+contract integration, full E2E jour…#353
ALLEN-AYODEJI merged 2 commits into
Trellis-Ecosystem:masterfrom
CodePrincess360:feature/testing-and-mobile-responsive

Conversation

@CodePrincess360

Copy link
Copy Markdown
Contributor

…ney, and mobile-responsive layout (#141, #142, #143, #120)

Implements the four assigned issues. Each new test layer is runnable locally and wired into CI. Along the way this branch also completes three half-merged changes already on master that left the contract crate, the CLI crate, and the frontend unbuildable — without them none of the new tests could compile or run (details under "Prerequisite fixes" below).

────────────────────────────────────────────────────────────────────────── #141 — Contract fuzzing / property-based testing for the escrow state machine ──────────────────────────────────────────────────────────────────────────

  • New proptest case prop_balance_conservation_random_op_sequence in contracts/trellis_core/src/test_properties.rs. It generates a random sequence (20–60 ops) of lock / submit / approve / dispute / resolve / cancel operations targeting randomly chosen milestones of a 2–4 milestone agreement, applies each one only when it is a legal transition from that milestone's current state (tracked in a local shadow state machine, not read back from the contract), and after every applied op asserts:

    contract token balance == sum of amounts still locked
                              (milestones Funded / WorkSubmitted / Disputed)
    

    which is the issue's total locked - released - refunded invariant. This exercises adversarial interleavings across milestones (e.g. disputing milestone 2 while milestone 0 is mid-approval) that the existing fixed per-milestone lock→submit→approve property tests never construct.

  • Green at the default case count and partway through a PROPTEST_CASES=10000 sweep with no invariant violations; raise the count locally for a deeper run.

  • proptest was already a dev-dependency; module doc comment updated to describe invariant 6. Commits 256 test_snapshots/ fixtures, same as every other property test.

────────────────────────────────────────────────────────────────────────── #142 — CLI + contract integration test suite (end-to-end, real RPC) ──────────────────────────────────────────────────────────────────────────

  • New tests/cli_integration/ — run.sh + README.md. The script starts a local Soroban devnet (stellar container start local), builds the CLI (release) and the contract (wasm32), deploys a fresh contract instance plus a native-XLM SAC token, then drives every CLI command exclusively through the built trellis binary and asserts on its real --json output:
    • full milestone lifecycle: init → status → lock-funds → milestone-status → submit-work (with --proof-uri) → approve-release
    • dispute path: raise-dispute → resolve-dispute --refund-to-payer
    • cancel-milestone on an unfunded milestone
    • error paths: malformed agreement ID (rejected client-side, non-zero exit), double-init, wrong-signer, and --dry-run 22/22 assertions pass against a local devnet.
  • Idempotent: fresh 64-hex agreement IDs per run, and existing local identities are re-funded (an unfunded key from an interrupted earlier run otherwise fails init with "Account not found").
  • CI: new cli-integration job in .github/workflows/contract-ci.yml (needs: verify) — only needs a Docker container, no testnet or secrets.

────────────────────────────────────────────────────────────────────────── #143 — Full user journey E2E across contract + frontend + browser ──────────────────────────────────────────────────────────────────────────

  • Added @playwright/test (dev-dependency), frontend/playwright.config.ts, test:e2e script.
  • frontend/e2e/create-agreement.spec.ts — deploys nothing itself; drives a real Chromium browser through connecting a (mocked) Freighter wallet, creating an agreement on the /create page, and confirming it reads back on /agreement/:id with the right payer/payee/amount/status.
  • frontend/e2e/freighter-mock.ts — fake Freighter extension speaking the real @stellar/freighter-api postMessage wire protocol (reverse- engineered from the bundled extension code, cited in-file). Everything except signing returns a fixed mock response; signing hands the tx XDR out to the Node test process, which signs with the test payer's real key — the key never enters the browser context.
  • tests/e2e/ — run.sh + README.md orchestrating devnet + contract deploy + npm run dev against it + the Playwright run, with a temporary frontend/.env (backed up and restored on exit) and an explicit timeout on every step that could hang. 1/1 passing locally.
  • CI: new e2e job in .github/workflows/frontend-ci.yml, workflow_dispatch only (needs Docker + a real browser, far heavier than the rest).
  • frontend/.gitignore: ignore Playwright's test-results / playwright-report.

────────────────────────────────────────────────────────────────────────── #120 — Mobile-first responsive layout
──────────────────────────────────────────────────────────────────────────

  • Navbar: real mobile menu — hamburger/close toggle (44x44 targets, full aria wiring), slide-down panel, auto-closes on navigation; nav links reduced to one NAV_LINKS source of truth shared by desktop + mobile; header no longer overflows on narrow widths (flex-wrap, tighter gaps).
  • Milestones on the status page: desktop keeps the table (MilestoneRow); <768px now renders a single-column card list (new MilestoneCard) instead of a horizontally-scrolling table. Shared action/state logic extracted to hooks/useMilestoneActions.ts so the two layouts can't drift.
  • Touch targets: min-h-[44px] on the action buttons / links / inputs across CreatePage, StatusPage, AgreementHistoryPage, MilestoneCard, Navbar.
  • Overflow fixes: min-w-0 on flex inputs so long values don't force horizontal page scroll; break-all on full-length identifiers in ExplorerLink; ToastProvider pinned to both edges on mobile, max-w-sm only from sm: up.
  • Verified at 320 / 375 / 414 / 768 / 1024 px — no horizontal scroll, no overlap.

Wiring needed to make #120/#143 testable through the real UI:

  • frontend/src/lib/soroban.ts: implemented getAgreement() (was a throw-stub) — simulates get_agreement over RPC and decodes the ScVal result, plus agreementIdToScVal / milestoneToScVal encoders.
  • CreatePage: the init form now actually builds the ScVal args and calls the contract via useContractInvoke (was a console.log stub); client-side validation of the hex agreement ID and positive integer amounts; label htmlFor/id so the fields are reachable by accessible name.
  • StatusPage: use the shared getAgreement(); drop the local throw-stub.

────────────────────────────────────────────────────────────────────────── Prerequisite fixes — half-merged changes already on master that don't build ────────────────────────────────────────────────────────────────────────── These are not part of the four issues, but master (67a358f) does not compile without them and the new tests can't run otherwise:

  • contracts/trellis_core/src/events.rs: the event topic symbols added by the earlier "namespace event topics with trlls_ prefix" change are 10 chars — symbol_short! hard-caps at 9, so the crate failed to compile. Shortened trlls_*trls_* (9 chars, same de-vowelled scheme). Every test_properties snapshot is regenerated as a result: the topic symbol is part of the recorded event, so ~11.8k *.json snapshots change (symbol bytes only; i128 values shift only because proptest inputs are seeded off the now-passing run).
  • cli/trellis_cli/src/{main.rs,commands/mod.rs}: confirm_action was called in three handlers but never defined, and main.rs called Config::from_env() which had been replaced by Config::resolve(...) — the CLI crate didn't compile. Added confirm_action (honours --yes, skips on --dry-run, fails closed under --quiet), wired every state- mutating handler to it and to validate_agreement_id, and switched main to Config::resolve so --network / --rpc-url / --network-passphrase (needed to point the CLI at a local devnet for No integration test suite: CLI + contract interaction is never tested end-to-end #142) take effect. Also JSON-quote --proof-uri before handing it to stellar contract invoke, which parses every non-bytes arg as JSON.
  • frontend: main.tsx imported BrowserRouter but never wrapped <App> in it, so every route hook threw; useContractInvoke.ts / useContractRead.ts imported SorobanRpc, which the pinned @stellar/stellar-sdk (16.x) no longer exports (it's rpc now); StatusPage.tsx used <ExplorerLink> without importing it. All three fixed.

Not addressed (pre-existing on master, unrelated to these issues): the frontend npm run typecheck / npm run build still fail on ~8 files this branch does not touch (NetworkBackground.tsx null checks, EventFeed JSX namespace, StatsBar/AgreementIdGenerator test imports, ThemeContext type-only import, CreateAgreementPage ScValType, etc.), and 32 pre-existing vitest failures from test files that render router-hook components without a . Every file changed here is type-clean; flagging separately rather than expanding scope.

Test status

  • contracts: cargo test --workspace green — 22 lib tests (incl. the new fuzz case) + 38 CLI tests.
  • CLI integration: tests/cli_integration/run.sh — 22/22 against local devnet.
  • E2E: tests/e2e/run.sh — 1/1 against local devnet + Chromium.
  • frontend vitest: 95 pass / 32 fail — same 32 as before this branch (pre-existing, see above).

Closes #141
Closes #142
Closes #143
Closes #120

…ney, and mobile-responsive layout (Trellis-Ecosystem#141, Trellis-Ecosystem#142, Trellis-Ecosystem#143, Trellis-Ecosystem#120)

Implements the four assigned issues. Each new test layer is runnable locally
and wired into CI. Along the way this branch also completes three half-merged
changes already on master that left the contract crate, the CLI crate, and
the frontend unbuildable — without them none of the new tests could compile
or run (details under "Prerequisite fixes" below).

──────────────────────────────────────────────────────────────────────────
Trellis-Ecosystem#141 — Contract fuzzing / property-based testing for the escrow state machine
──────────────────────────────────────────────────────────────────────────
* New proptest case `prop_balance_conservation_random_op_sequence` in
  contracts/trellis_core/src/test_properties.rs. It generates a random
  sequence (20–60 ops) of lock / submit / approve / dispute / resolve /
  cancel operations targeting randomly chosen milestones of a 2–4 milestone
  agreement, applies each one only when it is a legal transition from that
  milestone's current state (tracked in a local shadow state machine, not
  read back from the contract), and after every applied op asserts:

      contract token balance == sum of amounts still locked
                                (milestones Funded / WorkSubmitted / Disputed)

  which is the issue's `total locked - released - refunded` invariant. This
  exercises adversarial interleavings across milestones (e.g. disputing
  milestone 2 while milestone 0 is mid-approval) that the existing fixed
  per-milestone lock→submit→approve property tests never construct.
* Green at the default case count and partway through a
  `PROPTEST_CASES=10000` sweep with no invariant violations; raise the count
  locally for a deeper run.
* proptest was already a dev-dependency; module doc comment updated to
  describe invariant 6. Commits 256 `test_snapshots/` fixtures, same as
  every other property test.

──────────────────────────────────────────────────────────────────────────
Trellis-Ecosystem#142 — CLI + contract integration test suite (end-to-end, real RPC)
──────────────────────────────────────────────────────────────────────────
* New tests/cli_integration/ — run.sh + README.md. The script starts a local
  Soroban devnet (`stellar container start local`), builds the CLI (release)
  and the contract (wasm32), deploys a fresh contract instance plus a
  native-XLM SAC token, then drives every CLI command exclusively through the
  built `trellis` binary and asserts on its real `--json` output:
    - full milestone lifecycle: init → status → lock-funds → milestone-status
      → submit-work (with --proof-uri) → approve-release
    - dispute path: raise-dispute → resolve-dispute --refund-to-payer
    - cancel-milestone on an unfunded milestone
    - error paths: malformed agreement ID (rejected client-side, non-zero
      exit), double-init, wrong-signer, and --dry-run
  22/22 assertions pass against a local devnet.
* Idempotent: fresh 64-hex agreement IDs per run, and existing local
  identities are re-funded (an unfunded key from an interrupted earlier run
  otherwise fails init with "Account not found").
* CI: new `cli-integration` job in .github/workflows/contract-ci.yml
  (needs: verify) — only needs a Docker container, no testnet or secrets.

──────────────────────────────────────────────────────────────────────────
Trellis-Ecosystem#143 — Full user journey E2E across contract + frontend + browser
──────────────────────────────────────────────────────────────────────────
* Added @playwright/test (dev-dependency), frontend/playwright.config.ts,
  `test:e2e` script.
* frontend/e2e/create-agreement.spec.ts — deploys nothing itself; drives a
  real Chromium browser through connecting a (mocked) Freighter wallet,
  creating an agreement on the /create page, and confirming it reads back
  on /agreement/:id with the right payer/payee/amount/status.
* frontend/e2e/freighter-mock.ts — fake Freighter extension speaking the
  real `@stellar/freighter-api` postMessage wire protocol (reverse-
  engineered from the bundled extension code, cited in-file). Everything
  except signing returns a fixed mock response; signing hands the tx XDR out
  to the Node test process, which signs with the test payer's real key — the
  key never enters the browser context.
* tests/e2e/ — run.sh + README.md orchestrating devnet + contract deploy +
  `npm run dev` against it + the Playwright run, with a temporary
  frontend/.env (backed up and restored on exit) and an explicit timeout on
  every step that could hang. 1/1 passing locally.
* CI: new `e2e` job in .github/workflows/frontend-ci.yml, `workflow_dispatch`
  only (needs Docker + a real browser, far heavier than the rest).
* frontend/.gitignore: ignore Playwright's test-results / playwright-report.

──────────────────────────────────────────────────────────────────────────
Trellis-Ecosystem#120 — Mobile-first responsive layout
──────────────────────────────────────────────────────────────────────────
* Navbar: real mobile menu — hamburger/close toggle (44x44 targets, full
  aria wiring), slide-down panel, auto-closes on navigation; nav links
  reduced to one NAV_LINKS source of truth shared by desktop + mobile;
  header no longer overflows on narrow widths (flex-wrap, tighter gaps).
* Milestones on the status page: desktop keeps the table (MilestoneRow);
  <768px now renders a single-column card list (new MilestoneCard) instead
  of a horizontally-scrolling table. Shared action/state logic extracted to
  hooks/useMilestoneActions.ts so the two layouts can't drift.
* Touch targets: min-h-[44px] on the action buttons / links / inputs across
  CreatePage, StatusPage, AgreementHistoryPage, MilestoneCard, Navbar.
* Overflow fixes: min-w-0 on flex inputs so long values don't force
  horizontal page scroll; break-all on full-length identifiers in
  ExplorerLink; ToastProvider pinned to both edges on mobile, max-w-sm only
  from sm: up.
* Verified at 320 / 375 / 414 / 768 / 1024 px — no horizontal scroll, no
  overlap.

Wiring needed to make Trellis-Ecosystem#120/Trellis-Ecosystem#143 testable through the real UI:
* frontend/src/lib/soroban.ts: implemented getAgreement() (was a throw-stub)
  — simulates get_agreement over RPC and decodes the ScVal result, plus
  agreementIdToScVal / milestoneToScVal encoders.
* CreatePage: the init form now actually builds the ScVal args and calls the
  contract via useContractInvoke (was a console.log stub); client-side
  validation of the hex agreement ID and positive integer amounts; label
  htmlFor/id so the fields are reachable by accessible name.
* StatusPage: use the shared getAgreement(); drop the local throw-stub.

──────────────────────────────────────────────────────────────────────────
Prerequisite fixes — half-merged changes already on master that don't build
──────────────────────────────────────────────────────────────────────────
These are not part of the four issues, but master (67a358f) does not compile
without them and the new tests can't run otherwise:

* contracts/trellis_core/src/events.rs: the event topic symbols added by the
  earlier "namespace event topics with trlls_ prefix" change are 10 chars —
  `symbol_short!` hard-caps at 9, so the crate failed to compile. Shortened
  `trlls_*` → `trls_*` (9 chars, same de-vowelled scheme). Every
  test_properties snapshot is regenerated as a result: the topic symbol is
  part of the recorded event, so ~11.8k *.json snapshots change (symbol
  bytes only; i128 values shift only because proptest inputs are seeded off
  the now-passing run).
* cli/trellis_cli/src/{main.rs,commands/mod.rs}: `confirm_action` was called
  in three handlers but never defined, and `main.rs` called
  `Config::from_env()` which had been replaced by `Config::resolve(...)` —
  the CLI crate didn't compile. Added `confirm_action` (honours --yes,
  skips on --dry-run, fails closed under --quiet), wired every state-
  mutating handler to it and to `validate_agreement_id`, and switched
  `main` to `Config::resolve` so `--network` / `--rpc-url` /
  `--network-passphrase` (needed to point the CLI at a local devnet for
  Trellis-Ecosystem#142) take effect. Also JSON-quote `--proof-uri` before handing it to
  `stellar contract invoke`, which parses every non-bytes arg as JSON.
* frontend: `main.tsx` imported `BrowserRouter` but never wrapped `<App>` in
  it, so every route hook threw; `useContractInvoke.ts` / `useContractRead.ts`
  imported `SorobanRpc`, which the pinned @stellar/stellar-sdk (16.x) no
  longer exports (it's `rpc` now); `StatusPage.tsx` used `<ExplorerLink>`
  without importing it. All three fixed.

Not addressed (pre-existing on master, unrelated to these issues): the
frontend `npm run typecheck` / `npm run build` still fail on ~8 files this
branch does not touch (NetworkBackground.tsx null checks, EventFeed JSX
namespace, StatsBar/AgreementIdGenerator test imports, ThemeContext
type-only import, CreateAgreementPage ScValType, etc.), and 32 pre-existing
vitest failures from test files that render router-hook components without a
<MemoryRouter>. Every file changed here is type-clean; flagging separately
rather than expanding scope.

Test status
* contracts: `cargo test --workspace` green — 22 lib tests (incl. the new
  fuzz case) + 38 CLI tests.
* CLI integration: tests/cli_integration/run.sh — 22/22 against local devnet.
* E2E: tests/e2e/run.sh — 1/1 against local devnet + Chromium.
* frontend vitest: 95 pass / 32 fail — same 32 as before this branch
  (pre-existing, see above).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7zsKzioyWdXyuss8rkWtx
@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@CodePrincess360 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

# Conflicts:
#	.github/workflows/contract-ci.yml
#	cli/trellis_cli/src/commands/mod.rs
#	cli/trellis_cli/src/main.rs
#	contracts/trellis_core/src/events.rs
#	frontend/src/components/toast/ToastProvider.tsx
#	frontend/src/hooks/useContractInvoke.ts
#	frontend/src/pages/CreatePage.tsx
@ALLEN-AYODEJI
ALLEN-AYODEJI merged commit cac7bf4 into Trellis-Ecosystem:master Aug 30, 2026
3 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment