diff --git a/package.json b/package.json index 8ee6638..93e2832 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,8 @@ "@types/promise-retry": "^1.1.6", "eslint": "^8.57.0", "eslint-config-universe": "^13.0.0", - "fetch-mock": "^9.11.0", "jest": "^29.7.0", + "msw": "^2.4.9", "prettier": "^3.2.5", "ts-jest": "~29.2.5", "typescript": "^5.4.2" diff --git a/src/__tests__/ExpoClient-test.ts b/src/__tests__/ExpoClient-test.ts index 8ab1131..08859fe 100644 --- a/src/__tests__/ExpoClient-test.ts +++ b/src/__tests__/ExpoClient-test.ts @@ -1,129 +1,116 @@ -import { jest, afterEach, beforeEach, describe, test, expect } from '@jest/globals'; -import fetch, { Headers, Response } from 'node-fetch'; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from '@jest/globals'; +import { http, HttpResponse } from 'msw'; +import { setupServer } from 'msw/node'; import assert from 'node:assert'; -import { gzipSync } from 'node:zlib'; +import { randomUUID } from 'node:crypto'; +import { gunzipSync } from 'node:zlib'; import ExpoClient, { ExpoPushMessage } from '../ExpoClient'; import { getReceiptsApiUrl, sendApiUrl } from '../ExpoClientValues'; -jest.mock('node-fetch', () => { - const actualFetch = jest.requireActual('node-fetch'); - return { - __esModule: true, - ...actualFetch, - default: jest.fn(), // the fetch function - }; -}); - -const mockedFetch = jest.mocked(fetch); - -afterEach(() => { - jest.clearAllMocks(); -}); - -describe('sending push notification messages', () => { - test('sends requests to the correct url', async () => { - try { - await client().sendPushNotificationsAsync([]); - } catch {} - - expect(mockedFetch).toHaveBeenLastCalledWith(sendApiUrl, expect.anything()); - }); - - test('sets a user-agent header on the request', async () => { - try { - await client().sendPushNotificationsAsync([]); - } catch {} - - expect(headerOnLastCall('user-agent')).toMatch(/^expo-server-sdk-node\//); - }); - - test.each([ - { header: 'accept', expected: 'application/json' }, - { header: 'accept-encoding', expected: 'gzip' }, - { header: 'content-type', expected: 'application/json' }, - ])('sets the header $header=$expected on the request', async ({ header, expected }) => { - try { - await client().sendPushNotificationsAsync([]); - } catch {} - - expect(headerOnLastCall(header)).toContain(expected); - }); +const accessToken = 'foobar'; +const mockTickets = [{ status: 'ok', id: randomUUID() }]; +const mockReceipts = {}; +const validationError = HttpResponse.json( + { errors: [{ code: 'VALIDATION_ERROR' }] }, + { status: 400 }, +); + +const server = setupServer( + http.post(sendApiUrl, async ({ request }) => { + // The `useFcmV1` parameter can now only be true or absent + const url = new URL(request.url); + switch (url.searchParams.get('useFcmV1')) { + case 'true': + break; + case null: + break; + default: + return validationError; + } + if (request.headers.get('Content-Type') !== 'application/json') { + return validationError; + } + if (request.headers.get('Authorization') !== `Bearer ${accessToken}`) { + return HttpResponse.json( + { error: 'invalid_token', error_description: 'The bearer token is invalid' }, + { status: 401 }, + ); + } + let body; + if (request.headers.get('Content-Encoding') === 'gzip') { + body = JSON.parse(gunzipSync(await request.arrayBuffer()).toString()); + } else { + body = await request.json(); + } - test('does not set the authorization header by default', async () => { - try { - await client().sendPushNotificationsAsync([]); - } catch {} + if (typeof body != 'object') { + return HttpResponse.text('Not Found', { status: 404 }); + } + if (!body || !(body['to'] || Array.isArray(body))) { + return validationError; + } - expect(headerOnLastCall('Authorization')).toBeNull(); - }); + return HttpResponse.json({ data: mockTickets }); + }), + http.post(getReceiptsApiUrl, async ({ request }) => { + if (request.headers.get('Content-Type') !== 'application/json') { + return validationError; + } + const body = await request.json(); - test('resolves with the data from the server response', () => { - const data = ["here's one", "here's a second one"]; - mockFetchResponse({ data }); + if (typeof body != 'object') { + return HttpResponse.text('Not Found', { status: 404 }); + } + if (!body || !(body['ids'] || Array.isArray(body))) { + return validationError; + } + return HttpResponse.json({ data: mockReceipts }); + }), +); - return expect( - client().sendPushNotificationsAsync([{ to: 'one' }, { to: 'another' }]), - ).resolves.toEqual(data); - }); +beforeAll(() => { + server.listen({ onUnhandledRequest: 'error' }); +}); - test('sets the Authorization header to any supplied access token', async () => { - const accessToken = 'foobar'; +afterAll(() => { + server.close(); +}); - try { - await client({ accessToken }).sendPushNotificationsAsync([]); - } catch {} +afterEach(() => { + server.resetHandlers(); +}); - expect(headerOnLastCall('Authorization')).toContain(`Bearer ${accessToken}`); - }); +describe('sending push notification messages', () => { + test('resolves with the data from the server response', () => + expect(client().sendPushNotificationsAsync([{ to: 'one' }])).resolves.toEqual(mockTickets)); describe('the useFcmV1 option', () => { - test('omits the parameter when set to true', async () => { - try { - await client({ useFcmV1: true }).sendPushNotificationsAsync([]); - } catch {} - - expect(mockedFetch).toHaveBeenCalledWith(sendApiUrl, expect.anything()); - }); - - test('includes the parameter when set to false', async () => { - try { - await client({ useFcmV1: false }).sendPushNotificationsAsync([]); - } catch {} + test('omits the parameter when set to true', () => + expect(client({ useFcmV1: true }).sendPushNotificationsAsync([{ to: '' }])).resolves.toEqual( + mockTickets, + )); - expect(mockedFetch).toHaveBeenCalledWith(`${sendApiUrl}?useFcmV1=false`, expect.anything()); - }); + test('includes the parameter when set to false', () => + expect( + client({ useFcmV1: false }).sendPushNotificationsAsync([{ to: '' }]), + ).rejects.toThrow()); }); - test('compresses request bodies over 1 KiB', async () => { + test('compresses request bodies over 1 KiB', () => { const messages = [{ to: 'a', body: new Array(1500).join('?') }]; const messageLength = JSON.stringify(messages).length; expect(messageLength).toBeGreaterThan(1024); - try { - await client().sendPushNotificationsAsync(messages); - } catch {} - - expect(mockedFetch).toHaveBeenLastCalledWith( - sendApiUrl, - expect.objectContaining({ - body: gzipSync(Buffer.from(JSON.stringify(messages))), - }), - ); - expect(headerOnLastCall('content-encoding')).toContain('gzip'); + return expect(client().sendPushNotificationsAsync(messages)).resolves.toEqual(mockTickets); }); test(`throws an error when the number of tickets doesn't match the number of messages`, async () => { - const data = new Array(2); - - mockFetchResponse({ data }); + server.use(http.post(sendApiUrl, () => HttpResponse.json({ data: Array(2) }))); await expect(client().sendPushNotificationsAsync([{ to: 'a' }])).rejects.toThrow( `Expected Expo to respond with 1 ticket but got 2`, ); - - mockFetchResponse({ data }); - await expect(client().sendPushNotificationsAsync(Array(3).fill({ to: 'a' }))).rejects.toThrow( `Expected Expo to respond with 3 tickets but got 2`, ); @@ -133,7 +120,7 @@ describe('sending push notification messages', () => { const code = 'TEST_API_ERROR'; const message = 'This is a test error'; beforeEach(() => { - mockFetchResponse({ errors: [{ code, message }] }); + server.use(http.post(sendApiUrl, () => HttpResponse.json({ errors: [{ code, message }] }))); }); test('rejects with the error message', () => expect(client().sendPushNotificationsAsync([])).rejects.toThrow(message)); @@ -142,7 +129,9 @@ describe('sending push notification messages', () => { }); test('handles 200 HTTP responses with malformed JSON', async () => { - mockedFetch.mockResolvedValue(new Response('Not JSON')); + server.use( + http.post(sendApiUrl, () => HttpResponse.text('Not JSON')), + ); await expect(client().sendPushNotificationsAsync([])).rejects.toThrow( `Expo responded with an error`, @@ -153,7 +142,11 @@ describe('sending push notification messages', () => { const code = 'TEST_API_ERROR'; const message = 'This is a test error'; beforeEach(() => { - mockFetchResponse({ errors: [{ code, message }] }, 400); + server.use( + http.post(sendApiUrl, () => + HttpResponse.json({ errors: [{ code, message }] }, { status: 400 }), + ), + ); }); test('rejects with the error message', () => expect(client().sendPushNotificationsAsync([])).rejects.toThrow(message)); @@ -162,7 +155,9 @@ describe('sending push notification messages', () => { }); test('handles non-200 HTTP responses with arbitrary JSON', () => { - mockFetchResponse({ clowntown: true }, 400); + server.use( + http.post(sendApiUrl, () => HttpResponse.json({ clowntown: true }, { status: 400 })), + ); return expect(client().sendPushNotificationsAsync([])).rejects.toThrow( `Expo responded with an error`, @@ -170,10 +165,10 @@ describe('sending push notification messages', () => { }); test('handles non-200 HTTP responses with arbitrary text', () => { - mockedFetch.mockResolvedValue( - new Response('Not JSON', { - status: 400, - }), + server.use( + http.post(sendApiUrl, () => + HttpResponse.text('Not JSON', { status: 400 }), + ), ); return expect(client().sendPushNotificationsAsync([])).rejects.toThrow( @@ -196,7 +191,9 @@ describe('sending push notification messages', () => { message: `This is another error`, }, ]; - beforeEach(() => mockFetchResponse({ errors }, 400)); + beforeEach(() => { + server.use(http.post(sendApiUrl, () => HttpResponse.json({ errors }, { status: 400 }))); + }); test("throws the first error's message", () => expect(client().sendPushNotificationsAsync([])).rejects.toThrow(errors[0]?.message)); @@ -230,9 +227,21 @@ describe('sending push notification messages', () => { describe('when all retries fail', () => { beforeEach(() => { - mockFetchResponse({ errors }, 429); - mockFetchResponse({ errors }, 429); - mockFetchResponse({ errors }, 429); + server.use( + http.post(sendApiUrl, () => HttpResponse.json({ errors }, { status: 429 }), { + once: true, + }), + ); + server.use( + http.post(sendApiUrl, () => HttpResponse.json({ errors }, { status: 429 }), { + once: true, + }), + ); + server.use( + http.post(sendApiUrl, () => HttpResponse.json({ errors }, { status: 429 }), { + once: true, + }), + ); }); test('rejects with the error message', () => @@ -244,9 +253,17 @@ describe('sending push notification messages', () => { describe('when the second retry succeeds', () => { const data = ['one', 'another']; beforeEach(() => { - mockFetchResponse({ errors }, 429); - mockFetchResponse({ errors }, 429); - mockFetchResponse({ data }); + server.use( + http.post(sendApiUrl, () => HttpResponse.json({ errors }, { status: 429 }), { + once: true, + }), + ); + server.use( + http.post(sendApiUrl, () => HttpResponse.json({ errors }, { status: 429 }), { + once: true, + }), + ); + server.use(http.post(sendApiUrl, () => HttpResponse.json({ data }), { once: true })); }); test('resolves with the data response', () => expect(fastClient.sendPushNotificationsAsync([{ to: 'a' }, { to: 'b' }])).resolves.toEqual( @@ -257,36 +274,15 @@ describe('sending push notification messages', () => { }); describe('retrieving push notification receipts', () => { - test('sends requests to the correct url', async () => { - try { - await client().getPushNotificationReceiptsAsync([]); - } catch {} - - expect(mockedFetch).toHaveBeenLastCalledWith(getReceiptsApiUrl, expect.anything()); - }); - - test.each([ - { header: 'accept', expected: 'application/json' }, - { header: 'accept-encoding', expected: 'gzip' }, - { header: 'content-type', expected: 'application/json' }, - ])('sets the header $header=$expected on the request', async ({ header, expected }) => { - try { - await client().getPushNotificationReceiptsAsync([]); - } catch {} - - expect(headerOnLastCall(header)).toContain(expected); - }); - test('resolves with the data response from the Expo API server', () => { - const data = { foo: 'bar' }; - mockFetchResponse({ data }); - - return expect(client().getPushNotificationReceiptsAsync([])).resolves.toEqual(data); + return expect(client().getPushNotificationReceiptsAsync([])).resolves.toEqual(mockReceipts); }); describe('if the response is not a map', () => { const data = [{ status: 'ok' }]; - beforeEach(() => mockFetchResponse({ data })); + beforeEach(() => { + server.use(http.post(getReceiptsApiUrl, () => HttpResponse.json({ data }))); + }); test('throws an error', () => expect(client().getPushNotificationReceiptsAsync([])).rejects.toThrow( `Expected Expo to respond with a map`, @@ -479,22 +475,6 @@ function countAndValidateMessages(chunks: ExpoPushMessage[][]): number { return totalMessageCount; } -function mockFetchResponse(body: object, status: number = 200) { - // Response objects can only be used one time, so each mock call should - // create a new one, hence the "Once." - mockedFetch.mockResolvedValueOnce(new Response(JSON.stringify(body), { status })); -} - function client(options: object = {}) { - return new ExpoClient(options); -} - -function headerOnLastCall(header: string): string | null { - expect(mockedFetch).toHaveBeenCalled(); - const { lastCall } = mockedFetch.mock; - assert(Array.isArray(lastCall)); - const [, options] = lastCall; - assert.ok(options); // we always call `fetch` with two arguments - assert(options.headers instanceof Headers); - return options.headers.get(header); + return new ExpoClient({ accessToken, ...options }); } diff --git a/yarn.lock b/yarn.lock index f5aecf5..eb72b78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,7 +39,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.0.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.9": +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.9": version: 7.24.1 resolution: "@babel/core@npm:7.24.1" dependencies: @@ -383,15 +383,6 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0": - version: 7.24.1 - resolution: "@babel/runtime@npm:7.24.1" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10c0/500c6a99ddd84f37c7bc5dbc84777af47b1372b20e879941670451d55484faf18a673c5ebee9ca2b0f36208a729417873b35b1b92e76f811620f6adf7b8cb0f1 - languageName: node - linkType: hard - "@babel/template@npm:^7.22.15, @babel/template@npm:^7.24.0, @babel/template@npm:^7.3.3": version: 7.24.0 resolution: "@babel/template@npm:7.24.0" @@ -450,6 +441,34 @@ __metadata: languageName: node linkType: hard +"@bundled-es-modules/cookie@npm:^2.0.0": + version: 2.0.0 + resolution: "@bundled-es-modules/cookie@npm:2.0.0" + dependencies: + cookie: "npm:^0.5.0" + checksum: 10c0/0655dd331b35d7b5b6dd2301c3bcfb7233018c0e3235a40ced1d53f00463ab92dc01f0091f153812867bc0ef0f8e0a157a30acb16e8d7ef149702bf8db9fe7a6 + languageName: node + linkType: hard + +"@bundled-es-modules/statuses@npm:^1.0.1": + version: 1.0.1 + resolution: "@bundled-es-modules/statuses@npm:1.0.1" + dependencies: + statuses: "npm:^2.0.1" + checksum: 10c0/c1a8ede3efa8da61ccda4b98e773582a9733edfbeeee569d4630785f8e018766202edb190a754a3ec7a7f6bd738e857829affc2fdb676b6dab4db1bb44e62785 + languageName: node + linkType: hard + +"@bundled-es-modules/tough-cookie@npm:^0.1.6": + version: 0.1.6 + resolution: "@bundled-es-modules/tough-cookie@npm:0.1.6" + dependencies: + "@types/tough-cookie": "npm:^4.0.5" + tough-cookie: "npm:^4.1.4" + checksum: 10c0/28bcac878bff6b34719ba3aa8341e9924772ee55de5487680ebe784981ec9fccb70ed5d46f563e2404855a04de606f9e56aa4202842d4f5835bc04a4fe820571 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -524,6 +543,61 @@ __metadata: languageName: node linkType: hard +"@inquirer/confirm@npm:^3.0.0": + version: 3.2.0 + resolution: "@inquirer/confirm@npm:3.2.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + checksum: 10c0/a2cbfc8ae9c880bba4cce1993f5c399fb0d12741fdd574917c87fceb40ece62ffa60e35aaadf4e62d7c114f54008e45aee5d6d90497bb62d493996c02725d243 + languageName: node + linkType: hard + +"@inquirer/core@npm:^9.1.0": + version: 9.2.1 + resolution: "@inquirer/core@npm:9.2.1" + dependencies: + "@inquirer/figures": "npm:^1.0.6" + "@inquirer/type": "npm:^2.0.0" + "@types/mute-stream": "npm:^0.0.4" + "@types/node": "npm:^22.5.5" + "@types/wrap-ansi": "npm:^3.0.0" + ansi-escapes: "npm:^4.3.2" + cli-width: "npm:^4.1.0" + mute-stream: "npm:^1.0.0" + signal-exit: "npm:^4.1.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10c0/11c14be77a9fa85831de799a585721b0a49ab2f3b7d8fd1780c48ea2b29229c6bdc94e7892419086d0f7734136c2ba87b6a32e0782571eae5bbd655b1afad453 + languageName: node + linkType: hard + +"@inquirer/figures@npm:^1.0.6": + version: 1.0.6 + resolution: "@inquirer/figures@npm:1.0.6" + checksum: 10c0/2a00cf8db0b038dfb3b7ac9d09fe57ba12f0349e6258ad821bfa8e2e3cd9127f34b88ed7cae3e3441586f988db4df16ba91d6d701f88e529e87d2c2130a5c138 + languageName: node + linkType: hard + +"@inquirer/type@npm:^1.5.3": + version: 1.5.5 + resolution: "@inquirer/type@npm:1.5.5" + dependencies: + mute-stream: "npm:^1.0.0" + checksum: 10c0/4c41736c09ba9426b5a9e44993bdd54e8f532e791518802e33866f233a2a6126a25c1c82c19d1abbf1df627e57b1b957dd3f8318ea96073d8bfc32193943bcb3 + languageName: node + linkType: hard + +"@inquirer/type@npm:^2.0.0": + version: 2.0.0 + resolution: "@inquirer/type@npm:2.0.0" + dependencies: + mute-stream: "npm:^1.0.0" + checksum: 10c0/8c663d52beb2b89a896d3c3d5cc3d6d024fa149e565555bcb42fa640cbe23fba7ff2c51445342cef1fe6e46305e2d16c1590fa1d11ad0ddf93a67b655ef41f0a + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -830,6 +904,20 @@ __metadata: languageName: node linkType: hard +"@mswjs/interceptors@npm:^0.35.8": + version: 0.35.8 + resolution: "@mswjs/interceptors@npm:0.35.8" + dependencies: + "@open-draft/deferred-promise": "npm:^2.2.0" + "@open-draft/logger": "npm:^0.3.0" + "@open-draft/until": "npm:^2.0.0" + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.3" + strict-event-emitter: "npm:^0.5.1" + checksum: 10c0/b60f2cee39d902a590029a14240da9fad2659bf9dc94cf8f58e84ee7d8c680b8168e5428b9cec92c48aefae348d05ecb49d7a48515209440f718cc52592286fd + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -879,6 +967,30 @@ __metadata: languageName: node linkType: hard +"@open-draft/deferred-promise@npm:^2.2.0": + version: 2.2.0 + resolution: "@open-draft/deferred-promise@npm:2.2.0" + checksum: 10c0/eafc1b1d0fc8edb5e1c753c5e0f3293410b40dde2f92688211a54806d4136887051f39b98c1950370be258483deac9dfd17cf8b96557553765198ef2547e4549 + languageName: node + linkType: hard + +"@open-draft/logger@npm:^0.3.0": + version: 0.3.0 + resolution: "@open-draft/logger@npm:0.3.0" + dependencies: + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.0" + checksum: 10c0/90010647b22e9693c16258f4f9adb034824d1771d3baa313057b9a37797f571181005bc50415a934eaf7c891d90ff71dcd7a9d5048b0b6bb438f31bef2c7c5c1 + languageName: node + linkType: hard + +"@open-draft/until@npm:^2.0.0, @open-draft/until@npm:^2.1.0": + version: 2.1.0 + resolution: "@open-draft/until@npm:2.1.0" + checksum: 10c0/61d3f99718dd86bb393fee2d7a785f961dcaf12f2055f0c693b27f4d0cd5f7a03d498a6d9289773b117590d794a43cd129366fd8e99222e4832f67b1653d54cf + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -980,6 +1092,13 @@ __metadata: languageName: node linkType: hard +"@types/cookie@npm:^0.6.0": + version: 0.6.0 + resolution: "@types/cookie@npm:0.6.0" + checksum: 10c0/5b326bd0188120fb32c0be086b141b1481fec9941b76ad537f9110e10d61ee2636beac145463319c71e4be67a17e85b81ca9e13ceb6e3bb63b93d16824d6c149 + languageName: node + linkType: hard + "@types/graceful-fs@npm:^4.1.3": version: 4.1.9 resolution: "@types/graceful-fs@npm:4.1.9" @@ -1021,6 +1140,15 @@ __metadata: languageName: node linkType: hard +"@types/mute-stream@npm:^0.0.4": + version: 0.0.4 + resolution: "@types/mute-stream@npm:0.0.4" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/944730fd7b398c5078de3c3d4d0afeec8584283bc694da1803fdfca14149ea385e18b1b774326f1601baf53898ce6d121a952c51eb62d188ef6fcc41f725c0dc + languageName: node + linkType: hard + "@types/node-fetch@npm:^2.6.11": version: 2.6.11 resolution: "@types/node-fetch@npm:2.6.11" @@ -1040,6 +1168,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^22.5.5": + version: 22.6.1 + resolution: "@types/node@npm:22.6.1" + dependencies: + undici-types: "npm:~6.19.2" + checksum: 10c0/79fdb14f268070eb21d25f3e81811b73c10dfcc65a638a6546fd97aa3e7dfe473f31a547fd21c43b8559a435b6ab26057066a47b5453bd1b1cdffe14430ac399 + languageName: node + linkType: hard + "@types/promise-retry@npm:^1.1.6": version: 1.1.6 resolution: "@types/promise-retry@npm:1.1.6" @@ -1063,6 +1200,27 @@ __metadata: languageName: node linkType: hard +"@types/statuses@npm:^2.0.4": + version: 2.0.5 + resolution: "@types/statuses@npm:2.0.5" + checksum: 10c0/4dacec0b29483a44be902a022a11a22b339de7a6e7b2059daa4f7add10cb6dbcc28d02d2a416fe9687e48d335906bf983065391836d4e7c847e55ddef4de8fad + languageName: node + linkType: hard + +"@types/tough-cookie@npm:^4.0.5": + version: 4.0.5 + resolution: "@types/tough-cookie@npm:4.0.5" + checksum: 10c0/68c6921721a3dcb40451543db2174a145ef915bc8bcbe7ad4e59194a0238e776e782b896c7a59f4b93ac6acefca9161fccb31d1ce3b3445cb6faa467297fb473 + languageName: node + linkType: hard + +"@types/wrap-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/wrap-ansi@npm:3.0.0" + checksum: 10c0/8d8f53363f360f38135301a06b596c295433ad01debd082078c33c6ed98b05a5c8fe8853a88265432126096084f4a135ec1564e3daad631b83296905509f90b3 + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.3 resolution: "@types/yargs-parser@npm:21.0.3" @@ -1260,7 +1418,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.2.1": +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -1711,7 +1869,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.0.2": +"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -1756,6 +1914,13 @@ __metadata: languageName: node linkType: hard +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f + languageName: node + linkType: hard + "cliui@npm:^8.0.1": version: 8.0.1 resolution: "cliui@npm:8.0.1" @@ -1836,10 +2001,10 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^3.0.0": - version: 3.36.1 - resolution: "core-js@npm:3.36.1" - checksum: 10c0/4f0ad2464535d809ba659226feca15bff14b9b5452518bddff8d81b9c94b0227b3027d9838f22f1dce664958acb4107b935cc0037695ae545edc2a303bca98bf +"cookie@npm:^0.5.0": + version: 0.5.0 + resolution: "cookie@npm:0.5.0" + checksum: 10c0/c01ca3ef8d7b8187bae434434582288681273b5a9ed27521d4d7f9f7928fe0c920df0decd9f9d3bbd2d14ac432b8c8cf42b98b3bdd5bfe0e6edddeebebe8b61d languageName: node linkType: hard @@ -2724,8 +2889,8 @@ __metadata: "@types/promise-retry": "npm:^1.1.6" eslint: "npm:^8.57.0" eslint-config-universe: "npm:^13.0.0" - fetch-mock: "npm:^9.11.0" jest: "npm:^29.7.0" + msw: "npm:^2.4.9" node-fetch: "npm:^2.6.0" prettier: "npm:^3.2.5" promise-limit: "npm:^2.7.0" @@ -2801,29 +2966,6 @@ __metadata: languageName: node linkType: hard -"fetch-mock@npm:^9.11.0": - version: 9.11.0 - resolution: "fetch-mock@npm:9.11.0" - dependencies: - "@babel/core": "npm:^7.0.0" - "@babel/runtime": "npm:^7.0.0" - core-js: "npm:^3.0.0" - debug: "npm:^4.1.1" - glob-to-regexp: "npm:^0.4.0" - is-subset: "npm:^0.1.1" - lodash.isequal: "npm:^4.5.0" - path-to-regexp: "npm:^2.2.1" - querystring: "npm:^0.2.0" - whatwg-url: "npm:^6.5.0" - peerDependencies: - node-fetch: "*" - peerDependenciesMeta: - node-fetch: - optional: true - checksum: 10c0/1bc2a83b34c10ad412ee381b5f9ee64c4daa6390f9b42a76ae7dc9dcbb5a6795f7f8748e315fcafe612e7a8abd05c8f2d3554a36d576f22e12497010d9dd8e4c - languageName: node - linkType: hard - "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -3059,13 +3201,6 @@ __metadata: languageName: node linkType: hard -"glob-to-regexp@npm:^0.4.0": - version: 0.4.1 - resolution: "glob-to-regexp@npm:0.4.1" - checksum: 10c0/0486925072d7a916f052842772b61c3e86247f0a80cc0deb9b5a3e8a1a9faad5b04fb6f58986a09f34d3e96cd2a22a24b7e9882fb1cf904c31e9a310de96c429 - languageName: node - linkType: hard - "glob@npm:^10.2.2, glob@npm:^10.3.10": version: 10.4.5 resolution: "glob@npm:10.4.5" @@ -3158,6 +3293,13 @@ __metadata: languageName: node linkType: hard +"graphql@npm:^16.8.1": + version: 16.9.0 + resolution: "graphql@npm:16.9.0" + checksum: 10c0/a8850f077ff767377237d1f8b1da2ec70aeb7623cdf1dfc9e1c7ae93accc0c8149c85abe68923be9871a2934b1bce5a2496f846d4d56e1cfb03eaaa7ddba9b6a + languageName: node + linkType: hard + "has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": version: 1.0.2 resolution: "has-bigints@npm:1.0.2" @@ -3220,6 +3362,13 @@ __metadata: languageName: node linkType: hard +"headers-polyfill@npm:^4.0.2": + version: 4.0.3 + resolution: "headers-polyfill@npm:4.0.3" + checksum: 10c0/53e85b2c6385f8d411945fb890c5369f1469ce8aa32a6e8d28196df38568148de640c81cf88cbc7c67767103dd9acba48f4f891982da63178fc6e34560022afe + languageName: node + linkType: hard + "html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -3515,6 +3664,13 @@ __metadata: languageName: node linkType: hard +"is-node-process@npm:^1.2.0": + version: 1.2.0 + resolution: "is-node-process@npm:1.2.0" + checksum: 10c0/5b24fda6776d00e42431d7bcd86bce81cb0b6cabeb944142fe7b077a54ada2e155066ad06dbe790abdb397884bdc3151e04a9707b8cd185099efbc79780573ed + languageName: node + linkType: hard + "is-number-object@npm:^1.0.4": version: 1.0.7 resolution: "is-number-object@npm:1.0.7" @@ -3580,13 +3736,6 @@ __metadata: languageName: node linkType: hard -"is-subset@npm:^0.1.1": - version: 0.1.1 - resolution: "is-subset@npm:0.1.1" - checksum: 10c0/d8125598ab9077a76684e18726fb915f5cea7a7358ed0c6ff723f4484d71a0a9981ee5aae06c44de99cfdef0fefce37438c6257ab129e53c82045ea0c2acdebf - languageName: node - linkType: hard - "is-symbol@npm:^1.0.2, is-symbol@npm:^1.0.3": version: 1.0.4 resolution: "is-symbol@npm:1.0.4" @@ -4360,13 +4509,6 @@ __metadata: languageName: node linkType: hard -"lodash.isequal@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.isequal@npm:4.5.0" - checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f - languageName: node - linkType: hard - "lodash.memoize@npm:^4.1.2": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" @@ -4381,13 +4523,6 @@ __metadata: languageName: node linkType: hard -"lodash.sortby@npm:^4.7.0": - version: 4.7.0 - resolution: "lodash.sortby@npm:4.7.0" - checksum: 10c0/fc48fb54ff7669f33bb32997cab9460757ee99fafaf72400b261c3e10fde21538e47d8cfcbe6a25a31bcb5b7b727c27d52626386fc2de24eb059a6d64a89cdf5 - languageName: node - linkType: hard - "loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -4657,6 +4792,45 @@ __metadata: languageName: node linkType: hard +"msw@npm:^2.4.9": + version: 2.4.9 + resolution: "msw@npm:2.4.9" + dependencies: + "@bundled-es-modules/cookie": "npm:^2.0.0" + "@bundled-es-modules/statuses": "npm:^1.0.1" + "@bundled-es-modules/tough-cookie": "npm:^0.1.6" + "@inquirer/confirm": "npm:^3.0.0" + "@mswjs/interceptors": "npm:^0.35.8" + "@open-draft/until": "npm:^2.1.0" + "@types/cookie": "npm:^0.6.0" + "@types/statuses": "npm:^2.0.4" + chalk: "npm:^4.1.2" + graphql: "npm:^16.8.1" + headers-polyfill: "npm:^4.0.2" + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.2" + path-to-regexp: "npm:^6.3.0" + strict-event-emitter: "npm:^0.5.1" + type-fest: "npm:^4.9.0" + yargs: "npm:^17.7.2" + peerDependencies: + typescript: ">= 4.8.x" + peerDependenciesMeta: + typescript: + optional: true + bin: + msw: cli/index.js + checksum: 10c0/2ba476d49807161514b1551ef2e121b9540941d3cff7be6ef83183df4f4509a27ae6db7258d2fc75c4b171c5963ecb489a8f49e57f062d2a4cb57b98d636c759 + languageName: node + linkType: hard + +"mute-stream@npm:^1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 10c0/dce2a9ccda171ec979a3b4f869a102b1343dee35e920146776780de182f16eae459644d187e38d59a3d37adf85685e1c17c38cf7bfda7e39a9880f7a1d10a74c + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -4856,6 +5030,13 @@ __metadata: languageName: node linkType: hard +"outvariant@npm:^1.4.0, outvariant@npm:^1.4.2, outvariant@npm:^1.4.3": + version: 1.4.3 + resolution: "outvariant@npm:1.4.3" + checksum: 10c0/5976ca7740349cb8c71bd3382e2a762b1aeca6f33dc984d9d896acdf3c61f78c3afcf1bfe9cc633a7b3c4b295ec94d292048f83ea2b2594fae4496656eba992c + languageName: node + linkType: hard + "p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" @@ -4974,10 +5155,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^2.2.1": - version: 2.4.0 - resolution: "path-to-regexp@npm:2.4.0" - checksum: 10c0/286e3f2ea633ae9447d9c6beb2974db67801ffd64927e70129604e1fb987d94c9b38fa70cc494cd088c78d521914edec87de7f11928848c9ac265632c8644fc8 +"path-to-regexp@npm:^6.3.0": + version: 6.3.0 + resolution: "path-to-regexp@npm:6.3.0" + checksum: 10c0/73b67f4638b41cde56254e6354e46ae3a2ebc08279583f6af3d96fe4664fc75788f74ed0d18ca44fa4a98491b69434f9eee73b97bb5314bd1b5adb700f5c18d6 languageName: node linkType: hard @@ -5106,7 +5287,14 @@ __metadata: languageName: node linkType: hard -"punycode@npm:^2.1.0": +"psl@npm:^1.1.33": + version: 1.9.0 + resolution: "psl@npm:1.9.0" + checksum: 10c0/6a3f805fdab9442f44de4ba23880c4eba26b20c8e8e0830eff1cb31007f6825dace61d17203c58bfe36946842140c97a1ba7f67bc63ca2d88a7ee052b65d97ab + languageName: node + linkType: hard + +"punycode@npm:^2.1.0, punycode@npm:^2.1.1": version: 2.3.1 resolution: "punycode@npm:2.3.1" checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 @@ -5120,10 +5308,10 @@ __metadata: languageName: node linkType: hard -"querystring@npm:^0.2.0": - version: 0.2.1 - resolution: "querystring@npm:0.2.1" - checksum: 10c0/6841b32bec4f16ffe7f5b5e4373b47ad451f079cde3a7f45e63e550f0ecfd8f8189ef81fb50079413b3fc1c59b06146e4c98192cb74ed7981aca72090466cd94 +"querystringify@npm:^2.1.1": + version: 2.2.0 + resolution: "querystringify@npm:2.2.0" + checksum: 10c0/3258bc3dbdf322ff2663619afe5947c7926a6ef5fb78ad7d384602974c467fadfc8272af44f5eb8cddd0d011aae8fabf3a929a8eee4b86edcc0a21e6bd10f9aa languageName: node linkType: hard @@ -5163,13 +5351,6 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.14.0": - version: 0.14.1 - resolution: "regenerator-runtime@npm:0.14.1" - checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4 - languageName: node - linkType: hard - "regexp.prototype.flags@npm:^1.5.2": version: 1.5.2 resolution: "regexp.prototype.flags@npm:1.5.2" @@ -5196,6 +5377,13 @@ __metadata: languageName: node linkType: hard +"requires-port@npm:^1.0.0": + version: 1.0.0 + resolution: "requires-port@npm:1.0.0" + checksum: 10c0/b2bfdd09db16c082c4326e573a82c0771daaf7b53b9ce8ad60ea46aa6e30aaf475fe9b164800b89f93b748d2c234d8abff945d2551ba47bf5698e04cd7713267 + languageName: node + linkType: hard + "resolve-cwd@npm:^3.0.0": version: 3.0.0 resolution: "resolve-cwd@npm:3.0.0" @@ -5432,7 +5620,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1": +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 @@ -5530,6 +5718,20 @@ __metadata: languageName: node linkType: hard +"statuses@npm:^2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 + languageName: node + linkType: hard + +"strict-event-emitter@npm:^0.5.1": + version: 0.5.1 + resolution: "strict-event-emitter@npm:0.5.1" + checksum: 10c0/f5228a6e6b6393c57f52f62e673cfe3be3294b35d6f7842fc24b172ae0a6e6c209fa83241d0e433fc267c503bc2f4ffdbe41a9990ff8ffd5ac425ec0489417f7 + languageName: node + linkType: hard + "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -5782,12 +5984,15 @@ __metadata: languageName: node linkType: hard -"tr46@npm:^1.0.1": - version: 1.0.1 - resolution: "tr46@npm:1.0.1" +"tough-cookie@npm:^4.1.4": + version: 4.1.4 + resolution: "tough-cookie@npm:4.1.4" dependencies: - punycode: "npm:^2.1.0" - checksum: 10c0/41525c2ccce86e3ef30af6fa5e1464e6d8bb4286a58ea8db09228f598889581ef62347153f6636cd41553dc41685bdfad0a9d032ef58df9fbb0792b3447d0f04 + psl: "npm:^1.1.33" + punycode: "npm:^2.1.1" + universalify: "npm:^0.2.0" + url-parse: "npm:^1.5.3" + checksum: 10c0/aca7ff96054f367d53d1e813e62ceb7dd2eda25d7752058a74d64b7266fd07be75908f3753a32ccf866a2f997604b414cfb1916d6e7f69bc64d9d9939b0d6c45 languageName: node linkType: hard @@ -5893,6 +6098,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.9.0": + version: 4.26.1 + resolution: "type-fest@npm:4.26.1" + checksum: 10c0/d2719ff8d380befe8a3c61068f37f28d6fa2849fd140c5d2f0f143099e371da6856aad7c97e56b83329d45bfe504afe9fd936a7cff600cc0d46aa9ffb008d6c6 + languageName: node + linkType: hard + "typed-array-buffer@npm:^1.0.2": version: 1.0.2 resolution: "typed-array-buffer@npm:1.0.2" @@ -5998,6 +6210,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.19.2": + version: 6.19.8 + resolution: "undici-types@npm:6.19.8" + checksum: 10c0/078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344 + languageName: node + linkType: hard + "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0" @@ -6016,6 +6235,13 @@ __metadata: languageName: node linkType: hard +"universalify@npm:^0.2.0": + version: 0.2.0 + resolution: "universalify@npm:0.2.0" + checksum: 10c0/cedbe4d4ca3967edf24c0800cfc161c5a15e240dac28e3ce575c689abc11f2c81ccc6532c8752af3b40f9120fb5e454abecd359e164f4f6aa44c29cd37e194fe + languageName: node + linkType: hard + "update-browserslist-db@npm:^1.0.13": version: 1.0.13 resolution: "update-browserslist-db@npm:1.0.13" @@ -6039,6 +6265,16 @@ __metadata: languageName: node linkType: hard +"url-parse@npm:^1.5.3": + version: 1.5.10 + resolution: "url-parse@npm:1.5.10" + dependencies: + querystringify: "npm:^2.1.1" + requires-port: "npm:^1.0.0" + checksum: 10c0/bd5aa9389f896974beb851c112f63b466505a04b4807cea2e5a3b7092f6fbb75316f0491ea84e44f66fed55f1b440df5195d7e3a8203f64fcefa19d182f5be87 + languageName: node + linkType: hard + "v8-to-istanbul@npm:^9.0.1": version: 9.2.0 resolution: "v8-to-istanbul@npm:9.2.0" @@ -6066,13 +6302,6 @@ __metadata: languageName: node linkType: hard -"webidl-conversions@npm:^4.0.2": - version: 4.0.2 - resolution: "webidl-conversions@npm:4.0.2" - checksum: 10c0/def5c5ac3479286dffcb604547628b2e6b46c5c5b8a8cfaa8c71dc3bafc85859bde5fbe89467ff861f571ab38987cf6ab3d6e7c80b39b999e50e803c12f3164f - languageName: node - linkType: hard - "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0" @@ -6083,17 +6312,6 @@ __metadata: languageName: node linkType: hard -"whatwg-url@npm:^6.5.0": - version: 6.5.0 - resolution: "whatwg-url@npm:6.5.0" - dependencies: - lodash.sortby: "npm:^4.7.0" - tr46: "npm:^1.0.1" - webidl-conversions: "npm:^4.0.2" - checksum: 10c0/5afeff7da025fbaecceca6a5e0cdc6d10666efab245d0e5d785263a09a16b3afce7a81712512e184c98e70bdb79fb20d0ecd34553e9c121a9ba4f36760db4226 - languageName: node - linkType: hard - "which-boxed-primitive@npm:^1.0.2": version: 1.0.2 resolution: "which-boxed-primitive@npm:1.0.2" @@ -6185,6 +6403,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c + languageName: node + linkType: hard + "wrap-ansi@npm:^8.1.0": version: 8.1.0 resolution: "wrap-ansi@npm:8.1.0" @@ -6241,7 +6470,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.3.1": +"yargs@npm:^17.3.1, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: @@ -6262,3 +6491,10 @@ __metadata: checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f languageName: node linkType: hard + +"yoctocolors-cjs@npm:^2.1.2": + version: 2.1.2 + resolution: "yoctocolors-cjs@npm:2.1.2" + checksum: 10c0/a0e36eb88fea2c7981eab22d1ba45e15d8d268626e6c4143305e2c1628fa17ebfaa40cd306161a8ce04c0a60ee0262058eab12567493d5eb1409780853454c6f + languageName: node + linkType: hard