diff --git a/.eslintrc.json b/.eslintrc.json index 6244a24f0..0a06f569f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -109,6 +109,7 @@ "allow": [ "@pact-foundation/pact/src/dsl/matchers", "msw/node", + "msw/lib/**", "io-ts/lib/*", "io-ts-types/lib/*", "fp-ts/lib/*", diff --git a/.yarn/cache/@bundled-es-modules-cookie-npm-2.0.0-936890fd62-53114eabbe.zip b/.yarn/cache/@bundled-es-modules-cookie-npm-2.0.0-936890fd62-53114eabbe.zip new file mode 100644 index 000000000..d80c144d7 Binary files /dev/null and b/.yarn/cache/@bundled-es-modules-cookie-npm-2.0.0-936890fd62-53114eabbe.zip differ diff --git a/.yarn/cache/@bundled-es-modules-js-levenshtein-npm-2.0.1-926d2f52c4-13d0cbd2b0.zip b/.yarn/cache/@bundled-es-modules-js-levenshtein-npm-2.0.1-926d2f52c4-13d0cbd2b0.zip new file mode 100644 index 000000000..68691f23e Binary files /dev/null and b/.yarn/cache/@bundled-es-modules-js-levenshtein-npm-2.0.1-926d2f52c4-13d0cbd2b0.zip differ diff --git a/.yarn/cache/@bundled-es-modules-statuses-npm-1.0.1-c6f8822c93-bcaa7de192.zip b/.yarn/cache/@bundled-es-modules-statuses-npm-1.0.1-c6f8822c93-bcaa7de192.zip new file mode 100644 index 000000000..6d4c0c8e7 Binary files /dev/null and b/.yarn/cache/@bundled-es-modules-statuses-npm-1.0.1-c6f8822c93-bcaa7de192.zip differ diff --git a/.yarn/cache/@mswjs-cookies-npm-0.2.2-fdd3f4ab67-23b1ef56d5.zip b/.yarn/cache/@mswjs-cookies-npm-0.2.2-fdd3f4ab67-23b1ef56d5.zip deleted file mode 100644 index d20ef6a8b..000000000 Binary files a/.yarn/cache/@mswjs-cookies-npm-0.2.2-fdd3f4ab67-23b1ef56d5.zip and /dev/null differ diff --git a/.yarn/cache/@mswjs-cookies-npm-1.0.0-19fc2c1f12-5ae38a3991.zip b/.yarn/cache/@mswjs-cookies-npm-1.0.0-19fc2c1f12-5ae38a3991.zip new file mode 100644 index 000000000..ef8cade0f Binary files /dev/null and b/.yarn/cache/@mswjs-cookies-npm-1.0.0-19fc2c1f12-5ae38a3991.zip differ diff --git a/.yarn/cache/@mswjs-interceptors-npm-0.17.10-c1199a9424-0e6d32f399.zip b/.yarn/cache/@mswjs-interceptors-npm-0.17.10-c1199a9424-0e6d32f399.zip deleted file mode 100644 index 91a458825..000000000 Binary files a/.yarn/cache/@mswjs-interceptors-npm-0.17.10-c1199a9424-0e6d32f399.zip and /dev/null differ diff --git a/.yarn/cache/@mswjs-interceptors-npm-0.25.7-d6921a0a97-d9dc1bf11e.zip b/.yarn/cache/@mswjs-interceptors-npm-0.25.7-d6921a0a97-d9dc1bf11e.zip new file mode 100644 index 000000000..180afe48a Binary files /dev/null and b/.yarn/cache/@mswjs-interceptors-npm-0.25.7-d6921a0a97-d9dc1bf11e.zip differ diff --git a/.yarn/cache/@open-draft-deferred-promise-npm-2.2.0-adf396dc9f-7f29d39725.zip b/.yarn/cache/@open-draft-deferred-promise-npm-2.2.0-adf396dc9f-7f29d39725.zip new file mode 100644 index 000000000..33b2d2a86 Binary files /dev/null and b/.yarn/cache/@open-draft-deferred-promise-npm-2.2.0-adf396dc9f-7f29d39725.zip differ diff --git a/.yarn/cache/@open-draft-logger-npm-0.3.0-12b03e55aa-7adfe3d0ed.zip b/.yarn/cache/@open-draft-logger-npm-0.3.0-12b03e55aa-7adfe3d0ed.zip new file mode 100644 index 000000000..9de979b80 Binary files /dev/null and b/.yarn/cache/@open-draft-logger-npm-0.3.0-12b03e55aa-7adfe3d0ed.zip differ diff --git a/.yarn/cache/@open-draft-until-npm-1.0.3-c0d6a46a29-323e92ebef.zip b/.yarn/cache/@open-draft-until-npm-1.0.3-c0d6a46a29-323e92ebef.zip deleted file mode 100644 index 821769c46..000000000 Binary files a/.yarn/cache/@open-draft-until-npm-1.0.3-c0d6a46a29-323e92ebef.zip and /dev/null differ diff --git a/.yarn/cache/@open-draft-until-npm-2.1.0-e27da33c52-140ea3b16f.zip b/.yarn/cache/@open-draft-until-npm-2.1.0-e27da33c52-140ea3b16f.zip new file mode 100644 index 000000000..17f96da70 Binary files /dev/null and b/.yarn/cache/@open-draft-until-npm-2.1.0-e27da33c52-140ea3b16f.zip differ diff --git a/.yarn/cache/@types-set-cookie-parser-npm-2.4.2-eb947592c8-c31bf04eb9.zip b/.yarn/cache/@types-set-cookie-parser-npm-2.4.2-eb947592c8-c31bf04eb9.zip deleted file mode 100644 index 5e22a03d8..000000000 Binary files a/.yarn/cache/@types-set-cookie-parser-npm-2.4.2-eb947592c8-c31bf04eb9.zip and /dev/null differ diff --git a/.yarn/cache/@types-statuses-npm-2.0.3-9aae17abcc-ff47ea1177.zip b/.yarn/cache/@types-statuses-npm-2.0.3-9aae17abcc-ff47ea1177.zip new file mode 100644 index 000000000..8b685152e Binary files /dev/null and b/.yarn/cache/@types-statuses-npm-2.0.3-9aae17abcc-ff47ea1177.zip differ diff --git a/.yarn/cache/@xmldom-xmldom-npm-0.8.7-b6ccf72365-593d4429c2.zip b/.yarn/cache/@xmldom-xmldom-npm-0.8.7-b6ccf72365-593d4429c2.zip deleted file mode 100644 index 4ad958dc5..000000000 Binary files a/.yarn/cache/@xmldom-xmldom-npm-0.8.7-b6ccf72365-593d4429c2.zip and /dev/null differ diff --git a/.yarn/cache/@zxing-text-encoding-npm-0.9.0-8426ff59e9-c23b12aee7.zip b/.yarn/cache/@zxing-text-encoding-npm-0.9.0-8426ff59e9-c23b12aee7.zip deleted file mode 100644 index 8c1761bf3..000000000 Binary files a/.yarn/cache/@zxing-text-encoding-npm-0.9.0-8426ff59e9-c23b12aee7.zip and /dev/null differ diff --git a/.yarn/cache/cookie-npm-0.4.2-7761894d5f-a00833c998.zip b/.yarn/cache/cookie-npm-0.4.2-7761894d5f-a00833c998.zip deleted file mode 100644 index 2a478448c..000000000 Binary files a/.yarn/cache/cookie-npm-0.4.2-7761894d5f-a00833c998.zip and /dev/null differ diff --git a/.yarn/cache/headers-polyfill-npm-3.2.5-5873ac13a0-a3c4bdd661.zip b/.yarn/cache/headers-polyfill-npm-3.2.5-5873ac13a0-a3c4bdd661.zip deleted file mode 100644 index aef1e54c9..000000000 Binary files a/.yarn/cache/headers-polyfill-npm-3.2.5-5873ac13a0-a3c4bdd661.zip and /dev/null differ diff --git a/.yarn/cache/headers-polyfill-npm-4.0.2-0cef9f97de-a95280ed58.zip b/.yarn/cache/headers-polyfill-npm-4.0.2-0cef9f97de-a95280ed58.zip new file mode 100644 index 000000000..a2f105f8a Binary files /dev/null and b/.yarn/cache/headers-polyfill-npm-4.0.2-0cef9f97de-a95280ed58.zip differ diff --git a/.yarn/cache/is-arguments-npm-1.1.1-eff4f6d4d7-7f02700ec2.zip b/.yarn/cache/is-arguments-npm-1.1.1-eff4f6d4d7-7f02700ec2.zip deleted file mode 100644 index 9b956d869..000000000 Binary files a/.yarn/cache/is-arguments-npm-1.1.1-eff4f6d4d7-7f02700ec2.zip and /dev/null differ diff --git a/.yarn/cache/msw-npm-1.3.2-572bf57281-c2d4f7747f.zip b/.yarn/cache/msw-npm-1.3.2-572bf57281-c2d4f7747f.zip deleted file mode 100644 index 603ebaf8a..000000000 Binary files a/.yarn/cache/msw-npm-1.3.2-572bf57281-c2d4f7747f.zip and /dev/null differ diff --git a/.yarn/cache/msw-npm-2.0.3-b18b206488-3c0ef3c2fc.zip b/.yarn/cache/msw-npm-2.0.3-b18b206488-3c0ef3c2fc.zip new file mode 100644 index 000000000..48961a67e Binary files /dev/null and b/.yarn/cache/msw-npm-2.0.3-b18b206488-3c0ef3c2fc.zip differ diff --git a/.yarn/cache/set-cookie-parser-npm-2.4.8-3e04c5b17b-e15b5df9a5.zip b/.yarn/cache/set-cookie-parser-npm-2.4.8-3e04c5b17b-e15b5df9a5.zip deleted file mode 100644 index a2835af30..000000000 Binary files a/.yarn/cache/set-cookie-parser-npm-2.4.8-3e04c5b17b-e15b5df9a5.zip and /dev/null differ diff --git a/.yarn/cache/strict-event-emitter-npm-0.2.8-e8b9131760-6ac06fe72a.zip b/.yarn/cache/strict-event-emitter-npm-0.2.8-e8b9131760-6ac06fe72a.zip deleted file mode 100644 index 5021fd684..000000000 Binary files a/.yarn/cache/strict-event-emitter-npm-0.2.8-e8b9131760-6ac06fe72a.zip and /dev/null differ diff --git a/.yarn/cache/strict-event-emitter-npm-0.4.6-b845d23c7d-4f4f290961.zip b/.yarn/cache/strict-event-emitter-npm-0.4.6-b845d23c7d-4f4f290961.zip deleted file mode 100644 index a9eeb0478..000000000 Binary files a/.yarn/cache/strict-event-emitter-npm-0.4.6-b845d23c7d-4f4f290961.zip and /dev/null differ diff --git a/.yarn/cache/strict-event-emitter-npm-0.5.1-8414bf36b3-350480431b.zip b/.yarn/cache/strict-event-emitter-npm-0.5.1-8414bf36b3-350480431b.zip new file mode 100644 index 000000000..e770ed49e Binary files /dev/null and b/.yarn/cache/strict-event-emitter-npm-0.5.1-8414bf36b3-350480431b.zip differ diff --git a/.yarn/cache/util-npm-0.12.5-3668276f26-705e51f0de.zip b/.yarn/cache/util-npm-0.12.5-3668276f26-705e51f0de.zip deleted file mode 100644 index 61e97ee3a..000000000 Binary files a/.yarn/cache/util-npm-0.12.5-3668276f26-705e51f0de.zip and /dev/null differ diff --git a/.yarn/cache/web-encoding-npm-1.1.5-d5a3c7dc3d-2234a2b122.zip b/.yarn/cache/web-encoding-npm-1.1.5-d5a3c7dc3d-2234a2b122.zip deleted file mode 100644 index d8a67d1ed..000000000 Binary files a/.yarn/cache/web-encoding-npm-1.1.5-d5a3c7dc3d-2234a2b122.zip and /dev/null differ diff --git a/__config__/jest.setup-pacts-serlo-org-database-layer.ts b/__config__/jest.setup-pacts-serlo-org-database-layer.ts index 398ddc483..995e6fab7 100644 --- a/__config__/jest.setup-pacts-serlo-org-database-layer.ts +++ b/__config__/jest.setup-pacts-serlo-org-database-layer.ts @@ -1,5 +1,5 @@ import { Pact } from '@pact-foundation/pact' -import { rest } from 'msw' +import { http } from 'msw' import path from 'path' import { @@ -36,31 +36,18 @@ beforeAll(async () => { beforeEach(async () => { await createBeforeEach() global.server.use( - rest.get( + http.get( new RegExp(process.env.SERLO_ORG_DATABASE_LAYER_HOST.replace('.', '\\.')), - async (req, res, ctx) => { - const url = req.url - const pactRes = await fetch(`http://127.0.0.1:${port}${url.pathname}`) - return res(ctx.status(pactRes.status), ctx.json(await pactRes.json())) + async ({ request }) => { + const url = new URL(request.url) + return fetch(`http://127.0.0.1:${port}${url.pathname}`) }, ), - rest.post( + http.post( new RegExp(process.env.SERLO_ORG_DATABASE_LAYER_HOST.replace('.', '\\.')), - async (req, res, ctx) => { - const url = req.url - const pactRes = await fetch(`http://127.0.0.1:${port}${url.pathname}`, { - method: 'POST', - body: - typeof req.body === 'object' - ? JSON.stringify(req.body) - : (req.body as string), - headers: { - 'Content-Type': req.headers.get('Content-Type')!, - }, - }) - return pactRes.headers.get('Content-Type') - ? res(ctx.status(pactRes.status), ctx.json(await pactRes.json())) - : res(ctx.status(pactRes.status)) + async ({ request }) => { + const url = new URL(request.url) + return fetch(`http://127.0.0.1:${port}${url.pathname}`, request) }, ), ) diff --git a/__config__/jest.setup.ts b/__config__/jest.setup.ts index 84e1a5d29..754bb7268 100644 --- a/__config__/jest.setup.ts +++ b/__config__/jest.setup.ts @@ -31,14 +31,14 @@ beforeAll(async () => { onUnhandledRequest(req) { if ( req.method === 'POST' && - req.url.href.includes(process.env.SERLO_ORG_DATABASE_LAYER_HOST) + req.url.includes(process.env.SERLO_ORG_DATABASE_LAYER_HOST) ) { console.error('Found an unhandled request for message %s', req.text()) } else { console.error( 'Found an unhandled %s request to %s', req.method, - req.url.href, + req.url, ) } }, diff --git a/__config__/setup.ts b/__config__/setup.ts index 728e648ff..2253fef7d 100644 --- a/__config__/setup.ts +++ b/__config__/setup.ts @@ -1,4 +1,4 @@ -import { rest } from 'msw' +import { http, HttpResponse } from 'msw' import { SetupServer, setupServer } from 'msw/node' import { @@ -63,17 +63,13 @@ export async function createBeforeEach() { givenSpreadheetApi(defaultSpreadsheetApi()) global.server.use( - rest.post( - 'https://127.0.0.1/api/0/envelope', - async (req, res, ctx) => { - global.sentryEvents.push( - ...(await req.text()) - .split('\n') - .map((x) => JSON.parse(x) as Sentry.Event), - ) - return res(ctx.status(200)) - }, - ), + http.post('https://127.0.0.1/api/0/envelope', async ({ request }) => { + const text = await request.text() + global.sentryEvents.push( + ...text.split('\n').map((x) => JSON.parse(x) as Sentry.Event), + ) + return new HttpResponse(null, { status: 200 }) + }), ) await global.cache.flush() diff --git a/__tests__/__utils__/handlers.ts b/__tests__/__utils__/handlers.ts index 339f4ac4f..3b26825c5 100644 --- a/__tests__/__utils__/handlers.ts +++ b/__tests__/__utils__/handlers.ts @@ -1,9 +1,19 @@ -import { MockedRequest, rest } from 'msw' +import { + DefaultBodyType, + HttpResponse, + PathParams, + ResponseResolver, + StrictRequest, + http, +} from 'msw' +import { HttpRequestResolverExtras } from 'msw/lib/core/handlers/HttpHandler' +import { ResponseResolverInfo } from 'msw/lib/core/handlers/RequestHandler' import * as R from 'ramda' -import { createFakeIdentity, RestResolver } from './services' +import { createFakeIdentity } from './services' import { Model } from '~/internals/graphql' import { DatabaseLayer } from '~/model' +import { Payload } from '~/model/database-layer' import { DiscriminatorType } from '~/model/decoder' const ForDefinitions = { @@ -45,8 +55,9 @@ const ForDefinitions = { return { ...entity, trashed: true } }), ) - given('DeletedEntitiesQuery').isDefinedBy((req, res, ctx) => { - const { first, after, instance } = req.body.payload + given('DeletedEntitiesQuery').isDefinedBy(async ({ request }) => { + const body = await request.json() + const { first, after, instance } = body.payload const entitiesByInstance = instance ? entities.filter((entity) => entity.instance === instance) @@ -66,7 +77,7 @@ const ForDefinitions = { dateOfDeletion: entity.date, } }) - return res(ctx.json({ deletedEntities })) + return HttpResponse.json({ deletedEntities }) }) }, } @@ -106,7 +117,7 @@ export function given(type: M) { }), ) }, - isDefinedBy(resolver: MessageResolver>) { + isDefinedBy(resolver: ResponseResolver) { global.server.use( createDatabaseLayerHandler({ matchType: type, @@ -117,7 +128,12 @@ export function given(type: M) { }, } }, - isDefinedBy(resolver: MessageResolver>) { + isDefinedBy( + resolver: ResponseResolver< + Record, + { payload: Payload } + >, + ) { global.server.use( createDatabaseLayerHandler({ matchType: type, resolver }), ) @@ -206,14 +222,12 @@ function createMessageHandler( matchType: message.type, matchPayloads: message.payload === undefined ? undefined : [message.payload], - resolver: (_req, res, ctx) => { - return (once ? res.once : res)( - ctx.status(statusCode ?? 200), - ...(body === undefined - ? [] - : [ctx.json(body as Record)]), - ) + resolver: () => { + return HttpResponse.json(body === undefined ? [] : body, { + status: statusCode ?? 200, + }) }, + options: { once }, }) } @@ -223,36 +237,70 @@ function createDatabaseLayerHandler< >(args: { matchType: MessageType matchPayloads?: Partial[] - resolver: MessageResolver + resolver: ResponseResolver, { payload: Payload }> + options?: { once: boolean } }) { - const { matchType, matchPayloads, resolver } = args + const { matchType, matchPayloads, resolver, options } = args - const handler = rest.post(getDatabaseLayerUrl({ path: '/' }), resolver) + return http.post( + getDatabaseLayerUrl({ path: '/' }), + withTypeAndPayload(resolver, matchType, matchPayloads) as ResponseResolver< + HttpRequestResolverExtras, + { payload: Payload }, + undefined + >, + options, + ) +} - // Only use this handler if message matches - handler.predicate = (req: MockedRequest>) => - req?.body?.type === matchType && - (matchPayloads === undefined || - matchPayloads.some((payload) => - R.equals({ ...req.body.payload, ...payload }, req.body.payload), - )) +function withTypeAndPayload< + MessageType extends string = string, + Payload = DefaultPayloadType, +>( + resolver: ResponseResolver, { payload: Payload }>, + expectedType: MessageType, + expectedPayloads?: Partial[], +) { + return async ( + args: ResponseResolverInfo< + HttpRequestResolverExtras, + { payload: Payload } + >, + ) => { + const { request } = args - return handler + // Ignore requests that have a non-JSON body. + const contentType = request.headers.get('Content-Type') || '' + if (!contentType.includes('application/json')) { + return + } + + // Clone the request and read it as JSON. + const actualBody = (await request.clone().json()) as { + type: string + payload: DefaultPayloadType[] + } + + const isTypeMatching = actualBody.type === expectedType + const isPayloadMatching = + expectedPayloads === undefined || + expectedPayloads.some((payload) => + R.equals({ ...actualBody.payload, ...payload }, actualBody.payload), + ) + + // Compare two objects using "lodash". + if (!isTypeMatching || !isPayloadMatching) { + return + } + + return resolver(args) + } } function getDatabaseLayerUrl({ path }: { path: string }) { return `http://${process.env.SERLO_ORG_DATABASE_LAYER_HOST}${path}` } -type MessageResolver< - MessageType extends string = string, - Payload = DefaultPayloadType, -> = RestResolver> -type BodyType< - MessageType extends string = string, - Payload = DefaultPayloadType, -> = Required> - interface MessagePayload< MessageType extends string = string, Payload = DefaultPayloadType, @@ -287,20 +335,29 @@ function createCommunityChatHandler({ body: Record }) { const url = `${process.env.ROCKET_CHAT_URL}api/v1/${endpoint}` - const handler = rest.get(url, (req, res, ctx) => { + const handler = http.get(url, ({ request }) => { if ( - req.headers.get('X-User-Id') !== process.env.ROCKET_CHAT_API_USER_ID || - req.headers.get('X-Auth-Token') !== process.env.ROCKET_CHAT_API_AUTH_TOKEN + request.headers.get('X-User-Id') !== + process.env.ROCKET_CHAT_API_USER_ID || + request.headers.get('X-Auth-Token') !== + process.env.ROCKET_CHAT_API_AUTH_TOKEN ) - return res(ctx.status(403)) + return new HttpResponse(null, { + status: 403, + }) - return res(ctx.json(body)) + return HttpResponse.json(body) }) - handler.predicate = (req: MockedRequest) => { - return R.toPairs(parameters).every( - ([name, value]) => req.url.searchParams.get(name) === value, - ) + handler.predicate = ({ + request, + }: { + request: StrictRequest + }) => { + return R.toPairs(parameters).every(([name, value]) => { + const url = new URL(request.url) + return url.searchParams.get(name) === value + }) } return handler diff --git a/__tests__/__utils__/services.ts b/__tests__/__utils__/services.ts index 2ee4467cb..d57b594a1 100644 --- a/__tests__/__utils__/services.ts +++ b/__tests__/__utils__/services.ts @@ -4,14 +4,7 @@ import type { IdentityApiDeleteIdentityRequest, IdentityApiListIdentitiesRequest, } from '@ory/client' -import { - RestRequest, - ResponseResolver, - rest, - restContext, - PathParams, - DefaultBodyType, -} from 'msw' +import { HttpResponse, ResponseResolver, http } from 'msw' import { v4 as uuidv4 } from 'uuid' import type { Identity, KratosDB } from '~/internals/authentication' @@ -82,30 +75,41 @@ export function createFakeIdentity(user: Model<'User'>): Identity { const spreadsheets: Record = {} -export function givenSpreadheetApi(resolver: SpreadsheetApiResolver) { +export function givenSpreadheetApi(resolver: ResponseResolver) { const url = 'https://sheets.googleapis.com/v4/spreadsheets/:spreadsheetId/values/:range' - global.server.use(rest.get(url, resolver)) + global.server.use(http.get(url, resolver)) } -export function defaultSpreadsheetApi(): SpreadsheetApiResolver { - return (req, res, ctx) => { - const searchParams = req.url.searchParams +export function defaultSpreadsheetApi(): ResponseResolver { + return ({ request, params }) => { + const url = new URL(request.url) + const { searchParams } = url if (searchParams.get('key') !== process.env.GOOGLE_SPREADSHEET_API_SECRET) { - return res(ctx.status(403)) + return new HttpResponse(null, { + status: 403, + }) + } + + const typedParams = params as { + spreadsheetId: string + range: string } - const { spreadsheetId } = req.params - const range = decodeURIComponent(req.params.range) + const { spreadsheetId } = typedParams + const range = decodeURIComponent(typedParams.range) const majorDimension = searchParams.get('majorDimension') as MajorDimension const values = spreadsheets[toKey({ spreadsheetId, range, majorDimension })] - if (values === undefined) return res(ctx.status(404)) + if (values === undefined) + return new HttpResponse(null, { + status: 404, + }) - return res(ctx.json({ range, majorDimension, values })) + return HttpResponse.json({ range, majorDimension, values }) } } @@ -121,38 +125,22 @@ export function returnsJson({ }: { status?: number json: unknown -}): RestResolver { - return (_req, res, ctx) => - res(ctx.status(status), ctx.json(json as Record)) +}): ResponseResolver { + return () => HttpResponse.json(json as Record, { status }) } -export function returnsMalformedJson(): RestResolver { - return (_req, res, ctx) => res(ctx.body('MALFORMED JSON')) +export function returnsMalformedJson(): ResponseResolver { + return () => new HttpResponse('MALFORMED JSON') } -export function hasInternalServerError(): RestResolver { - return (_req, res, ctx) => res(ctx.status(500)) +export function hasInternalServerError(): ResponseResolver { + return () => new HttpResponse(null, { status: 500 }) } -export type RestResolver< - RequestBodyType extends DefaultBodyType = DefaultBodyType, - RequestParamsType extends PathParams = PathParams, -> = ResponseResolver< - RestRequest, - typeof restContext -> - function toKey(query: SpreadsheetQuery) { return [query.spreadsheetId, query.range, query.majorDimension].join('/') } -type SpreadsheetApiResolver = RestResolver - -interface SpreadsheetQueryBasic extends PathParams { - range: string - spreadsheetId: string -} - interface SpreadsheetQuery { range: string spreadsheetId: string diff --git a/__tests__/examples/data-sources/how-to-create-a-mutation.ts b/__tests__/examples/data-sources/how-to-create-a-mutation.ts index dc1658104..178fbd2d0 100644 --- a/__tests__/examples/data-sources/how-to-create-a-mutation.ts +++ b/__tests__/examples/data-sources/how-to-create-a-mutation.ts @@ -1,5 +1,5 @@ import * as t from 'io-ts' -import { rest } from 'msw' +import { http, HttpResponse } from 'msw' import { createMutation, @@ -41,21 +41,28 @@ describe('How to create a mutation in a data source: update the content of an ar // with a JSON of the form `{ "success": boolean }` to determine whether the // response was successful or not. global.server.use( - rest.put<{ newContent: string }, { id: string }, { success: boolean }>( + http.put( 'http://database-api.serlo.org/articles/:id', - (req, res, ctx) => { - const id = parseInt(req.params.id) + async ({ request, params }) => { + const typedParams = params as { id: string } + const id = parseInt(typedParams.id) // given id is not a number -> return with "400 Bad Request" - if (Number.isNaN(id)) return res(ctx.status(400)) + if (Number.isNaN(id)) + return new HttpResponse(null, { + status: 400, + }) if (contentDatabase[id] !== undefined) { + const body = (await request.json()) as { + newContent: string + } // article with given id is in database - contentDatabase[id] = req.body.newContent + contentDatabase[id] = body.newContent - return res(ctx.json({ success: true as boolean })) + return HttpResponse.json({ success: true as boolean }) } else { - return res(ctx.json({ success: false as boolean })) + return HttpResponse.json({ success: false as boolean }) } }, ), diff --git a/__tests__/examples/data-sources/how-to-create-a-query.ts b/__tests__/examples/data-sources/how-to-create-a-query.ts index 1d41b7be6..413610d82 100644 --- a/__tests__/examples/data-sources/how-to-create-a-query.ts +++ b/__tests__/examples/data-sources/how-to-create-a-query.ts @@ -1,6 +1,6 @@ import { option as O } from 'fp-ts' import * as t from 'io-ts' -import { rest } from 'msw' +import { http, HttpResponse } from 'msw' import { createTestEnvironment } from '../../__utils__' import { createQuery, Query } from '~/internals/data-source-helper/query' @@ -36,25 +36,28 @@ describe('How to create a query in a data source: Fetching the content of an art // REST API in front of the database which exposes a GET endpoint // /article/:id with which the content of an article can be fetched. global.server.use( - rest.get( - 'http://database-api.serlo.org/article/:id', - (req, res, ctx) => { - const id = parseInt(req.params.id) - - // given id is not a number -> return with "400 Bad Request" - if (Number.isNaN(id)) return res(ctx.status(400)) - - const content = contentDatabase[id] - - if (content !== undefined) { - // article with given id is in database - return res(ctx.json({ id, content })) - } else { - // No article found -> return "404 Not Found" - return res(ctx.status(404)) - } - }, - ), + http.get('http://database-api.serlo.org/article/:id', ({ params }) => { + const typedParams = params as { id: string } + const id = parseInt(typedParams.id) + + // given id is not a number -> return with "400 Bad Request" + if (Number.isNaN(id)) + return new HttpResponse(null, { + status: 400, + }) + + const content = contentDatabase[id] + + if (content !== undefined) { + // article with given id is in database + return HttpResponse.json({ id, content }) + } else { + // No article found -> return "404 Not Found" + return new HttpResponse(null, { + status: 404, + }) + } + }), ) // We assume that the cache is empty in each of the following test cases diff --git a/__tests__/schema/ai.ts b/__tests__/schema/ai.ts index 10c53ae6b..cce75e912 100644 --- a/__tests__/schema/ai.ts +++ b/__tests__/schema/ai.ts @@ -1,14 +1,9 @@ import gql from 'graphql-tag' -import { rest } from 'msw' +import { HttpResponse, ResponseResolver, http } from 'msw' import type { OpenAI } from 'openai' import { user as baseUser } from '../../__fixtures__' -import { - Client, - RestResolver, - given, - hasInternalServerError, -} from '../__utils__' +import { Client, given, hasInternalServerError } from '../__utils__' interface ChoicesFromChatCompletion { choices: OpenAI.ChatCompletion['choices'] @@ -51,8 +46,10 @@ const query = new Client({ userId: user.id }).prepareQuery({ }) beforeAll(() => { - mockOpenAIServer((_req, res, ctx) => { - return res(ctx.status(200), ctx.json(mockedOpenAiResponse)) + mockOpenAIServer(() => { + return HttpResponse.json(mockedOpenAiResponse, { + status: 200, + }) }) }) @@ -87,9 +84,9 @@ test('fails when internal server error open ai api occurs', async () => { await query.shouldFailWithError('INTERNAL_SERVER_ERROR') }) -function mockOpenAIServer(resolver: RestResolver) { +function mockOpenAIServer(resolver: ResponseResolver) { // server is a global variable that is defined in __config__/setup.ts global.server.use( - rest.post('https://api.openai.com/v1/chat/completions', resolver), + http.post('https://api.openai.com/v1/chat/completions', resolver), ) } diff --git a/__tests__/schema/entity/checkout-revision.ts b/__tests__/schema/entity/checkout-revision.ts index 366c7b428..7617893da 100644 --- a/__tests__/schema/entity/checkout-revision.ts +++ b/__tests__/schema/entity/checkout-revision.ts @@ -1,5 +1,6 @@ import { Instance } from '@serlo/api' import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { article as baseArticle, @@ -48,7 +49,7 @@ beforeEach(() => { reason: 'reason', revisionId: unrevisedRevision.id, }) - .isDefinedBy((_req, res, ctx) => { + .isDefinedBy(() => { given('UuidQuery').for({ ...unrevisedRevision, trashed: false }) given('UuidQuery').for({ ...article, @@ -56,7 +57,7 @@ beforeEach(() => { }) given('UnrevisedEntitiesQuery').for([]) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) }) diff --git a/__tests__/schema/entity/reject-revision.ts b/__tests__/schema/entity/reject-revision.ts index fff3c2a6b..f577f9bb6 100644 --- a/__tests__/schema/entity/reject-revision.ts +++ b/__tests__/schema/entity/reject-revision.ts @@ -1,5 +1,6 @@ import { Instance } from '@serlo/api' import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { article as baseArticle, @@ -46,11 +47,11 @@ beforeEach(() => { reason: 'reason', revisionId: currentRevision.id, }) - .isDefinedBy((_req, res, ctx) => { + .isDefinedBy(() => { given('UuidQuery').for({ ...currentRevision, trashed: true }) given('UnrevisedEntitiesQuery').for([]) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) }) diff --git a/__tests__/schema/entity/set.ts b/__tests__/schema/entity/set.ts index a68ca8f4a..1a35dead1 100644 --- a/__tests__/schema/entity/set.ts +++ b/__tests__/schema/entity/set.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import R from 'ramda' import { @@ -380,13 +381,16 @@ testCases.forEach((testCase) => { ...entityAddRevisionPayload, input: { ...entityAddRevisionPayload.input, needsReview: false }, }) - .isDefinedBy((_, res, ctx) => { + .isDefinedBy(() => { given('UuidQuery').for( { ...testCase.entity, currentRevisionId: newRevision.id }, newRevision, ) - return res(ctx.json({ success: true, revisionId: newRevision.id })) + return HttpResponse.json({ + success: true, + revisionId: newRevision.id, + }) }) given('SubscriptionsQuery') @@ -586,26 +590,28 @@ describe('Autoreview entities', () => { taxonomy, ) - given('EntityAddRevisionMutation').isDefinedBy((req, res, ctx) => { + given('EntityAddRevisionMutation').isDefinedBy(async ({ request }) => { + const body = await request.json() + given('UuidQuery').for(newRevision) - if (!req.body.payload.input.needsReview) + if (!body.payload.input.needsReview) given('UuidQuery').for({ ...entity, currentRevisionId: newRevisionId }) - return res(ctx.json({ success: true, revisionId: newRevisionId })) + return HttpResponse.json({ success: true, revisionId: newRevisionId }) }) - given('EntityCreateMutation').isDefinedBy((req, res, ctx) => { + given('EntityCreateMutation').isDefinedBy(async ({ request }) => { + const body = await request.json() + given('UuidQuery').for(newRevision) - return res( - ctx.json({ - ...entity, - currentRevisionId: req.body.payload.input.needsReview - ? oldRevisionId - : newRevisionId, - }), - ) + return HttpResponse.json({ + ...entity, + currentRevisionId: body.payload.input.needsReview + ? oldRevisionId + : newRevisionId, + }) }) }) diff --git a/__tests__/schema/entity/sort.ts b/__tests__/schema/entity/sort.ts index 5e39b0211..2bd1c8c6f 100644 --- a/__tests__/schema/entity/sort.ts +++ b/__tests__/schema/entity/sort.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { exerciseGroup as baseExerciseGroup, @@ -47,15 +48,16 @@ test('returns "{ success: true }" when mutation could be successfully executed', }) test('updates the cache of groupedExercise', async () => { - given('EntitySortMutation').isDefinedBy((req, res, ctx) => { - const { childrenIds } = req.body.payload + given('EntitySortMutation').isDefinedBy(async ({ request }) => { + const body = await request.json() + const { childrenIds } = body.payload given('UuidQuery').for({ ...exerciseGroup, exerciseIds: childrenIds.map(castToUuid), }) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) given('UuidQuery').for( @@ -91,15 +93,16 @@ test('updates the cache of groupedExercise', async () => { }) test('updates the cache of Course', async () => { - given('EntitySortMutation').isDefinedBy((req, res, ctx) => { - const { childrenIds } = req.body.payload + given('EntitySortMutation').isDefinedBy(async ({ request }) => { + const body = await request.json() + const { childrenIds } = body.payload given('UuidQuery').for({ ...course, pageIds: childrenIds.map(castToUuid), }) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) given('UuidQuery').for( diff --git a/__tests__/schema/entity/update-license.ts b/__tests__/schema/entity/update-license.ts index 496f9eb34..270fb64da 100644 --- a/__tests__/schema/entity/update-license.ts +++ b/__tests__/schema/entity/update-license.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { article, user } from '../../../__fixtures__' import { given, Client } from '../../__utils__' @@ -28,10 +29,10 @@ beforeEach(() => { entityId: article.id, licenseId: 4, }) - .isDefinedBy((_req, res, ctx) => { + .isDefinedBy(() => { given('UuidQuery').for({ ...article, licenseId: newLicenseId }) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) }) diff --git a/__tests__/schema/events.ts b/__tests__/schema/events.ts index 49e8a7a3b..8a12dc135 100644 --- a/__tests__/schema/events.ts +++ b/__tests__/schema/events.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import * as R from 'ramda' import { @@ -381,8 +382,9 @@ test('AbstractEntity.events returns events for this entity', async () => { }) function setupEvents(allEvents: Model<'AbstractNotificationEvent'>[]) { - given('EventsQuery').isDefinedBy((req, res, ctx) => { - const { after, objectId, actorId, first, instance } = req.body.payload + given('EventsQuery').isDefinedBy(async ({ request }) => { + const body = await request.json() + const { after, objectId, actorId, first, instance } = body.payload const filteredEvents = [...allEvents] .reverse() @@ -393,12 +395,10 @@ function setupEvents(allEvents: Model<'AbstractNotificationEvent'>[]) { .filter((event) => objectId == null || event.objectId === objectId) .filter((event) => after == null || event.id < after) - return res( - ctx.json({ - events: filteredEvents.slice(0, first), - hasNextPage: filteredEvents.length > first, - }), - ) + return HttpResponse.json({ + events: filteredEvents.slice(0, first), + hasNextPage: filteredEvents.length > first, + }) }) } diff --git a/__tests__/schema/page/add-revision.ts b/__tests__/schema/page/add-revision.ts index 0df7b0982..5a79617ab 100644 --- a/__tests__/schema/page/add-revision.ts +++ b/__tests__/schema/page/add-revision.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import * as R from 'ramda' import { page, pageRevision, user as baseUser } from '../../../__fixtures__' @@ -32,11 +33,12 @@ describe('PageAddRevisionMutation', () => { beforeEach(() => { given('UuidQuery').for(user, page, pageRevision) - given('PageAddRevisionMutation').isDefinedBy((req, res, ctx) => { + given('PageAddRevisionMutation').isDefinedBy(async ({ request }) => { + const body = await request.json() // const { title, content } = req.body.payload const newRevision = { ...pageRevision, - ...R.pick(['title', 'content'], req.body.payload), + ...R.pick(['title', 'content'], body.payload), id: newRevisionId, } given('UuidQuery').for(newRevision) @@ -45,7 +47,7 @@ describe('PageAddRevisionMutation', () => { revisionIds: [newRevision.id, ...page.revisionIds], currentRevisionId: newRevision.id, }) - return res(ctx.json({ success: true, revisionId: newRevision.id })) + return HttpResponse.json({ success: true, revisionId: newRevision.id }) }) }) diff --git a/__tests__/schema/page/checkout-revision.ts b/__tests__/schema/page/checkout-revision.ts index 71c8130d3..85563c446 100644 --- a/__tests__/schema/page/checkout-revision.ts +++ b/__tests__/schema/page/checkout-revision.ts @@ -1,5 +1,6 @@ import { Instance } from '@serlo/api' import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { page as basePage, @@ -41,14 +42,14 @@ beforeEach(() => { reason: 'reason', revisionId: unrevisedRevision.id, }) - .isDefinedBy((_req, res, ctx) => { + .isDefinedBy(() => { given('UuidQuery').for({ ...page, currentRevisionId: unrevisedRevision.id, }) given('UuidQuery').for({ ...unrevisedRevision, trashed: false }) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) }) diff --git a/__tests__/schema/page/create.ts b/__tests__/schema/page/create.ts index 3caac2fca..20670d61c 100644 --- a/__tests__/schema/page/create.ts +++ b/__tests__/schema/page/create.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { user as baseUser } from '../../../__fixtures__' import { given, Client } from '../../__utils__' @@ -41,8 +42,9 @@ describe('PageCreateMutation', () => { beforeEach(() => { given('UuidQuery').for(user) - given('PageCreateMutation').isDefinedBy((req, res, ctx) => { - const { content, instance, licenseId, title, userId } = req.body.payload + given('PageCreateMutation').isDefinedBy(async ({ request }) => { + const body = await request.json() + const { content, instance, licenseId, title, userId } = body.payload const newPageRevisionId = castToUuid(19769) @@ -72,7 +74,7 @@ describe('PageCreateMutation', () => { given('UuidQuery').for(newPage, newPageRevision) - return res(ctx.json({ ...newPage })) + return HttpResponse.json({ ...newPage }) }) }) diff --git a/__tests__/schema/page/pages.ts b/__tests__/schema/page/pages.ts index bd63b30c3..5cdaf151f 100644 --- a/__tests__/schema/page/pages.ts +++ b/__tests__/schema/page/pages.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { page as basePage } from '../../../__fixtures__' import { given, Client, Query, nextUuid } from '../../__utils__' @@ -26,13 +27,14 @@ beforeEach(() => { }) given('UuidQuery').for(page, page2) - given('PagesQuery').isDefinedBy((req, res, ctx) => { - const { instance } = req.body.payload + given('PagesQuery').isDefinedBy(async ({ request }) => { + const body = await request.json() + const { instance } = body.payload if (instance === Instance.En) { - return res(ctx.json({ success: true, pages: [page.id] })) + return HttpResponse.json({ success: true, pages: [page.id] }) } - return res(ctx.json({ success: true, pages: [page.id, page2.id] })) + return HttpResponse.json({ success: true, pages: [page.id, page2.id] }) }) }) diff --git a/__tests__/schema/page/reject-revision.ts b/__tests__/schema/page/reject-revision.ts index b8e3e5d9a..fec05ecf3 100644 --- a/__tests__/schema/page/reject-revision.ts +++ b/__tests__/schema/page/reject-revision.ts @@ -1,5 +1,6 @@ import { Instance } from '@serlo/api' import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { page as basePage, @@ -41,10 +42,10 @@ beforeEach(() => { reason: 'reason', revisionId: currentRevision.id, }) - .isDefinedBy((_req, res, ctx) => { + .isDefinedBy(() => { given('UuidQuery').for({ ...currentRevision, trashed: true }) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) }) diff --git a/__tests__/schema/taxonomy-term/create-entity-links.ts b/__tests__/schema/taxonomy-term/create-entity-links.ts index e496d6bbe..ecc2fa159 100644 --- a/__tests__/schema/taxonomy-term/create-entity-links.ts +++ b/__tests__/schema/taxonomy-term/create-entity-links.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { article, @@ -43,7 +44,7 @@ beforeEach(() => { given('TaxonomyCreateEntityLinksMutation') .withPayload({ ...input, userId: user.id }) - .isDefinedBy((_req, res, ctx) => { + .isDefinedBy(() => { given('UuidQuery').for({ ...exercise, taxonomyTermIds: [ @@ -55,7 +56,7 @@ beforeEach(() => { ...taxonomyTermCurriculumTopic, childrenIds: [...taxonomyTermCurriculumTopic.childrenIds, exercise.id], }) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) }) diff --git a/__tests__/schema/taxonomy-term/create.ts b/__tests__/schema/taxonomy-term/create.ts index c0c04e2ce..db93b7189 100644 --- a/__tests__/schema/taxonomy-term/create.ts +++ b/__tests__/schema/taxonomy-term/create.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { taxonomyTermCurriculumTopic, @@ -68,7 +69,7 @@ describe('TaxonomyTermCreateMutation', () => { given('TaxonomyTermCreateMutation') .withPayload(payload) - .isDefinedBy((_req, res, ctx) => { + .isDefinedBy(() => { given('UuidQuery').for(taxonomyTermTopic) const updatedParent = { @@ -79,7 +80,7 @@ describe('TaxonomyTermCreateMutation', () => { ], } given('UuidQuery').for(updatedParent) - return res(ctx.json(taxonomyTermTopic)) + return HttpResponse.json(taxonomyTermTopic) }) const query = new Client({ userId: user.id }) diff --git a/__tests__/schema/taxonomy-term/delete-entity-links.ts b/__tests__/schema/taxonomy-term/delete-entity-links.ts index 95fb227e3..31694462c 100644 --- a/__tests__/schema/taxonomy-term/delete-entity-links.ts +++ b/__tests__/schema/taxonomy-term/delete-entity-links.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { article, @@ -37,7 +38,7 @@ beforeEach(() => { given('TaxonomyDeleteEntityLinksMutation') .withPayload({ ...input, userId: user.id }) - .isDefinedBy((_req, res, ctx) => { + .isDefinedBy(() => { given('UuidQuery').for({ ...article, taxonomyTermIds: [], @@ -46,7 +47,7 @@ beforeEach(() => { ...taxonomyTermCurriculumTopic, childrenIds: [], }) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) }) diff --git a/__tests__/schema/taxonomy-term/set-name-and-description.ts b/__tests__/schema/taxonomy-term/set-name-and-description.ts index bd9fd3afe..efa9ea47a 100644 --- a/__tests__/schema/taxonomy-term/set-name-and-description.ts +++ b/__tests__/schema/taxonomy-term/set-name-and-description.ts @@ -1,10 +1,12 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { taxonomyTermCurriculumTopic, user as baseUser, } from '../../../__fixtures__' import { Client, given } from '../../__utils__' +import { Payload } from '~/model/database-layer' describe('TaxonomyTermSetNameAndDescriptionMutation', () => { const user = { ...baseUser, roles: ['de_architect'] } @@ -99,8 +101,11 @@ describe('TaxonomyTermSetNameAndDescriptionMutation', () => { ...input, userId: user.id, }) - .isDefinedBy((req, res, ctx) => { - const { name, description } = req.body.payload + .isDefinedBy(async ({ request }) => { + const body = (await request.json()) as { + payload: Payload<'TaxonomyTermSetNameAndDescriptionMutation'> + } + const { name, description } = body.payload given('UuidQuery').for({ ...taxonomyTermCurriculumTopic, @@ -108,7 +113,7 @@ describe('TaxonomyTermSetNameAndDescriptionMutation', () => { description, }) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) await mutation.shouldReturnData({ taxonomyTerm: { setNameAndDescription: { success: true } }, diff --git a/__tests__/schema/taxonomy-term/sort.ts b/__tests__/schema/taxonomy-term/sort.ts index bea806279..f965af4b1 100644 --- a/__tests__/schema/taxonomy-term/sort.ts +++ b/__tests__/schema/taxonomy-term/sort.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { article, @@ -36,8 +37,9 @@ const mutation = new Client({ userId: user.id }) beforeEach(() => { given('UuidQuery').for(user, taxonomyTerm) - given('TaxonomySortMutation').isDefinedBy((req, res, ctx) => { - const { childrenIds } = req.body.payload + given('TaxonomySortMutation').isDefinedBy(async ({ request }) => { + const body = await request.json() + const { childrenIds } = body.payload if ( [...childrenIds].sort().join(',') !== [...taxonomyTerm.childrenIds].sort().join(',') @@ -52,7 +54,7 @@ beforeEach(() => { childrenIds: childrenIds.map(castToUuid), }) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) }) diff --git a/__tests__/schema/user/delete-bots.ts b/__tests__/schema/user/delete-bots.ts index e2700c149..b0e7ddfee 100644 --- a/__tests__/schema/user/delete-bots.ts +++ b/__tests__/schema/user/delete-bots.ts @@ -1,12 +1,11 @@ import gql from 'graphql-tag' -import { rest } from 'msw' +import { HttpResponse, ResponseResolver, http } from 'msw' import { article, user, user2 } from '../../../__fixtures__' import { given, Client, Query, - RestResolver, castToUuid, returnsJson, assertErrorEvent, @@ -44,59 +43,66 @@ beforeEach(() => { } given('UuidQuery').for(users, article) - given('UserDeleteBotsMutation').isDefinedBy((req, res, ctx) => { - const { botIds } = req.body.payload + given('UserDeleteBotsMutation').isDefinedBy(async ({ request }) => { + const body = await request.json() + const { botIds } = body.payload for (const id of botIds) { given('UuidQuery').withPayload({ id }).returnsNotFound() } - return res( - ctx.json({ - success: true, - emailHashes: botIds.map((id) => emailHash({ id })), - }), - ) + return HttpResponse.json({ + success: true, + emailHashes: botIds.map((id) => emailHash({ id })), + }) }) chatUsers = [user.username] - givenChatDeleteUserEndpoint((req, res, ctx) => { - const { headers, body } = req + givenChatDeleteUserEndpoint(async ({ request }) => { + const body = (await request.json()) as { + username: string + } + const { headers } = request if ( headers.get('X-Auth-Token') !== process.env.ROCKET_CHAT_API_AUTH_TOKEN || headers.get('X-User-Id') !== process.env.ROCKET_CHAT_API_USER_ID ) - return res(ctx.status(400)) + return new HttpResponse(null, { status: 400 }) const { username } = body if (chatUsers.includes(username)) { chatUsers = chatUsers.filter((x) => x !== username) - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) } else { - return res(ctx.json({ success: false, errorType: 'error-invalid-user' })) + return HttpResponse.json({ + success: false, + errorType: 'error-invalid-user', + }) } }) - givenMailchimpDeleteEmailEndpoint((req, res, ctx) => { - const authHeader = req.headers.get('Authorization') ?? '' + givenMailchimpDeleteEmailEndpoint(({ request, params }) => { + const authHeader = request.headers.get('Authorization') ?? '' const key = Buffer.from(authHeader.slice('Basic '.length), 'base64') .toString() .split(':')[1] - if (key !== process.env.MAILCHIMP_API_KEY) return res(ctx.status(405)) + if (key !== process.env.MAILCHIMP_API_KEY) + return new HttpResponse(null, { status: 405 }) - const { emailHash } = req.params + const typedParams = params as { emailHash: string } + const { emailHash } = typedParams if (mailchimpEmails.includes(emailHash)) { mailchimpEmails = mailchimpEmails.filter((x) => x !== emailHash) - return res(ctx.status(204)) + return new HttpResponse(null, { status: 204 }) } else { - return res(ctx.status(404)) + return new HttpResponse(null, { status: 404 }) } }) }) @@ -232,22 +238,18 @@ test('fails when kratos has an error', async () => { await mutation.shouldFailWithError('INTERNAL_SERVER_ERROR') }) -function givenChatDeleteUserEndpoint( - resolver: RestResolver<{ username: string }>, -) { +function givenChatDeleteUserEndpoint(resolver: ResponseResolver) { global.server.use( - rest.post(`${process.env.ROCKET_CHAT_URL}api/v1/users.delete`, resolver), + http.post(`${process.env.ROCKET_CHAT_URL}api/v1/users.delete`, resolver), ) } -function givenMailchimpDeleteEmailEndpoint( - resolver: RestResolver, -) { +function givenMailchimpDeleteEmailEndpoint(resolver: ResponseResolver) { const url = `https://us5.api.mailchimp.com/3.0/` + `lists/a7bb2bbc4f/members/:emailHash/actions/delete-permanent` - global.server.use(rest.post(url, resolver)) + global.server.use(http.post(url, resolver)) } function emailHash(user: { id: number }) { diff --git a/__tests__/schema/user/delete-regular-users.ts b/__tests__/schema/user/delete-regular-users.ts index a00030758..a5d437815 100644 --- a/__tests__/schema/user/delete-regular-users.ts +++ b/__tests__/schema/user/delete-regular-users.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import * as R from 'ramda' import { user as baseUser } from '../../../__fixtures__' @@ -30,12 +31,13 @@ beforeEach(() => { }) .withInput({ users: users.map(R.pick(['id', 'username'])) }) - given('UserDeleteRegularUsersMutation').isDefinedBy((req, res, ctx) => { - const { userId } = req.body.payload + given('UserDeleteRegularUsersMutation').isDefinedBy(async ({ request }) => { + const body = await request.json() + const { userId } = body.payload given('UuidQuery').withPayload({ id: userId }).returnsNotFound() - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) given('UuidQuery').for(users) @@ -56,15 +58,16 @@ test('runs successfully when mutation could be successfully executed', async () }) test('runs partially when one of the mutations failed', async () => { - given('UserDeleteRegularUsersMutation').isDefinedBy((req, res, ctx) => { - const { userId } = req.body.payload + given('UserDeleteRegularUsersMutation').isDefinedBy(async ({ request }) => { + const body = await request.json() + const { userId } = body.payload if (userId === user.id) - return res(ctx.json({ success: false, reason: 'failure!' })) + return HttpResponse.json({ success: false, reason: 'failure!' }) given('UuidQuery').withPayload({ id: userId }).returnsNotFound() - return res(ctx.json({ success: true })) + return HttpResponse.json({ success: true }) }) expect(global.kratos.identities).toHaveLength(users.length) diff --git a/__tests__/schema/uuid/set-state.ts b/__tests__/schema/uuid/set-state.ts index 3b5bae6f4..d4faeb123 100644 --- a/__tests__/schema/uuid/set-state.ts +++ b/__tests__/schema/uuid/set-state.ts @@ -1,4 +1,5 @@ import gql from 'graphql-tag' +import { HttpResponse } from 'msw' import { article, page, user as baseUser } from '../../../__fixtures__' import { nextUuid, Client, given } from '../../__utils__' @@ -28,8 +29,14 @@ beforeEach(() => { given('UuidQuery').for(user, articles) given('UuidSetStateMutation') .withPayload({ userId: user.id, trashed: true }) - .isDefinedBy((req, res, ctx) => { - const { ids, trashed } = req.body.payload + .isDefinedBy(async ({ request }) => { + const body = (await request.json()) as { + payload: { + ids: number[] + trashed: boolean + } + } + const { ids, trashed } = body.payload for (const id of ids) { const article = articles.find((x) => x.id === id) @@ -37,11 +44,15 @@ beforeEach(() => { if (article != null) { article.trashed = trashed } else { - return res(ctx.status(500)) + return new HttpResponse(null, { + status: 500, + }) } } - return res(ctx.status(200)) + return new HttpResponse(null, { + status: 200, + }) }) }) diff --git a/package.json b/package.json index 3dead8709..2de4452ad 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "jest": "^29.7.0", "jest-transform-graphql": "^2.1.0", "lerna": "^7.4.2", - "msw": "^1.3.2", + "msw": "^2.0.3", "npm-run-all": "^4.1.5", "prettier": "^3.0.3", "prettier-plugin-packagejson": "^2.4.6", diff --git a/yarn.lock b/yarn.lock index 13b49baf5..8d5d6b5c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1643,6 +1643,33 @@ __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: ^0.5.0 + checksum: 53114eabbedda20ba6c63f45dcea35c568616d22adf5d1882cef9761f65ae636bf47e0c66325572cc8e3a335e0257caf5f76ff1287990d9e9265be7bc9767a87 + languageName: node + linkType: hard + +"@bundled-es-modules/js-levenshtein@npm:^2.0.1": + version: 2.0.1 + resolution: "@bundled-es-modules/js-levenshtein@npm:2.0.1" + dependencies: + js-levenshtein: ^1.1.6 + checksum: 13d0cbd2b00e563e09a797559dcff8c7e208c1f71e1787535a3d248f7e3d33ef3f0809b9f498d41788ab5fd399882dcca79917d70d97921b7dde94a282c1b7d8 + 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: ^2.0.1 + checksum: bcaa7de192e73056950b5fd20e75140d8d09074b1adc4437924b2051bb02b4dbf568c96e67d53b220fb7d735c3446e2ba746599cb1793ab2d23dd2ef230a8622 + languageName: node + linkType: hard + "@cnakazawa/watch@npm:^1.0.3": version: 1.0.4 resolution: "@cnakazawa/watch@npm:1.0.4" @@ -3034,29 +3061,24 @@ __metadata: languageName: node linkType: hard -"@mswjs/cookies@npm:^0.2.2": - version: 0.2.2 - resolution: "@mswjs/cookies@npm:0.2.2" - dependencies: - "@types/set-cookie-parser": ^2.4.0 - set-cookie-parser: ^2.4.6 - checksum: 23b1ef56d57efcc1b44600076f531a1fb703855af342a31e01bad4adaf0dab51f6d3b5595a95a7988c3f612ba075835f9a06c52833205284d101eb9a51dd72b0 +"@mswjs/cookies@npm:^1.0.0": + version: 1.0.0 + resolution: "@mswjs/cookies@npm:1.0.0" + checksum: 5ae38a399162fb96b367b9d0da24c1cf64dea9f5a0a07255a14c4b047da788049268059b0350a0b7788f37a32834c9fbef2fc5f60755d0d98bcf95bcb6ffe92e languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.17.10": - version: 0.17.10 - resolution: "@mswjs/interceptors@npm:0.17.10" +"@mswjs/interceptors@npm:^0.25.1": + version: 0.25.7 + resolution: "@mswjs/interceptors@npm:0.25.7" dependencies: - "@open-draft/until": ^1.0.3 - "@types/debug": ^4.1.7 - "@xmldom/xmldom": ^0.8.3 - debug: ^4.3.3 - headers-polyfill: 3.2.5 + "@open-draft/deferred-promise": ^2.2.0 + "@open-draft/logger": ^0.3.0 + "@open-draft/until": ^2.0.0 + is-node-process: ^1.2.0 outvariant: ^1.2.1 - strict-event-emitter: ^0.2.4 - web-encoding: ^1.1.5 - checksum: 0e6d32f399144b5cefe6fd7620f2776c83adc9bbbbccf2eb4ea347332be059f585136c44168c09b544c41cd3d686f88e43432e10192227a24fbb0c98a2f52dc8 + strict-event-emitter: ^0.5.1 + checksum: d9dc1bf11e088b52ba01a4fbcedb3bf7451592bd0255e3591a6478f5465caf0fbd9264c7a32b0aad5bbd31baa2c9eabd6a36b5deace23a82061e34c884eb5fcb languageName: node linkType: hard @@ -3464,10 +3486,27 @@ __metadata: languageName: node linkType: hard -"@open-draft/until@npm:^1.0.3": - version: 1.0.3 - resolution: "@open-draft/until@npm:1.0.3" - checksum: 323e92ebef0150ed0f8caedc7d219b68cdc50784fa4eba0377eef93533d3f46514eb2400ced83dda8c51bddc3d2c7b8e9cf95e5ec85ab7f62dfc015d174f62f2 +"@open-draft/deferred-promise@npm:^2.2.0": + version: 2.2.0 + resolution: "@open-draft/deferred-promise@npm:2.2.0" + checksum: 7f29d39725bb8ab5b62f89d88a4202ce2439ac740860979f9e3d0015dfe4bc3daddcfa5727fa4eed482fdbee770aa591b1136b98b0a0f0569a65294f35bdf56a + 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: ^1.2.0 + outvariant: ^1.4.0 + checksum: 7adfe3d0ed8ca32333ce2a77f9a93d561ebc89c989eaa9722f1dc8a2d2854f5de1bef6fa6894cdf58e16fa4dd9cfa99444ea1f5cac6eb1518e9247911ed042d5 + 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: 140ea3b16f4a3a6a729c1256050e20a93d408d7aa1e125648ce2665b3c526ed452510c6e4a6f4b15d95fb5e41203fb51510eb8fbc8812d5e5a91880293d66471 languageName: node linkType: hard @@ -3884,7 +3923,7 @@ __metadata: jest: ^29.7.0 jest-transform-graphql: ^2.1.0 lerna: ^7.4.2 - msw: ^1.3.2 + msw: ^2.0.3 npm-run-all: ^4.1.5 prettier: ^3.0.3 prettier-plugin-packagejson: ^2.4.6 @@ -4184,7 +4223,7 @@ __metadata: languageName: node linkType: hard -"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.7": +"@types/debug@npm:^4.0.0": version: 4.1.7 resolution: "@types/debug@npm:4.1.7" dependencies: @@ -4623,15 +4662,6 @@ __metadata: languageName: node linkType: hard -"@types/set-cookie-parser@npm:^2.4.0": - version: 2.4.2 - resolution: "@types/set-cookie-parser@npm:2.4.2" - dependencies: - "@types/node": "*" - checksum: c31bf04eb9620829dc3c91bced74ac934ad039d20d20893fb5acac0f08769cbd4eef3bf7502a0289c7be59c3e9cfa456147b4e88bff47dd1b9efb4995ba5d5a3 - languageName: node - linkType: hard - "@types/stack-utils@npm:^1.0.1": version: 1.0.1 resolution: "@types/stack-utils@npm:1.0.1" @@ -4646,6 +4676,13 @@ __metadata: languageName: node linkType: hard +"@types/statuses@npm:^2.0.1": + version: 2.0.3 + resolution: "@types/statuses@npm:2.0.3" + checksum: ff47ea1177c9ed37d733e8d089663fc45f006a4eea319e4cd558a49feb17159e32ab53013fbb6f32bf30f869f652eb88d00d42cb819d88009fb7ce0afe48d73c + languageName: node + linkType: hard + "@types/tough-cookie@npm:*": version: 4.0.1 resolution: "@types/tough-cookie@npm:4.0.1" @@ -5030,13 +5067,6 @@ __metadata: languageName: node linkType: hard -"@xmldom/xmldom@npm:^0.8.3": - version: 0.8.7 - resolution: "@xmldom/xmldom@npm:0.8.7" - checksum: 593d4429c2281ee7799adcb6ff8604b68cf30ce0721537e3e380287b423e67c7ac197d90987f932b4fd3febc409ded8435706e7f90fbba6e22e08740477341d1 - languageName: node - linkType: hard - "@yarnpkg/lockfile@npm:^1.1.0": version: 1.1.0 resolution: "@yarnpkg/lockfile@npm:1.1.0" @@ -5087,13 +5117,6 @@ __metadata: languageName: node linkType: hard -"@zxing/text-encoding@npm:0.9.0": - version: 0.9.0 - resolution: "@zxing/text-encoding@npm:0.9.0" - checksum: c23b12aee7639382e4949961304a1294776afaffa40f579e09ffecd0e5e68cf26ef3edd75009de46da8a536e571448755ca68b3e2ea707d53793c0edb2e2c34a - languageName: node - linkType: hard - "JSONStream@npm:^1.3.5": version: 1.3.5 resolution: "JSONStream@npm:1.3.5" @@ -6788,7 +6811,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1": +"chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -7388,20 +7411,13 @@ __metadata: languageName: node linkType: hard -"cookie@npm:0.5.0": +"cookie@npm:0.5.0, cookie@npm:^0.5.0": version: 0.5.0 resolution: "cookie@npm:0.5.0" checksum: 1f4bd2ca5765f8c9689a7e8954183f5332139eb72b6ff783d8947032ec1fdf43109852c178e21a953a30c0dd42257828185be01b49d1eb1a67fd054ca588a180 languageName: node linkType: hard -"cookie@npm:^0.4.2": - version: 0.4.2 - resolution: "cookie@npm:0.4.2" - checksum: a00833c998bedf8e787b4c342defe5fa419abd96b32f4464f718b91022586b8f1bafbddd499288e75c037642493c83083da426c6a9080d309e3bd90fd11baa9b - languageName: node - linkType: hard - "copy-descriptor@npm:^0.1.0": version: 0.1.1 resolution: "copy-descriptor@npm:0.1.1" @@ -9663,7 +9679,7 @@ __metadata: languageName: node linkType: hard -"formdata-node@npm:^4.3.2": +"formdata-node@npm:4.4.1, formdata-node@npm:^4.3.2": version: 4.4.1 resolution: "formdata-node@npm:4.4.1" dependencies: @@ -10578,10 +10594,10 @@ __metadata: languageName: node linkType: hard -"headers-polyfill@npm:3.2.5": - version: 3.2.5 - resolution: "headers-polyfill@npm:3.2.5" - checksum: a3c4bdd661584fd39e40c0f91412abc514616edfbd20d29a75567e591f90ef5c445c8e209b7f3c2b2375d27e95e4690f33417368a168d4832484a93861ab6a3c +"headers-polyfill@npm:^4.0.1": + version: 4.0.2 + resolution: "headers-polyfill@npm:4.0.2" + checksum: a95280ed58df429fc86c4f49b21596be3ea3f5f3d790e7d75238668df9b90b292f15a968c7c19ae1db88c0ae036dd1bf363a71b8e771199d82848e2d8b3c6c2e languageName: node linkType: hard @@ -11101,16 +11117,6 @@ __metadata: languageName: node linkType: hard -"is-arguments@npm:^1.0.4": - version: 1.1.1 - resolution: "is-arguments@npm:1.1.1" - dependencies: - call-bind: ^1.0.2 - has-tostringtag: ^1.0.0 - checksum: 7f02700ec2171b691ef3e4d0e3e6c0ba408e8434368504bb593d0d7c891c0dbfda6d19d30808b904a6cb1929bca648c061ba438c39f296c2a8ca083229c49f27 - languageName: node - linkType: hard - "is-array-buffer@npm:^3.0.1, is-array-buffer@npm:^3.0.2": version: 3.0.2 resolution: "is-array-buffer@npm:3.0.2" @@ -11329,7 +11335,7 @@ __metadata: languageName: node linkType: hard -"is-generator-function@npm:^1.0.10, is-generator-function@npm:^1.0.7": +"is-generator-function@npm:^1.0.10": version: 1.0.10 resolution: "is-generator-function@npm:1.0.10" dependencies: @@ -11575,7 +11581,7 @@ __metadata: languageName: node linkType: hard -"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.12, is-typed-array@npm:^1.1.3, is-typed-array@npm:^1.1.9": +"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.12, is-typed-array@npm:^1.1.9": version: 1.1.12 resolution: "is-typed-array@npm:1.1.12" dependencies: @@ -14716,37 +14722,41 @@ __metadata: languageName: node linkType: hard -"msw@npm:^1.3.2": - version: 1.3.2 - resolution: "msw@npm:1.3.2" - dependencies: - "@mswjs/cookies": ^0.2.2 - "@mswjs/interceptors": ^0.17.10 - "@open-draft/until": ^1.0.3 +"msw@npm:^2.0.3": + version: 2.0.3 + resolution: "msw@npm:2.0.3" + dependencies: + "@bundled-es-modules/cookie": ^2.0.0 + "@bundled-es-modules/js-levenshtein": ^2.0.1 + "@bundled-es-modules/statuses": ^1.0.1 + "@mswjs/cookies": ^1.0.0 + "@mswjs/interceptors": ^0.25.1 + "@open-draft/until": ^2.1.0 "@types/cookie": ^0.4.1 "@types/js-levenshtein": ^1.1.1 - chalk: ^4.1.1 + "@types/statuses": ^2.0.1 + chalk: ^4.1.2 chokidar: ^3.4.2 - cookie: ^0.4.2 + formdata-node: 4.4.1 graphql: ^16.8.1 - headers-polyfill: 3.2.5 + headers-polyfill: ^4.0.1 inquirer: ^8.2.0 is-node-process: ^1.2.0 js-levenshtein: ^1.1.6 node-fetch: ^2.6.7 outvariant: ^1.4.0 path-to-regexp: ^6.2.0 - strict-event-emitter: ^0.4.3 + strict-event-emitter: ^0.5.0 type-fest: ^2.19.0 yargs: ^17.3.1 peerDependencies: - typescript: ">= 4.4.x <= 5.2.x" + typescript: ">= 4.7.x <= 5.2.x" peerDependenciesMeta: typescript: optional: true bin: msw: cli/index.js - checksum: c2d4f7747f5806f0fd8d8cc3ca250ee1c2a7a6cd608de43f95bd072ba1fb13cdce0b52932ce9bf8f4a21b194d2815db535501e224ec8f7052593447fe1c0cb70 + checksum: 3c0ef3c2fc11064dc76d6b6769e0fe575efd3c0b8a189ed280b0bd6b04b601a7a80c270e02a76d839e497243202a81cb4daec54a08429ad4802d33cc87efa131 languageName: node linkType: hard @@ -17992,13 +18002,6 @@ __metadata: languageName: node linkType: hard -"set-cookie-parser@npm:^2.4.6": - version: 2.4.8 - resolution: "set-cookie-parser@npm:2.4.8" - checksum: e15b5df9a56ab06d4895286033a6aff7b318ad024310df058b5821b3539cc06f716ef529618cac0dd78df40e37830de715f388c0f97f84062dd9be2326efcd0c - languageName: node - linkType: hard - "set-function-length@npm:^1.1.1": version: 1.1.1 resolution: "set-function-length@npm:1.1.1" @@ -18634,7 +18637,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:2.0.1": +"statuses@npm:2.0.1, statuses@npm:^2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" checksum: 18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb @@ -18671,19 +18674,10 @@ __metadata: languageName: node linkType: hard -"strict-event-emitter@npm:^0.2.4": - version: 0.2.8 - resolution: "strict-event-emitter@npm:0.2.8" - dependencies: - events: ^3.3.0 - checksum: 6ac06fe72a6ee6ae64d20f1dd42838ea67342f1b5f32b03b3050d73ee6ecee44b4d5c4ed2965a7154b47991e215f373d4e789e2b2be2769cd80e356126c2ca53 - languageName: node - linkType: hard - -"strict-event-emitter@npm:^0.4.3": - version: 0.4.6 - resolution: "strict-event-emitter@npm:0.4.6" - checksum: 4f4f2909613e7811de789991c06bfb770d6d6987e2ec5c66fa7485d0f07cc4e7e32eba0dcf26cee6d86af6c92946d7f4acdfaff57d0c4114df2cfa1bf0e3c091 +"strict-event-emitter@npm:^0.5.0, strict-event-emitter@npm:^0.5.1": + version: 0.5.1 + resolution: "strict-event-emitter@npm:0.5.1" + checksum: 350480431bc1c28fdb601ef4976c2f8155fc364b4740f9692dd03e5bdd48aafc99a5e021fe655fbd986d0b803e9f3fc5c4b018b35cb838c4690d60f2a26f1cf3 languageName: node linkType: hard @@ -20195,19 +20189,6 @@ __metadata: languageName: node linkType: hard -"util@npm:^0.12.3": - version: 0.12.5 - resolution: "util@npm:0.12.5" - dependencies: - inherits: ^2.0.3 - is-arguments: ^1.0.4 - is-generator-function: ^1.0.7 - is-typed-array: ^1.1.3 - which-typed-array: ^1.1.2 - checksum: 705e51f0de5b446f4edec10739752ac25856541e0254ea1e7e45e5b9f9b0cb105bc4bd415736a6210edc68245a7f903bf085ffb08dd7deb8a0e847f60538a38a - languageName: node - linkType: hard - "utils-merge@npm:1.0.1": version: 1.0.1 resolution: "utils-merge@npm:1.0.1" @@ -20405,19 +20386,6 @@ __metadata: languageName: node linkType: hard -"web-encoding@npm:^1.1.5": - version: 1.1.5 - resolution: "web-encoding@npm:1.1.5" - dependencies: - "@zxing/text-encoding": 0.9.0 - util: ^0.12.3 - dependenciesMeta: - "@zxing/text-encoding": - optional: true - checksum: 2234a2b122f41006ce07859b3c0bf2e18f46144fda2907d5db0b571b76aa5c26977c646100ad9c00d2f8a4f6f2b848bc02147845d8c447ab365ec4eff376338d - languageName: node - linkType: hard - "web-streams-polyfill@npm:4.0.0-beta.3": version: 4.0.0-beta.3 resolution: "web-streams-polyfill@npm:4.0.0-beta.3" @@ -20555,7 +20523,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.11, which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.2, which-typed-array@npm:^1.1.9": +"which-typed-array@npm:^1.1.11, which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.9": version: 1.1.13 resolution: "which-typed-array@npm:1.1.13" dependencies: