msw.ts70 lines · main
| 1 | import { http, HttpResponse, HttpResponseResolver } from 'msw' |
| 2 | import { setupServer } from 'msw/node' |
| 3 | |
| 4 | import type { paths } from '../../data/api' |
| 5 | import { GlobalAPIMocks } from './msw-global-api-mocks' |
| 6 | import { API_URL } from '@/lib/constants' |
| 7 | |
| 8 | export const mswServer = setupServer(...GlobalAPIMocks) |
| 9 | |
| 10 | mswServer.events.on('request:start', ({ request }) => { |
| 11 | console.log('[MSW] Outgoing:', request.method, request.url) |
| 12 | }) |
| 13 | |
| 14 | // Recursively changes params in an endpoint path segment from {param} |
| 15 | // format to :param format which is expected by Mock Service Worker |
| 16 | type RemapParams<T extends string> = T extends `${infer Before}{${infer Param}}${infer After}` |
| 17 | ? `${Before}:${Param}${RemapParams<After>}` |
| 18 | : T |
| 19 | |
| 20 | type TrimQueryParams<T extends string> = T extends `${infer Endpoint}?${string}` ? Endpoint : T |
| 21 | |
| 22 | // We need to remap all keys from our openapi-typescript generated types |
| 23 | // so that they follow MSW's parameter formatting |
| 24 | type RemapPaths = { |
| 25 | [K in keyof paths as RemapParams<K>]: paths[K] |
| 26 | } |
| 27 | |
| 28 | type Endpoints = keyof RemapPaths |
| 29 | type Methods = Exclude<keyof typeof http, 'all'> |
| 30 | |
| 31 | // Extract either the 200 or 201 response object |
| 32 | type SuccessResponse<P extends Endpoints, M extends Methods> = RemapPaths[P][M] extends { |
| 33 | responses: { 200: { content: { 'application/json': infer R200 } } } |
| 34 | } |
| 35 | ? R200 |
| 36 | : RemapPaths[P][M] extends { |
| 37 | responses: { 201: { content: { 'application/json': infer R201 } } } |
| 38 | } |
| 39 | ? R201 |
| 40 | : never |
| 41 | |
| 42 | const isResponseResolver = (val: unknown): val is HttpResponseResolver => typeof val === `function` |
| 43 | |
| 44 | export const addAPIMock = <P extends Endpoints | `${Endpoints}?${string}`, M extends Methods>({ |
| 45 | method, |
| 46 | path, |
| 47 | response, |
| 48 | }: SuccessResponse<TrimQueryParams<P>, M> extends never |
| 49 | ? // Don't require a mocked response when the API doesn't return one |
| 50 | { method: M; path: P; response?: never } |
| 51 | : { |
| 52 | method: M |
| 53 | path: P |
| 54 | response: SuccessResponse<TrimQueryParams<P>, M> | HttpResponseResolver |
| 55 | }) => { |
| 56 | const fullPath = `${API_URL}${path}` |
| 57 | console.log('[MSW] Adding mock:', method.toUpperCase(), fullPath) |
| 58 | |
| 59 | mswServer.use( |
| 60 | http[method]( |
| 61 | fullPath, |
| 62 | isResponseResolver(response) |
| 63 | ? response |
| 64 | : ({ request }) => { |
| 65 | console.log('[MSW] Handling request:', request.method, request.url, response) |
| 66 | return HttpResponse.json(response ?? null) |
| 67 | } |
| 68 | ) |
| 69 | ) |
| 70 | } |