Skip to content

test(api_remote): value-bearing field coverage + lockstep error-string check - #122

Merged
TaprootFreak merged 8 commits into
release/mainnet-hardeningfrom
test/api-remote-field-coverage
May 27, 2026
Merged

test(api_remote): value-bearing field coverage + lockstep error-string check#122
TaprootFreak merged 8 commits into
release/mainnet-hardeningfrom
test/api-remote-field-coverage

Conversation

@TaprootFreak

@TaprootFreak TaprootFreak commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Tighten node/tests/api_remote.rs with value-bearing field assertions on every endpoint response and a lockstep check against every server-error string the wallet app currently expects.

The goal is not to make the node conform to the app — both are subject to redesign. The goal is to surface every divergence between what the node returns today and what the app reads today, so each gap can be resolved on its merits (sometimes server, sometimes app, sometimes both).

What's added

Field-coverage tests (5 new):

  • mint_response_carries_state_hash_and_coins_root
  • commit_response_carries_state_hash_and_coins_root
  • balance_response_carries_username_after_claim
  • balance_response_has_no_username_for_unclaimed_wallet
  • claim_response_carries_address

Error-contract tests (2 new + extended 8 existing):

  • APP_KNOWN_ERROR_STRINGS constant — 19 strings, identical to app/src/lib/api/errorMessages.ts::KNOWN_SERVER_ERRORS, anchored by a length-check so adding/removing an app string is a hard signal
  • send_returns_structured_error_envelope{success, error} shape on 4xx
  • error_strings_match_known_app_mapping — provokes every reachable string, asserts exact match
  • 8 existing 4xx tests extended with body-content assertions

Divergences surfaced

Each row below is an open design question, not a defect attribution. The test stays red until the divergence is resolved on the more sensible side.

# Divergence Possible resolution
1 Mint response: account_state_hash + output_coins_root shipped as None Node fix likely better: populating them removes the second-round-trip the wallet currently does to derive prev_commitment_pubkey for the next send
2 Commit response: same None pair Same as #1
3 App reads "Invalid hex"; node emits "account_address is not valid hex" App fix likely better: node string is more diagnostic; app should match the family, not a single placeholder
4 App reads "Invalid address length"; node emits "account_address must be 32 bytes (64 hex chars)" Same as #3
5 App reads "Broadcast failed"; node emits "Failed to broadcast commitment inscription on-chain" App fix likely better: node string is more diagnostic
6 App reads "Missing signature"; node skips signature verification entirely when signature field is absent Node fix required: signature should be required server-side, not silently skipped

Coverage matrix

Endpoint Field Value-checked
GET /api/info network, capabilities.{address_list, faucet, usernames, lnurl}, username_domain
GET /api/balance balance, username (present + absent)
POST /api/mint success, error, proof_id, account_state_hash, output_coins_root ✓ (#1)
POST /api/send same
POST /api/commit same ✓ (#2)
POST /api/username/claim username, address
GET /api/username/resolve/:u username, address
Errors (4xx/5xx) structured envelope + 19 known strings ✓ (#3#6)

Test plan

  • cargo fmt --all -- --check clean
  • cargo clippy --workspace --all-features --no-deps -- -D warnings clean
  • cargo check --workspace --all-features --tests clean
  • api-e2e job runs the suite against dev-api.zkcoins.app (triggered via workflow_dispatch from this branch) — expected red on Release: develop -> main #1Add CLAUDE.md with project instructions #6
  • Each divergence resolved in a follow-up PR (node or app, decided per row), with this branch rebased onto develop
  • When all six are resolved, this PR moves Draft → Ready

…lue-bearing field assertions

Adds five new tests in Section 4 that assert every wallet-app-facing
response field by content, not just by presence. Mirrors the existing
strong-assertion block in `send_commit_roundtrip_moves_balance` so a
server bug returning a placeholder zero-hash or a truncated string
fails CI at the API layer instead of in the wallet's integration loop.

  - mint_response_carries_state_hash_and_coins_root
  - commit_response_carries_state_hash_and_coins_root
  - balance_response_carries_username_after_claim
  - claim_response_carries_address
  - balance_response_has_no_username_for_unclaimed_wallet

The mint and commit tests are written against the expected contract
(hash fields populated as 32-byte non-zero hex); the current server
sets them to `None`, so the two tests surface that lockstep gap until
the server is updated to emit the fields.
Every 4xx the wallet app consumes MUST deserialise as
`{ success: false, error: <non-empty string> }` so the client can
branch on the failure reason without re-reading the body. This commit:

  - extends `balance_invalid_hex_returns_422` and
    `balance_wrong_length_returns_422` with body content assertions
    (the balance handler uses a different envelope from
    `handler_error_response`, so the assertion documents that today's
    body is the bare `BalanceResponse { balance: 0 }` with no `error`
    field — surfaces any future refactor that swaps shapes)

  - adds `send_returns_structured_error_envelope` covering the
    `handler_error_response` shape used by every `/api/send` 4xx path
…ssages.ts mapping

Adds a lockstep test against `app/src/lib/api/errorMessages.ts ::
KNOWN_SERVER_ERRORS` so a server-side error rename surfaces in CI
instead of degrading wallet UX to `Serverfehler <status>: <raw>`.

  - new constant `APP_KNOWN_ERROR_STRINGS` mirrors the app's 19-entry
    list (13 from `map_send_coins_error`, 6 from
    `handler_error_response` call sites)

  - new test `error_strings_match_known_app_mapping` provokes each
    reachable string through a single amortised mint:
      reachable: Unknown account address, Signature verification failed,
                 Request timestamp too old or in the future,
                 prev_commitment_pubkey required for account update,
                 Insufficient funds
      mismatch (server emits more-specific text):
                 Invalid hex, Invalid address length
      operator-only / internal-state-only (documented, not provoked):
                 In-coin not present in source's output_coins_root,
                 Source commitment not present in history MMR,
                 Coin is missing commitment,
                 Should provide an inclusion proof,
                 Coin should not exist in coin history tree,
                 Coin should not exist in tree yet,
                 Too many in-coins / out-coins for one transition,
                 prove failed, internal error,
                 Missing signature, Broadcast failed

  - extends `mint_invalid_hex_address_returns_422`,
    `mint_wrong_address_length_returns_422`,
    `send_bad_address_hex_returns_422`,
    `send_unknown_account_returns_404`,
    `send_bad_signature_returns_401`,
    `send_stale_timestamp_returns_401` with body content assertions so
    each negative-path test is also a per-string contract anchor
@TaprootFreak
TaprootFreak marked this pull request as ready for review May 26, 2026 22:36
@TaprootFreak TaprootFreak added the ci:full Trigger heavy CI jobs (Server + Shared Tests + Coverage Gate, ~60-90 min on M3 Ultra) label May 26, 2026
Resolves the four red tests from the field-coverage suite by
fixing the node side of each divergence (the app stays as-is for
these; the app-side family-matching is a separate PR).

N1 mint response: populate account_state_hash + output_coins_root
    (hex-encoded 32-byte digests) so wallet clients have everything
    needed to derive prev_commitment_pubkey for the next send
    without a second GET /api/proof/:id round-trip.

N2 commit response: same pair populated in broadcast_commit_and_deliver,
    so commit-side flows can also pin the resulting state directly.

N3 timestamp window: explicit check_timestamp_window helper runs
    BEFORE verify_send_signature in send/commit/claim handlers,
    emitting "Request timestamp too old or in the future" as its
    own 401 instead of collapsing into "Signature verification
    failed". Clock-skew misconfiguration now surfaces distinctly.

N4 missing signature: signed handlers now reject absent
    signature/timestamp fields with 401 "Missing signature" /
    "Missing timestamp" upstream of crypto verification. Defence-
    in-depth Option-arms stay in verify_send_signature.

Unit tests in router_tests.rs updated to the new response shape
and to the dedicated timestamp string.
… is wired

Replaces the inline "unreachable" comment with a live provocation: POST
/api/send with signature deliberately omitted now returns 401 with
"Missing signature". Mirrors the handler-level gate added in the same
PR. Inventory comment updated to match.
send_bad_address_hex_returns_422: sign the request body so it passes
the new "Missing signature"/timestamp gates that fire upstream of the
per-field hex validator. The hex parser still rejects "0xZZZZZZ" with
422; the test now exercises the hex branch as intended.

error_strings_match_known_app_mapping: the "prev_commitment_pubkey
required" branch is the AccountUpdate transition, which is unreachable
from a wallet that only received a mint (account.proof is still None
→ AccountCreation path). Move the string to the documented-only list
with router_tests + account_node_tests references; the unit-level
coverage is sufficient and saves a publisher-UTXO per CI run.
Adds a handler-level unit test asserting that POST /api/send with a
stale (year-1970) timestamp returns 401 with
"Request timestamp too old or in the future", covering router.rs:675-676
which the existing helper-level `check_timestamp_window_*` tests and
the live `send_stale_timestamp_returns_401` api_remote test exercise
but the coverage-gate nextest pass did not reach.
@TaprootFreak
TaprootFreak changed the base branch from develop to release/mainnet-hardening May 27, 2026 07:44
…overage

Resolve runtime.rs conflict: keep release-branch's
upsert_account_with_source("receive") and short-form digest_to_bytes
(matches the existing use statement at line 28).
@TaprootFreak
TaprootFreak merged commit ced1ee7 into release/mainnet-hardening May 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:full Trigger heavy CI jobs (Server + Shared Tests + Coverage Gate, ~60-90 min on M3 Ultra)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant