Skip to content

Commit d9a4ae6

Browse files
test(e2e): route snap tarball downloads through MockServerE2E proxy
ReactNativeBlobUtil.fetch() bypasses shim.js's JS-layer fetch/XHR/expo-fetch patches because it dispatches directly to OkHttp/NSURLSession. As a result, the testSpecificMock rules registered for snap tarball downloads never fire, and tests silently hit the live npm registry. Rewrite the tarball URL at the source — inside NpmLocation.fetchNpmTarball — when running in E2E. The request body and headers contract with native ReactNativeBlobUtil stays untouched; only the target URL changes from `https://registry.npmjs.org/...` to `http://localhost:<mockServerPort>/proxy?url=<encoded-target>`. Changes: - app/core/Snaps/location/npm.ts: rewrite tarballUrl when isE2E - app/util/test/utils.js: export FALLBACK_MOCK_SERVER_PORT + getMockServerPortInApp helper, mirroring fixtureServerPort/commandQueueServerPort pattern - shim.js: store launchArgs.mockServerPort in testConfig so app code can read it - app/core/Snaps/location/npm.test.ts: 2 new tests covering the rewrite both ways (E2E on / off) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 81512e9 commit d9a4ae6

4 files changed

Lines changed: 77 additions & 13 deletions

File tree

app/core/Snaps/location/npm.test.ts

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,38 @@
11
import { NativeModules } from 'react-native';
2+
3+
// Mock the isE2E flag via a mutable variable so individual tests can toggle it.
4+
let mockIsE2E = false;
5+
jest.mock('../../../util/test/utils', () => ({
6+
get isE2E() {
7+
return mockIsE2E;
8+
},
9+
getMockServerPortInApp: jest.fn(() => 8000),
10+
}));
11+
12+
// eslint-disable-next-line import-x/first
213
import { NpmLocation } from './npm';
314

415
// Mock RNTar native module before anything else
516
NativeModules.RNTar = {
617
unTar: jest.fn().mockResolvedValue('/document-dir/archive'),
718
};
819

20+
const mockBlobFetch = jest.fn((..._args: unknown[]) =>
21+
Promise.resolve({
22+
flush: jest.fn(),
23+
data: '/document-dir/archive.tgz',
24+
respInfo: {
25+
status: 200,
26+
headers: {
27+
'content-length': '2000',
28+
},
29+
},
30+
}),
31+
);
32+
933
jest.mock('react-native-blob-util', () => ({
1034
config: jest.fn(() => ({
11-
fetch: jest.fn(() =>
12-
Promise.resolve({
13-
flush: jest.fn(),
14-
data: '/document-dir/archive.tgz',
15-
respInfo: {
16-
status: 200,
17-
headers: {
18-
'content-length': '2000',
19-
},
20-
},
21-
}),
22-
),
35+
fetch: mockBlobFetch,
2336
})),
2437
fs: {
2538
unlink: jest.fn().mockResolvedValue(undefined),
@@ -46,6 +59,11 @@ jest.mock('react-native-blob-util', () => ({
4659
}));
4760

4861
describe('NpmLocation', () => {
62+
beforeEach(() => {
63+
mockBlobFetch.mockClear();
64+
mockIsE2E = false;
65+
});
66+
4967
// This test is heavily mocked and not necesarily a good indicator that everything works E2E.
5068
it('fetches and unpacks tarballs', async () => {
5169
const location = new NpmLocation(new URL('npm:@metamask/example-snap'));
@@ -61,4 +79,32 @@ describe('NpmLocation', () => {
6179
`module.exports.onRpcRequest = () => null`,
6280
);
6381
});
82+
83+
describe('E2E URL rewriting', () => {
84+
it('routes tarball downloads to MockServerE2E /proxy when isE2E is true', async () => {
85+
mockIsE2E = true;
86+
87+
const location = new NpmLocation(new URL('npm:@metamask/example-snap'));
88+
await location.fetch('snap.manifest.json');
89+
90+
expect(mockBlobFetch).toHaveBeenCalledWith(
91+
'GET',
92+
expect.stringMatching(
93+
/^http:\/\/localhost:8000\/proxy\?url=https%3A%2F%2Fregistry\.npmjs\.org%2F/,
94+
),
95+
);
96+
});
97+
98+
it('uses the original tarball URL when isE2E is false', async () => {
99+
mockIsE2E = false;
100+
101+
const location = new NpmLocation(new URL('npm:@metamask/example-snap'));
102+
await location.fetch('snap.manifest.json');
103+
104+
expect(mockBlobFetch).toHaveBeenCalledWith(
105+
'GET',
106+
expect.stringMatching(/^https:\/\/registry\.npmjs\.org\//),
107+
);
108+
});
109+
});
64110
});

app/core/Snaps/location/npm.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
getNpmCanonicalBasePath,
1111
TARBALL_SIZE_SAFETY_LIMIT,
1212
} from '@metamask/snaps-controllers';
13+
import { isE2E, getMockServerPortInApp } from '../../../util/test/utils';
1314

1415
const { RNTar } = NativeModules;
1516

@@ -50,7 +51,17 @@ export class NpmLocation extends BaseNpmLocation {
5051
let response: FetchBlobResponse | null = null;
5152
let untarPath: string | null = null;
5253
try {
53-
response = await this.#blobFetch('GET', tarballUrl.toString());
54+
// In E2E tests, route the tarball download through MockServerE2E's /proxy
55+
// endpoint so test-specific mocks can intercept it. ReactNativeBlobUtil's
56+
// native fetch bypasses shim.js's JS-layer fetch/XHR patches, so the
57+
// redirect must happen here at the source URL.
58+
const fetchUrl = isE2E
59+
? `http://localhost:${getMockServerPortInApp()}/proxy?url=${encodeURIComponent(
60+
tarballUrl.toString(),
61+
)}`
62+
: tarballUrl.toString();
63+
64+
response = await this.#blobFetch('GET', fetchUrl);
5465

5566
const responseInfo = response.respInfo;
5667

app/util/test/utils.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const flushPromises = () => new Promise(setImmediate);
55
// iOS: These are overridden by LaunchArgs at runtime
66
export const FALLBACK_FIXTURE_SERVER_PORT = 12345;
77
export const FALLBACK_COMMAND_QUEUE_SERVER_PORT = 2446;
8+
export const FALLBACK_MOCK_SERVER_PORT = 8000;
89

910
// E2E test configuration required in app
1011
export const testConfig = {};
@@ -42,5 +43,7 @@ export const getFixturesServerPortInApp = () =>
4243
testConfig.fixtureServerPort ?? FALLBACK_FIXTURE_SERVER_PORT;
4344
export const getCommandQueueServerPortInApp = () =>
4445
testConfig.commandQueueServerPort ?? FALLBACK_COMMAND_QUEUE_SERVER_PORT;
46+
export const getMockServerPortInApp = () =>
47+
testConfig.mockServerPort ?? FALLBACK_MOCK_SERVER_PORT;
4548

4649
export const isRc = process.env.METAMASK_ENVIRONMENT === 'rc';

shim.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { LaunchArguments } from 'react-native-launch-arguments';
3535
import {
3636
FALLBACK_FIXTURE_SERVER_PORT,
3737
FALLBACK_COMMAND_QUEUE_SERVER_PORT,
38+
FALLBACK_MOCK_SERVER_PORT,
3839
isE2E,
3940
isTest,
4041
enableApiCallLogs,
@@ -114,6 +115,9 @@ if (isTest) {
114115
testConfig.commandQueueServerPort = raw?.commandQueueServerPort
115116
? raw.commandQueueServerPort
116117
: FALLBACK_COMMAND_QUEUE_SERVER_PORT;
118+
testConfig.mockServerPort = raw?.mockServerPort
119+
? raw.mockServerPort
120+
: FALLBACK_MOCK_SERVER_PORT;
117121
}
118122

119123
// Fix for https://github.com/facebook/react-native/issues/5667

0 commit comments

Comments
 (0)