Skip to content

Commit ea334d0

Browse files
test(e2e): mock Polymarket endpoints to remove from allowlist (MMQA-1800) (#29811)
## **Description** Removes both Polymarket entries from `ALLOWLISTED_HOSTS` by adding default mocks for the Polymarket endpoints fired by the wallet's Explore tab and shoring up several pre-existing test-mock gaps the wildcard had been masking. Parent epic [MMQA-1364](https://consensyssoftware.atlassian.net/browse/MMQA-1364). **Why these were allowlisted in the first place** The wallet's Explore tab (`TrendingView` / `usePredictMarketData`) fetches data from `gamma-api.polymarket.com` and other Polymarket hosts when rendered. Many specs pass through Explore on the way to the browser — `tests/flows/browser.flow.ts:118` calls `navigateToBrowserView()` via `Explore → Trending → Browser`, so Polymarket calls fire across many specs that have nothing to do with prediction markets. **What this PR adds** 1. **New default mocks under `defaults/polymarket-apis.ts`** for the Explore-render endpoints: | Endpoint | Default response | | --- | --- | | `gamma-api.polymarket.com/events/pagination?...` | `{ data: [] }` | | `gamma-api.polymarket.com/public-search?...` | `{ events: [] }` | | `gamma-api.polymarket.com/markets?...` | `[]` | | `gamma-api.polymarket.com/teams?...` | `[]` (defensive — non-predict specs that hit the new TeamsCache get an empty payload to absorb the leak) | | `polymarket.com/api/homepage/carousel` | `[]` | | `polymarket.com/api/crypto/crypto-price?...` | `{}` | | `data-api.polymarket.com/positions?...` | `[]` | | `data-api.polymarket.com/activity?...` | `[]` | | `data-api.polymarket.com/upnl?...` | `[]` | `gamma-api.polymarket.com/events/{id}` and `events?parent_event_id=...` are intentionally **not** in defaults — empty payloads for those routes caused the wallet's detail screens to render the legacy layout (no picks list). They're now covered by spec-specific mocks instead. 2. **Extended `POLYMARKET_COMPLETE_MOCKS`** with two new helpers: - `POLYMARKET_PRICES_HISTORY_MOCKS` — `clob.polymarket.com/prices-history` returning `{ history: [] }`. Predict happy-path specs were making live calls here via the wildcard. - `POLYMARKET_TEAMS_MOCKS` — `gamma-api.polymarket.com/teams?league=...` returning realistic NBA + NFL team rosters via a callback. Required because the new `TeamsCache`/`buildGameData` path (recently added on main) drops `market.game` to `undefined` when teams aren't found, causing `PredictMarketDetails.tsx:370` to render the legacy layout (no picks list, no cash-out button). 3. **Tightened `POLYMARKET_EVENT_DETAILS_MOCKS`**: - Bumped to explicit `PRIORITY.BASE` (was at default mockttp priority, tied with the proxy fallback handler — the wildcard was masking cases where the fallback won). - Tightened matcher to `/events/{numericId}` only, after Cursor Bugbot flagged that `includes('events/')` also matches `events/pagination` because `events/` is a substring of `events/pagination`. The new regex `gamma-api\.polymarket\.com\/events\/[0-9]+` ensures pagination requests fall through to their dedicated mock regardless of registration order. 4. **Extended `trending-api-mocks.ts`** with three new entries used by the trending feed's prediction detail flow: - `gamma-api.polymarket.com/events/1` returning the same Bitcoin event payload as the existing `events/pagination` mock - `clob.polymarket.com/prices-history` returning `{ history: [] }` - `clob.polymarket.com/prices` (POST) returning `{}` **Allowlist entries removed:** - `gamma-api.polymarket.com` (was redundant — covered by the wildcard) - `*.polymarket.com` (covered all subdomains + apex) `ALLOWLISTED_HOSTS` is now down to **5** entries: 4 local + `metamask.github.io`. ## **Changelog** CHANGELOG entry: null ## **Related issues** [MMQA-1800](https://consensyssoftware.atlassian.net/browse/MMQA-1800) Parent epic: [MMQA-1364](https://consensyssoftware.atlassian.net/browse/MMQA-1364) Fixes: ## **Manual testing steps** ```gherkin Feature: E2E mock coverage for Polymarket Explore-tab calls Scenario: browser specs that pass through Explore no longer leak Polymarket requests Given an E2E spec calls navigateToBrowserView() And the flow taps Explore → Trending → Browser When TrendingView renders sports market feeds via usePredictMarketData Then requests to gamma-api.polymarket.com (events/pagination, public-search, markets, teams) are answered by the default mocks And requests to data-api.polymarket.com (positions, activity, upnl) are answered by the default mocks And requests to polymarket.com (homepage/carousel, crypto/crypto-price) are answered by the default mocks And no entries for Polymarket hosts are required in mock-e2e-allowlist.ts Scenario: predict specs render market detail with picks list and cash-out button Given the spec registers POLYMARKET_COMPLETE_MOCKS as its testSpecificMock And the production default for predictLiveSports has leagues ['nfl','nba'] When the test taps a position and navigates to PredictMarketDetails Then gamma-api.polymarket.com/teams returns realistic team payloads for the requested league And buildGameData populates market.game with home/away team data And PredictMarketDetails renders PredictGameDetailsContent with the picks list And the predict-picks-cash-out-button-{positionId} element is present for the test to tap Scenario: trending-feed renders prediction detail without leaking Given the spec registers TRENDING_API_MOCKS as its testSpecificMock When the test taps a prediction row and the wallet navigates to the detail screen Then events/{id}, prices, and prices-history are answered by the trending mocks And the spec passes without a live request leak ``` ## **Screenshots/Recordings** ### **Before** `tests/api-mocking/mock-e2e-allowlist.ts`: ```ts export const ALLOWLISTED_HOSTS = [ '0.0.0.0', '127.0.0.1', 'localhost', '10.0.2.2', 'gamma-api.polymarket.com', '*.polymarket.com', 'metamask.github.io', ]; ``` `tests/api-mocking/mock-responses/defaults/polymarket-apis.ts` had a single entry: the geoblock mock. All other Polymarket calls leaked to live via the wildcard, masking gaps in test-specific mocks. ### **After** `tests/api-mocking/mock-e2e-allowlist.ts`: ```ts export const ALLOWLISTED_HOSTS = [ '0.0.0.0', '127.0.0.1', 'localhost', '10.0.2.2', 'metamask.github.io', ]; ``` `defaults/polymarket-apis.ts` now has 10 default GET matchers (geoblock + 9 new) covering Explore-render endpoints. `POLYMARKET_COMPLETE_MOCKS` covers `clob.polymarket.com/prices-history` and `gamma-api.polymarket.com/teams` with realistic NBA + NFL rosters. `POLYMARKET_EVENT_DETAILS_MOCKS` has explicit `PRIORITY.BASE` and a tightened numeric-ID matcher. `TRENDING_API_MOCKS` covers `events/1`, `prices-history`, and `prices`. **CI verification (commit `a3d232d3`):** All E2E smoke suites pass with **zero leak warnings** sampled across `prediction-market-{android,ios}-smoke`, `wallet-platform-{android,ios}-smoke` (which includes `trending-feed.spec.ts`), `confirmations-{android,ios}-smoke`, and `browser-ios-smoke`. Run: https://github.com/MetaMask/metamask-mobile/actions/runs/25526071329 ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. [MMQA-1364]: https://consensyssoftware.atlassian.net/browse/MMQA-1364?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [MMQA-1800]: https://consensyssoftware.atlassian.net/browse/MMQA-1800?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4b4292d commit ea334d0

4 files changed

Lines changed: 377 additions & 5 deletions

File tree

tests/api-mocking/mock-e2e-allowlist.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ export const ALLOWLISTED_HOSTS = [
66
'127.0.0.1',
77
'localhost',
88
'10.0.2.2', // Android emulator host
9-
'gamma-api.polymarket.com',
10-
'*.polymarket.com',
119
'metamask.github.io', // Test-snaps and test-dapp pages loaded in browser
1210
];
1311

tests/api-mocking/mock-responses/defaults/polymarket-apis.ts

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
/**
2-
* Default mock responses for Polymarket API endpoints
2+
* Default mock responses for Polymarket API endpoints.
3+
*
4+
* These defaults cover the calls fired by the wallet's Explore tab
5+
* (TrendingView / usePredictMarketData) so that non-predict specs that pass
6+
* through Explore (e.g. via navigateToBrowserView()) don't leak live requests
7+
* to Polymarket. Predict and trending specs override these via
8+
* POLYMARKET_COMPLETE_MOCKS registered as testSpecificMock at higher priority.
9+
*
10+
* clob.polymarket.com is intentionally not covered here — it only fires on
11+
* trade actions, never during Explore render.
312
*/
413

514
/** Single source of truth for geoblock mock (eligible region). Reused in defaults and in POLYMARKET_COMPLETE_MOCKS. */
@@ -13,7 +22,63 @@ export const POLYMARKET_GEOBLOCK_ELIGIBLE = {
1322
} as const;
1423

1524
export const POLYMARKET_API_MOCKS = {
16-
GET: [POLYMARKET_GEOBLOCK_ELIGIBLE],
25+
GET: [
26+
POLYMARKET_GEOBLOCK_ELIGIBLE,
27+
// gamma-api: events pagination — consumer reads `data?.data`
28+
{
29+
urlEndpoint:
30+
/^https:\/\/gamma-api\.polymarket\.com\/events\/pagination(\?.*)?$/,
31+
responseCode: 200,
32+
response: { data: [] },
33+
},
34+
// gamma-api: public-search — consumer reads `data?.events`
35+
{
36+
urlEndpoint:
37+
/^https:\/\/gamma-api\.polymarket\.com\/public-search(\?.*)?$/,
38+
responseCode: 200,
39+
response: { events: [] },
40+
},
41+
// gamma-api: markets list
42+
{
43+
urlEndpoint: /^https:\/\/gamma-api\.polymarket\.com\/markets(\?.*)?$/,
44+
responseCode: 200,
45+
response: [],
46+
},
47+
// gamma-api: sports league team metadata (TeamsCache)
48+
{
49+
urlEndpoint: /^https:\/\/gamma-api\.polymarket\.com\/teams(\?.*)?$/,
50+
responseCode: 200,
51+
response: [],
52+
},
53+
// polymarket.com: homepage carousel
54+
{
55+
urlEndpoint: 'https://polymarket.com/api/homepage/carousel',
56+
responseCode: 200,
57+
response: [],
58+
},
59+
// polymarket.com: crypto price feed
60+
{
61+
urlEndpoint: /^https:\/\/polymarket\.com\/api\/crypto\/crypto-price.*$/,
62+
responseCode: 200,
63+
response: {},
64+
},
65+
// data-api: positions / activity / upnl
66+
{
67+
urlEndpoint: /^https:\/\/data-api\.polymarket\.com\/positions(\?.*)?$/,
68+
responseCode: 200,
69+
response: [],
70+
},
71+
{
72+
urlEndpoint: /^https:\/\/data-api\.polymarket\.com\/activity(\?.*)?$/,
73+
responseCode: 200,
74+
response: [],
75+
},
76+
{
77+
urlEndpoint: /^https:\/\/data-api\.polymarket\.com\/upnl(\?.*)?$/,
78+
responseCode: 200,
79+
response: [],
80+
},
81+
],
1782
POST: [],
1883
PUT: [],
1984
DELETE: [],

tests/api-mocking/mock-responses/polymarket/polymarket-mocks.ts

Lines changed: 256 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,13 @@ export const POLYMARKET_EVENT_DETAILS_MOCKS = async (mockServer: Mockttp) => {
211211
.forGet('/proxy')
212212
.matching((request) => {
213213
const url = new URL(request.url).searchParams.get('url');
214-
return Boolean(url?.includes('gamma-api.polymarket.com/events/'));
214+
// Match only /events/{numericId}, NOT /events/pagination or
215+
// /events?parent_event_id=... — those have their own dedicated mocks.
216+
return Boolean(
217+
url && /gamma-api\.polymarket\.com\/events\/[0-9]+/.test(url),
218+
);
215219
})
220+
.asPriority(PRIORITY.BASE)
216221
.thenCallback((request) => {
217222
const url = new URL(request.url).searchParams.get('url');
218223
const eventIdMatch = url?.match(/\/events\/([0-9]+)$/);
@@ -656,6 +661,254 @@ export const POLYMARKET_FEE_RATE_MOCKS = async (mockServer: Mockttp) => {
656661
});
657662
};
658663

664+
/**
665+
* Mock for Polymarket CLOB prices-history API
666+
* Returns an empty history series — sufficient for predict happy-path specs
667+
* that render the chart (consumer treats non-array history as empty).
668+
*/
669+
export const POLYMARKET_PRICES_HISTORY_MOCKS = async (mockServer: Mockttp) => {
670+
await mockServer
671+
.forGet('/proxy')
672+
.matching((request) => {
673+
const url = new URL(request.url).searchParams.get('url');
674+
return Boolean(url?.includes('clob.polymarket.com/prices-history'));
675+
})
676+
.asPriority(PRIORITY.BASE)
677+
.thenReply(200, JSON.stringify({ history: [] }), {
678+
'content-type': 'application/json',
679+
});
680+
};
681+
682+
/**
683+
* Static team metadata for sports leagues used by predict E2E fixtures.
684+
* Covers NBA + NFL teams referenced in event slugs (see polymarket-positions-response.ts).
685+
*
686+
* The wallet's TeamsCache stores teams by abbreviation, and buildGameData
687+
* (gameParser.ts) returns null when either home or away team is missing —
688+
* which causes market.game to be undefined and the wallet to fall back to
689+
* the non-game market detail layout (no picks list). So returning realistic
690+
* team data here is required for the new PredictGameDetailsContent path.
691+
*/
692+
const POLYMARKET_TEAMS_BY_LEAGUE: Record<
693+
string,
694+
{
695+
id: string;
696+
name: string;
697+
logo: string;
698+
abbreviation: string;
699+
color: string;
700+
alias: string;
701+
league: string;
702+
}[]
703+
> = {
704+
nba: [
705+
{
706+
id: 'nba-sas',
707+
name: 'San Antonio Spurs',
708+
logo: '',
709+
abbreviation: 'sas',
710+
color: '#000000',
711+
alias: 'Spurs',
712+
league: 'nba',
713+
},
714+
{
715+
id: 'nba-nop',
716+
name: 'New Orleans Pelicans',
717+
logo: '',
718+
abbreviation: 'nop',
719+
color: '#0C2340',
720+
alias: 'Pelicans',
721+
league: 'nba',
722+
},
723+
{
724+
id: 'nba-bos',
725+
name: 'Boston Celtics',
726+
logo: '',
727+
abbreviation: 'bos',
728+
color: '#007A33',
729+
alias: 'Celtics',
730+
league: 'nba',
731+
},
732+
{
733+
id: 'nba-bkn',
734+
name: 'Brooklyn Nets',
735+
logo: '',
736+
abbreviation: 'bkn',
737+
color: '#000000',
738+
alias: 'Nets',
739+
league: 'nba',
740+
},
741+
],
742+
nfl: [
743+
{
744+
id: 'nfl-buf',
745+
name: 'Buffalo Bills',
746+
logo: '',
747+
abbreviation: 'buf',
748+
color: '#00338D',
749+
alias: 'Bills',
750+
league: 'nfl',
751+
},
752+
{
753+
id: 'nfl-atl',
754+
name: 'Atlanta Falcons',
755+
logo: '',
756+
abbreviation: 'atl',
757+
color: '#A71930',
758+
alias: 'Falcons',
759+
league: 'nfl',
760+
},
761+
{
762+
id: 'nfl-chi',
763+
name: 'Chicago Bears',
764+
logo: '',
765+
abbreviation: 'chi',
766+
color: '#0B162A',
767+
alias: 'Bears',
768+
league: 'nfl',
769+
},
770+
{
771+
id: 'nfl-was',
772+
name: 'Washington Commanders',
773+
logo: '',
774+
abbreviation: 'was',
775+
color: '#5A1414',
776+
alias: 'Commanders',
777+
league: 'nfl',
778+
},
779+
{
780+
id: 'nfl-den',
781+
name: 'Denver Broncos',
782+
logo: '',
783+
abbreviation: 'den',
784+
color: '#FB4F14',
785+
alias: 'Broncos',
786+
league: 'nfl',
787+
},
788+
{
789+
id: 'nfl-nyj',
790+
name: 'New York Jets',
791+
logo: '',
792+
abbreviation: 'nyj',
793+
color: '#125740',
794+
alias: 'Jets',
795+
league: 'nfl',
796+
},
797+
{
798+
id: 'nfl-det',
799+
name: 'Detroit Lions',
800+
logo: '',
801+
abbreviation: 'det',
802+
color: '#0076B6',
803+
alias: 'Lions',
804+
league: 'nfl',
805+
},
806+
{
807+
id: 'nfl-kc',
808+
name: 'Kansas City Chiefs',
809+
logo: '',
810+
abbreviation: 'kc',
811+
color: '#E31837',
812+
alias: 'Chiefs',
813+
league: 'nfl',
814+
},
815+
{
816+
id: 'nfl-jax',
817+
name: 'Jacksonville Jaguars',
818+
logo: '',
819+
abbreviation: 'jax',
820+
color: '#006778',
821+
alias: 'Jaguars',
822+
league: 'nfl',
823+
},
824+
{
825+
id: 'nfl-pit',
826+
name: 'Pittsburgh Steelers',
827+
logo: '',
828+
abbreviation: 'pit',
829+
color: '#FFB612',
830+
alias: 'Steelers',
831+
league: 'nfl',
832+
},
833+
{
834+
id: 'nfl-cin',
835+
name: 'Cincinnati Bengals',
836+
logo: '',
837+
abbreviation: 'cin',
838+
color: '#FB4F14',
839+
alias: 'Bengals',
840+
league: 'nfl',
841+
},
842+
{
843+
id: 'nfl-sea',
844+
name: 'Seattle Seahawks',
845+
logo: '',
846+
abbreviation: 'sea',
847+
color: '#002244',
848+
alias: 'Seahawks',
849+
league: 'nfl',
850+
},
851+
{
852+
id: 'nfl-sf',
853+
name: 'San Francisco 49ers',
854+
logo: '',
855+
abbreviation: 'sf',
856+
color: '#AA0000',
857+
alias: '49ers',
858+
league: 'nfl',
859+
},
860+
{
861+
id: 'nfl-tb',
862+
name: 'Tampa Bay Buccaneers',
863+
logo: '',
864+
abbreviation: 'tb',
865+
color: '#D50A0A',
866+
alias: 'Buccaneers',
867+
league: 'nfl',
868+
},
869+
{
870+
id: 'nfl-dal',
871+
name: 'Dallas Cowboys',
872+
logo: '',
873+
abbreviation: 'dal',
874+
color: '#003594',
875+
alias: 'Cowboys',
876+
league: 'nfl',
877+
},
878+
],
879+
};
880+
881+
/**
882+
* Mock for Polymarket gamma-api /teams API
883+
* Returns the full list of teams for the queried league. The wallet's
884+
* TeamsCache filters by requested abbreviations, so returning all teams
885+
* for the league is safe and avoids per-test fixture maintenance.
886+
*/
887+
export const POLYMARKET_TEAMS_MOCKS = async (mockServer: Mockttp) => {
888+
await mockServer
889+
.forGet('/proxy')
890+
.matching((request) => {
891+
const url = new URL(request.url).searchParams.get('url');
892+
return Boolean(url?.includes('gamma-api.polymarket.com/teams'));
893+
})
894+
.asPriority(PRIORITY.BASE)
895+
.thenCallback((request) => {
896+
const proxiedUrlParam = new URL(request.url).searchParams.get('url');
897+
let league = '';
898+
try {
899+
const polymarketUrl = new URL(proxiedUrlParam ?? '');
900+
league = polymarketUrl.searchParams.get('league') ?? '';
901+
} catch {
902+
league = '';
903+
}
904+
const teams = POLYMARKET_TEAMS_BY_LEAGUE[league] ?? [];
905+
return {
906+
statusCode: 200,
907+
json: teams,
908+
};
909+
});
910+
};
911+
659912
/**
660913
* Mock for Polymarket CLOB order book API
661914
* Returns order book data for specific token IDs with correct market mapping
@@ -2214,6 +2467,8 @@ export const POLYMARKET_COMPLETE_MOCKS = async (mockServer: Mockttp) => {
22142467
await POLYMARKET_EVENT_DETAILS_MOCKS(mockServer);
22152468
await POLYMARKET_ORDER_BOOK_MOCKS(mockServer);
22162469
await POLYMARKET_PRICES_MOCKS(mockServer); // Mock for CLOB prices API
2470+
await POLYMARKET_PRICES_HISTORY_MOCKS(mockServer); // Mock for CLOB prices-history API (chart series)
2471+
await POLYMARKET_TEAMS_MOCKS(mockServer); // Mock for gamma-api /teams (sports league team metadata)
22172472
await POLYMARKET_FEE_RATE_MOCKS(mockServer);
22182473
await POLYMARKET_MARKET_FEEDS_MOCKS(mockServer);
22192474
await POLYMARKET_CLOB_AUTH_MOCKS(mockServer);

0 commit comments

Comments
 (0)