External API contract tests: Alfredpay, Avenia, price feeds (milestones 2-4)#1254
Open
ebma wants to merge 4 commits into
Open
External API contract tests: Alfredpay, Avenia, price feeds (milestones 2-4)#1254ebma wants to merge 4 commits into
ebma wants to merge 4 commits into
Conversation
Milestone 2 of docs/features/contract-tests.md: consumed-contract zod schemas for configs, quotes (both directions), onramp/offramp order creation and polling, fiat accounts, KYC status, and the 409 trade-limit error body — parsed against the fake (hermetic, PR-blocking) and the partner API (live, nightly). Order-creating live tests are gated behind pre-provisioned sandbox fixtures and stop before any payment. Live verification surfaced a wire-type lie: the limit-breach errorMetadata quantities arrive as JSON numbers, not strings — the client now stringifies them at the boundary so AlfredpayTradeLimitError.quantity stays a truthful string. getQuote has no production consumers and is deliberately uncovered.
Milestone 3 of docs/features/contract-tests.md: consumed-contract zod schemas for quotes, pix-key validation, PIX ticket creation/listing, payout ticket status, limits, balances, and subaccount info. The subaccount-scoped live tests are gated behind a pre-provisioned sandbox subaccount; the only transaction created per run is a PIX pay-in ticket that expires unpaid. Payout tickets are covered hermetically only — creating one live would move funds. Resolves the wire-type lie the PRD calls out: BaseTicket timestamps and PixInputTicketOutput.expiration are ISO strings on the wire, not Date (nothing ever consumed them as Date). The contract also caught FakeBrla drift: subaccountInfo served an empty accountInfo while production reads accountInfo.identityStatus — now scriptable on the fake. createOnchainSwapQuote/Ticket, getMainAccountBalance and getAveniaSwapTicket have no production consumers and are deliberately uncovered.
Milestone 4 of docs/features/contract-tests.md: the simple/price shape getCryptoPrice consumes, as a schema next to priceFeed.service.ts (its future Milestone-5 warn-only parse site). FakePrices patches above the HTTP seam, so the hermetic half is fixture-based; the live half mirrors the service's request construction and asserts presence of the requested ids. Falls back to the keyless public API when no COINGECKO_API_KEY is set. Nabla/DIA rates are chain state — out of scope per the no-chain-fidelity non-goal. Adds zod to apps/api (catalog version, already a transitive dependency via shared).
The nightly contracts workflow now passes sandbox credentials and pre-provisioned fixtures from repository secrets (CONTRACT_ALFREDPAY_*, CONTRACT_BRLA_*, CONTRACT_AVENIA_SUBACCOUNT_ID, COINGECKO_API_KEY). Until those are provisioned, missing secrets resolve to empty and CONTRACT_EXPECT_LIVE fails the run loudly — the intended rot alarm, per the PRD. .env.example documents the BRLA credentials and the sandbox-only contract fixtures; testing-strategy.md and the PRD status line reflect milestones 1-4 being implemented, with milestone 5 (warn-only production parsing) shipping separately per endpoint after a quiet week of nightlies.
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for vortexfi ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Comment on lines
+2
to
+19
| import { | ||
| AccountLimitsResponse, | ||
| AveniaAccountBalanceResponse, | ||
| AveniaAccountInfoResponse, | ||
| AveniaFeeType, | ||
| AveniaOperationFee, | ||
| AveniaPayinTicket, | ||
| AveniaPayoutTicket, | ||
| AveniaQuoteResponse, | ||
| AveniaSubaccountAccountInfo, | ||
| AveniaSubaccountWallet, | ||
| AveniaTicketStatus, | ||
| Limit, | ||
| PixInputTicketOutput, | ||
| PixKeyData, | ||
| PixOutputTicketOutput, | ||
| UsedLimitDetails | ||
| } from "./types"; |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR completes milestones 2–4 of the “external API contracts” layer by introducing consumed-field Zod schemas and contract suites for Alfredpay, Avenia/BRLA, and CoinGecko price feeds, with hermetic (PR-blocking) and live (nightly) halves to detect drift.
Changes:
- Add consumed-contract Zod schemas + hermetic schema tests for Alfredpay and Avenia/BRLA in
@vortexfi/shared. - Add new live + fake-backed contract suites in
apps/api/src/tests/contracts/for Alfredpay, Avenia/BRLA, and CoinGecko simple/price. - Align BRLA ticket timestamp types with wire reality (ISO strings) and update fakes/workflow/env docs to support nightly live runs.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/shared/src/services/brla/types.ts | Change ticket timestamp fields from Date to wire-accurate ISO string. |
| packages/shared/src/services/brla/schemas.ts | Add Avenia/BRLA consumed-field Zod schemas for contract verification. |
| packages/shared/src/services/brla/schemas.test.ts | Add hermetic tests to pin accept/reject behavior of Avenia/BRLA schemas. |
| packages/shared/src/services/brla/index.ts | Re-export BRLA schemas from the service barrel. |
| packages/shared/src/services/alfredpay/schemas.ts | Add Alfredpay consumed-field Zod schemas (configs/quotes/tx/KYC/limits). |
| packages/shared/src/services/alfredpay/schemas.test.ts | Add hermetic tests for Alfredpay schema accept/reject behavior. |
| packages/shared/src/services/alfredpay/index.ts | Re-export Alfredpay schemas from the service barrel. |
| packages/shared/src/services/alfredpay/alfredpayApiService.ts | Stringify numeric limit-error quantities at the boundary to keep error type truthful. |
| docs/testing-strategy.md | Document expanded external contract coverage (Alfredpay/Avenia/CoinGecko). |
| docs/features/contract-tests.md | Update PRD status and explicitly describe covered/uncovered endpoints. |
| bun.lock | Add zod to the workspace lockfile. |
| apps/api/src/tests/contracts/pricefeeds.contract.test.ts | Add CoinGecko contract suite (fixtures + optional live). |
| apps/api/src/tests/contracts/avenia.contract.test.ts | Add Avenia/BRLA contract suite (fake + live with fixture gating). |
| apps/api/src/tests/contracts/alfredpay.contract.test.ts | Add Alfredpay contract suite (fake + live with fixture gating). |
| apps/api/src/test-utils/fake-world/fake-anchors.ts | Update BRLA fake outputs to match consumed contract (identityStatus, ISO expiration). |
| apps/api/src/api/services/priceFeed.schemas.ts | Introduce Zod schema for consumed CoinGecko simple/price response shape. |
| apps/api/package.json | Add zod dependency for new price feed schema usage. |
| apps/api/.env.example | Document new fixture env vars for live contract tests. |
| .github/workflows/contracts.yml | Wire nightly live contract runs to repository secrets for sandbox credentials/fixtures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1
to
+19
| import { z } from "zod"; | ||
| import { | ||
| AccountLimitsResponse, | ||
| AveniaAccountBalanceResponse, | ||
| AveniaAccountInfoResponse, | ||
| AveniaFeeType, | ||
| AveniaOperationFee, | ||
| AveniaPayinTicket, | ||
| AveniaPayoutTicket, | ||
| AveniaQuoteResponse, | ||
| AveniaSubaccountAccountInfo, | ||
| AveniaSubaccountWallet, | ||
| AveniaTicketStatus, | ||
| Limit, | ||
| PixInputTicketOutput, | ||
| PixKeyData, | ||
| PixOutputTicketOutput, | ||
| UsedLimitDetails | ||
| } from "./types"; |
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
Implements milestones 2–4 of
docs/features/contract-tests.md, completing the external-API-contract layer for all planned services (SquidRouter landed as milestone 1). Per service, consumed-contract zod schemas live next to the client'stypes.tsand the same assertions run in two halves:schema.parse()d as part of the ordinary api test run, so a fake edited out of sync with the contract fails locally before push.CONTRACT_EXPECT_LIVE=1fails a suite where zero live calls completed.The consumed field sets were derived from a full call-site trace of each client method — fields nothing reads are not asserted, and methods with no production consumers (Alfredpay
getQuote; AveniacreateOnchainSwapQuote/createOnchainSwapTicket/getMainAccountBalance/getAveniaSwapTicket) are deliberately uncovered.Real drift found during live verification
"maxQuantity":86996891.21). The client now stringifies at the boundary soAlfredpayTradeLimitError.quantitystays a truthful string; the schema models the numeric wire.FakeBrla.subaccountInfohad drifted: it servedaccountInfo: {}while production readsaccountInfo.identityStatus. The fake now serves a scriptableidentityStatus— exactly the verified-fake failure mode this layer exists to catch.BaseTicket.createdAt/updatedAt/expiresAtandPixInputTicketOutput.expirationare nowstring(wire truth); nothing consumed them asDate(confirmed by trace + clean monorepo typecheck).getAllConfigsreturns400 {"errorCode":111301}against the production host. Handled as inconclusive here, butalfredpay-limits.servicecalls this in production — worth checking independently.Safety properties of the live halves
apps/api/.env.example) and skip cleanly when unset.CREATED; the Avenia PIX pay-in ticket expires unpaid; payout tickets are hermetic-only since creating one live would move funds).Operator action required
The nightly
contracts.ymlnow reads sandbox credentials/fixtures from repository secrets:CONTRACT_ALFREDPAY_{BASE_URL,API_KEY,API_SECRET,CUSTOMER_ID,FIAT_ACCOUNT_ID,KYC_SUBMISSION_ID},CONTRACT_BRLA_{BASE_URL,API_KEY,PRIVATE_KEY},CONTRACT_AVENIA_SUBACCOUNT_ID,COINGECKO_API_KEY. Until provisioned, the nightly fails loudly by design (credential rot must not look green).Verification
bun typecheckandbun verifyclean.simple/priceall pass against the real APIs.Milestone 5 (warn-only
safeParsein the production clients) intentionally ships separately per endpoint once its schema survives a quiet week of nightlies, per the PRD.