msw.ts 2.3 KB

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