Repository navigation
test(mcp): simulate real library shapes in legacy paid_call tests - #608
Miracle656 merged 1 commit into
Conversation
…ibraries throw
Three of the four simulated errors were invented: the ledger-number sentence
appears in no installed package, and the two Response stand-ins carried only
{ok, status, text}, so the ledger's settlement-header decode never ran.
Fixtures now match what the payment path actually produces: @x402/fetch rethrows
a scheme failure as "Failed to create payment payload: <cause>", a rejected
payment comes back as the seller's second 402 Response rather than a thrown
Error, and a settled call echoes X-PAYMENT-RESPONSE.
The trustline case is left as a plain string on purpose. Horizon answers it as
BadResponseError with result_codes at err.response.extras.result_codes, and
extractStructuredSignal reads err.result_codes, so the faithful fixture
classifies as ERR_BAD_REQUEST instead. That is a source gap, not a test one, and
out of scope here.
|
review and magre pls |
…loses #421) The four `paid_call` tests built their stand-ins by hand — `{ ok, status, text }` object literals and error strings invented for the test. That is what made the text-matching in `handlePaidCallError` look safer than it is: the suite was asserting against strings the test itself wrote, not against anything either library produces. Now the error is what `@x402/fetch` actually rethrows for a lapsed signature (`Failed to create payment payload: The transaction has expired`, the cause coming from `@stellar/stellar-sdk`'s own `webauth/utils.js`), the 402 is a real `Response` carrying the x402 error body rather than an object with a `text()`, and the success case is a `Response` whose `X-PAYMENT-RESPONSE` header holds a base64url settlement echo. Each change buys a code path the old stand-in skipped: - The realistic expiry string matches `paymentCouldNotHaveSettled`'s `'failed to create payment payload'` branch, so the test now exercises the spend classification the ledger records — the invented `'Signature expiration ledger 123...'` only reached the `'signatureexpirationledger'` branch. - `fetchWithPayment` does not throw on a rejected payment: it pays, retries, and returns the seller's second response. A real 402 `Response` is what the code actually receives. - A hand-rolled object has no `Headers`, so the success test never ran the header decode that reads the settlement echo. This completes #421. `bazaar_search` (success, empty → `ERR_NO_RESULTS`, non-2xx, non-JSON and unparseable bodies, SSRF refusals), `ListTools` (costs and Zod-derived schemas) and the unknown-tool path were all covered by earlier work; "simulated errors resemble real ones" was the one criterion left. 129 test files / 1,553 tests pass; tsc clean. Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB
|
Merged as This is a small diff doing something genuinely valuable, which is the hardest kind to get credit for. The four What I liked is that each replacement buys a code path the stand-in was skipping, not just realism for its own sake: The expiry string reaches a different branch.
A hand-rolled object has no And citing provenance in the comments — Why this closes the issueThe other four acceptance criteria were already met by work merged earlier, which is easy to miss when the issue text predates it. For the record:
"Simulated errors resemble real ones" was the one box left, and this is it. 76 tests in One observation, not a request
129 test files / 1,553 tests pass. |
Summary
tests/mcp.test.tsstill carries the four originalpaid_calltests. Three of them simulated the upstream with material no dependency produces, so the suite proved the classifier's branch selection works against shapes that never arrive. This rewrites those three fixtures to what the installed payment libraries actually throw and return. Fixture construction only — no titles, no assertions, no source files.ERR_EXPIRED_AUTHORISATION— the old message,Transaction signature expired. Signature expiration ledger 123 is less than current ledger 125, is not a string any dependency emits (grep -ri "signature expired" node_modules→ nothing).@x402/fetch2.24.0 rethrows a scheme failure asFailed to create payment payload: <cause>(dist/esm/index.mjs:48), and the SDK's expiry text isThe transaction has expired(@stellar/stellar-sdk13.3.0,lib/webauth/utils.js:124, thrown asInvalidChallengeError).ERR_PAYMENT_REJECTED— a rejected payment does not throw. The wrapper pays, retries andreturn secondResponse(@x402/fetch/dist/esm/index.mjs:80), so the seller's second 402 reaches the server as aResponsewith an x402 error body, not a{ ok, status, text }stand-in.X-PAYMENT-RESPONSE, whichPaymentLedgerbase64-decodes insrc/mcp/ledger.ts:163. The previous stand-in had noheadersproperty at all, so the call never entered the one code path that reads the settlement echo. It is now a realResponse, andreadBodyCappedconsumes its stream the way it does in production.The fourth is left as-is, deliberately. Horizon answers
op_no_trustwithBadResponseError, whoseresult_codeslive aterr.response.extras.result_codes, whileextractStructuredSignalreadserr.result_codes(src/mcp/codes.ts:118). Measured through the classifier, the faithful fixture returnsERR_BAD_REQUEST, notERR_MISSING_TRUSTLINE. Flipping that assertion inside a tests-only PR would ship a behaviour change disguised as a fixture, so it stays, and the gap belongs in its own issue. Note thatmain's owndescribe('BazaarMcpServer paid_call error classification')block setserr.result_codesdirectly, so it exercises the same field no dependency actually populates.Related issue
Closes #421 — its other four acceptance criteria landed with #521 (
c55a980); this is the remaining "simulated errors resemble real ones" criterion. See the issue thread for the per-criterion audit.Type of change
Checklist
npx tsc --noEmitpasses — exit 0npm run buildpasses —prisma generate+tsc, exit 0npx vitest run tests/mcp.test.ts: 75 passed; full suitevitest run: 1445 passed | 1 skippednode scripts/check-dropped-tests.mjs origin/main HEAD— 1340 cases on base, 1340 on the merge result, exit 0