mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Right now our tests for API mocking is using vi.mock and mocking that query or fetch handler. This is not the right approach IMO, 2 years ago @jordienr added MSW with some very powerful helpers. The idea is to move component test that rely on API using MSW within ViteTest. Principles are simple: - Mock API responses - Mount your component that uses API responses - Tests and assert on UI - Added Skill for Clanker This pattern is 100 times better than what we have <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Expanded and strengthened test suites for secrets, org lookup, support flows, OAuth auth, and onboarding; mocks now use contract-backed responses for more realistic coverage. * **Documentation** * Added a comprehensive guide describing a standardized pattern for component tests that mock network requests. * **Chores** * Improved test helpers, typing for API mocks, and test runner configuration for more reliable and maintainable tests. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46439?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <alaister@users.noreply.github.com> Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
86 lines
3.2 KiB
TypeScript
86 lines
3.2 KiB
TypeScript
import { DefaultBodyType, http, HttpResponse, HttpResponseResolver, PathParams } from 'msw'
|
|
import { setupServer } from 'msw/node'
|
|
|
|
import type { paths } from '../../data/api'
|
|
import { GlobalAPIMocks } from './msw-global-api-mocks'
|
|
import { API_URL } from '@/lib/constants'
|
|
|
|
export const mswServer = setupServer(...GlobalAPIMocks)
|
|
|
|
mswServer.events.on('request:start', ({ request }) => {
|
|
console.log('[MSW] Outgoing:', request.method, request.url)
|
|
})
|
|
|
|
// Recursively changes params in an endpoint path segment from {param}
|
|
// format to :param format which is expected by Mock Service Worker
|
|
type RemapParams<T extends string> = T extends `${infer Before}{${infer Param}}${infer After}`
|
|
? `${Before}:${Param}${RemapParams<After>}`
|
|
: T
|
|
|
|
type TrimQueryParams<T extends string> = T extends `${infer Endpoint}?${string}` ? Endpoint : T
|
|
|
|
// We need to remap all keys from our openapi-typescript generated types
|
|
// so that they follow MSW's parameter formatting
|
|
type RemapPaths = {
|
|
[K in keyof paths as RemapParams<K>]: paths[K]
|
|
}
|
|
|
|
type Endpoints = keyof RemapPaths
|
|
type Methods = Exclude<keyof typeof http, 'all'>
|
|
|
|
// Extract either the 200 or 201 response object
|
|
type SuccessResponse<P extends Endpoints, M extends Methods> = RemapPaths[P][M] extends {
|
|
responses: { 200: { content: { 'application/json': infer R200 } } }
|
|
}
|
|
? R200
|
|
: RemapPaths[P][M] extends {
|
|
responses: { 201: { content: { 'application/json': infer R201 } } }
|
|
}
|
|
? R201
|
|
: never
|
|
|
|
// Studio's standard error envelope — what `handleError` and `ResponseError` consume.
|
|
// OpenAPI doesn't document error bodies (4xx/5xx are `content?: never`), so we
|
|
// hardcode this convention to keep error-state tests strongly typed.
|
|
export type APIErrorBody = { message: string }
|
|
|
|
// Resolver constrained to the OpenAPI success body (or the error envelope above).
|
|
// Catches drift between mocks and the API contract. `Extract<..., DefaultBodyType>`
|
|
// filters to the JSON-body subset to satisfy `HttpResponseResolver`'s body constraint.
|
|
type TypedResolver<P extends Endpoints, M extends Methods> = HttpResponseResolver<
|
|
PathParams,
|
|
DefaultBodyType,
|
|
Extract<SuccessResponse<P, M>, DefaultBodyType> | APIErrorBody
|
|
>
|
|
|
|
const isResponseResolver = (val: unknown): val is HttpResponseResolver => typeof val === `function`
|
|
|
|
export const addAPIMock = <P extends Endpoints | `${Endpoints}?${string}`, M extends Methods>({
|
|
method,
|
|
path,
|
|
response,
|
|
}: SuccessResponse<TrimQueryParams<P>, M> extends never
|
|
? // Endpoints with no documented JSON response body — resolver is optional, used
|
|
// when the test needs to assert on the request or override the status.
|
|
{ method: M; path: P; response?: HttpResponseResolver }
|
|
: {
|
|
method: M
|
|
path: P
|
|
response: SuccessResponse<TrimQueryParams<P>, M> | TypedResolver<TrimQueryParams<P>, M>
|
|
}) => {
|
|
const fullPath = `${API_URL}${path}`
|
|
console.log('[MSW] Adding mock:', method.toUpperCase(), fullPath)
|
|
|
|
mswServer.use(
|
|
http[method](
|
|
fullPath,
|
|
isResponseResolver(response)
|
|
? response
|
|
: ({ request }) => {
|
|
console.log('[MSW] Handling request:', request.method, request.url, response)
|
|
return HttpResponse.json(response ?? null)
|
|
}
|
|
)
|
|
)
|
|
}
|