|
| 1 | +/** |
| 2 | + * Core tests |
| 3 | + * General functionality that is not specific to any HTTP method |
| 4 | + * (so don’t add tests for request body, form data, etc.) |
| 5 | + */ |
| 6 | +import { http, HttpResponse } from "msw"; |
| 7 | +import { setupServer, type SetupServerApi } from "msw/node"; |
| 8 | +import createClient from "../../src/index.js"; |
| 9 | +import type { components, paths } from "./schema.js"; |
| 10 | + |
| 11 | +type Resource = components["schemas"]["Resource"]; |
| 12 | +type Error = components["schemas"]["Error"]; |
| 13 | + |
| 14 | +const resource: Resource = { |
| 15 | + id: 123, |
| 16 | + name: "Resource", |
| 17 | + created_at: "2024-06-01T12:00:00Z", |
| 18 | + updated_at: "2024-06-01T12:00:00Z", |
| 19 | +}; |
| 20 | + |
| 21 | +const baseUrl = "https://openapi-ts.dev/core-test"; |
| 22 | + |
| 23 | +// generic msw setup |
| 24 | +let server: SetupServerApi; |
| 25 | + |
| 26 | +beforeAll(() => { |
| 27 | + server = setupServer( |
| 28 | + http.get(`${baseUrl}/error-404`, () => |
| 29 | + HttpResponse.json({ code: 404, message: "Internal server error" }, { status: 404 }), |
| 30 | + ), |
| 31 | + http.get(`${baseUrl}/error-500`, () => |
| 32 | + HttpResponse.json({ code: 500, message: "Internal server error" }, { status: 500 }), |
| 33 | + ), |
| 34 | + // default |
| 35 | + http.get(`${baseUrl}/*`, () => HttpResponse.json([resource, resource, resource])), |
| 36 | + ); |
| 37 | + server.listen(); |
| 38 | +}); |
| 39 | + |
| 40 | +afterAll(() => { |
| 41 | + server.close(); |
| 42 | +}); |
| 43 | + |
| 44 | +describe("request", () => { |
| 45 | + describe("headers", () => { |
| 46 | + test("default headers are preserved", async () => { |
| 47 | + const baseUrl = "https://openapi-ts.dev/core-test/header-test-default"; |
| 48 | + let headers = ""; |
| 49 | + server.use( |
| 50 | + http.get(`${baseUrl}/resources`, (req) => { |
| 51 | + headers = req.request.headers.toString(); |
| 52 | + return HttpResponse.json([resource, resource, resource]); |
| 53 | + }), |
| 54 | + ); |
| 55 | + const client = createClient<paths>({ baseUrl, headers: { foo: "bar" } }); |
| 56 | + await client.GET("/resources"); |
| 57 | + expect(headers).toEqual('{foo:"bar"}'); |
| 58 | + }); |
| 59 | + |
| 60 | + test("default headers can be overridden", async () => { |
| 61 | + const baseUrl = "https://openapi-ts.dev/core-test/header-test-override"; |
| 62 | + let headers = ""; |
| 63 | + server.use( |
| 64 | + http.get(`${baseUrl}/resources`, (req) => { |
| 65 | + headers = req.request.headers.toString(); |
| 66 | + return HttpResponse.json([resource, resource, resource]); |
| 67 | + }), |
| 68 | + ); |
| 69 | + const client = createClient<paths>({ baseUrl, headers: { foo: "bar" } }); |
| 70 | + await client.GET("/resources", { |
| 71 | + headers: { foo: "baz" }, |
| 72 | + }); |
| 73 | + expect(headers).toEqual({ foo: "baz" }); |
| 74 | + }); |
| 75 | + |
| 76 | + test('default headers removed by "null"', async () => { |
| 77 | + const baseUrl = "https://openapi-ts.dev/core-test/header-test-null"; |
| 78 | + let headers = ""; |
| 79 | + server.use( |
| 80 | + http.get(`${baseUrl}/resources`, (req) => { |
| 81 | + headers = req.request.headers.toString(); |
| 82 | + return HttpResponse.json([resource, resource, resource]); |
| 83 | + }), |
| 84 | + ); |
| 85 | + const client = createClient<paths>({ baseUrl, headers: { foo: "bar" } }); |
| 86 | + await client.GET("/resources", { headers: { foo: null } }); |
| 87 | + expect(headers).toEqual({ foo: "baz" }); |
| 88 | + }); |
| 89 | + |
| 90 | + test('default headers aren’t overridden by "undefined"', async () => { |
| 91 | + const baseUrl = "https://openapi-ts.dev/core-test/header-test-undefined"; |
| 92 | + let headers = ""; |
| 93 | + server.use( |
| 94 | + http.get(`${baseUrl}/resources`, (req) => { |
| 95 | + headers = req.request.headers.toString(); |
| 96 | + return HttpResponse.json([resource, resource, resource]); |
| 97 | + }), |
| 98 | + ); |
| 99 | + const client = createClient<paths>({ baseUrl, headers: { foo: "bar" } }); |
| 100 | + await client.GET("/resources", { headers: { foo: undefined } }); |
| 101 | + expect(headers).toEqual({ foo: "bar" }); |
| 102 | + }); |
| 103 | + |
| 104 | + test("arbitrary headers are allowed on any request", async () => { |
| 105 | + const baseUrl = "https://openapi-ts.dev/core-test/header-test-arbitrary"; |
| 106 | + let headers = ""; |
| 107 | + server.use( |
| 108 | + http.get(`${baseUrl}/resources`, (req) => { |
| 109 | + headers = req.request.headers.toString(); |
| 110 | + return HttpResponse.json([resource, resource, resource]); |
| 111 | + }), |
| 112 | + ); |
| 113 | + const client = createClient<paths>({ baseUrl }); |
| 114 | + client.GET("/resources", { |
| 115 | + headers: { |
| 116 | + foo: "bar", |
| 117 | + bar: 123, |
| 118 | + baz: true, |
| 119 | + }, |
| 120 | + }); |
| 121 | + expect(headers).toEqual({}); |
| 122 | + }); |
| 123 | + }); |
| 124 | +}); |
| 125 | + |
| 126 | +describe("response", () => { |
| 127 | + describe("data/error", () => { |
| 128 | + test("valid path", async () => { |
| 129 | + const client = createClient<paths>({ baseUrl }); |
| 130 | + |
| 131 | + const result = await client.GET("/resources"); |
| 132 | + |
| 133 | + // 1. assert data & error may be undefined initially |
| 134 | + assertType<Resource[] | undefined>(result.data); |
| 135 | + assertType<Error | undefined>(result.error); |
| 136 | + |
| 137 | + // 2. assert data is not undefined inside condition block |
| 138 | + if (result.data) { |
| 139 | + assertType<NonNullable<Resource[]>>(result.data); |
| 140 | + assertType<undefined>(result.error); |
| 141 | + } |
| 142 | + // 2b. inverse should work, too |
| 143 | + if (!result.error) { |
| 144 | + assertType<NonNullable<Resource[]>>(result.data); |
| 145 | + assertType<undefined>(result.error); |
| 146 | + } |
| 147 | + |
| 148 | + // 3. assert error is not undefined inside condition block |
| 149 | + if (result.error) { |
| 150 | + assertType<undefined>(result.data); |
| 151 | + assertType<NonNullable<Error>>(result.error); |
| 152 | + } |
| 153 | + // 3b. inverse should work, too |
| 154 | + if (!result.data) { |
| 155 | + assertType<undefined>(result.data); |
| 156 | + assertType<NonNullable<Error>>(result.error); |
| 157 | + } |
| 158 | + }); |
| 159 | + |
| 160 | + test("invalid path", async () => { |
| 161 | + const client = createClient<paths>({ baseUrl }); |
| 162 | + |
| 163 | + const result = await client.GET( |
| 164 | + // @ts-expect-error this should throw an error |
| 165 | + "/not-a-real-path", |
| 166 | + ); |
| 167 | + |
| 168 | + assertType<undefined>(result.data); |
| 169 | + // @ts-expect-error: FIXME when #1723 is resolved; this shouldn’t throw an error |
| 170 | + assertType<undefined>(result.error); |
| 171 | + }); |
| 172 | + |
| 173 | + describe("media union", () => { |
| 174 | + const client = createClient<paths>({ baseUrl }); |
| 175 | + |
| 176 | + // ⚠️ Warning: DO NOT iterate over type tests! Deduplicating runtime tests |
| 177 | + // is good. But these do not test runtime. |
| 178 | + test("application/json", async () => { |
| 179 | + const { data } = await client.GET("/media-json"); |
| 180 | + assertType<Resource[] | undefined>(data); |
| 181 | + }); |
| 182 | + |
| 183 | + test("application/vnd.api+json", async () => { |
| 184 | + const { data } = await client.GET("/media-vnd-json"); |
| 185 | + assertType<Resource[] | undefined>(data); |
| 186 | + }); |
| 187 | + |
| 188 | + test("text/html", async () => { |
| 189 | + const { data } = await client.GET("/media-text"); |
| 190 | + assertType<string | undefined>(data); |
| 191 | + }); |
| 192 | + |
| 193 | + test("multiple", async () => { |
| 194 | + const { data } = await client.GET("/media-multiple"); |
| 195 | + assertType<Resource[] | string | undefined>(data); |
| 196 | + }); |
| 197 | + |
| 198 | + test("invalid", async () => { |
| 199 | + const { data } = await client.GET( |
| 200 | + // @ts-expect-error not a real path |
| 201 | + "/invalid", |
| 202 | + ); |
| 203 | + assertType<unknown>(data); |
| 204 | + }); |
| 205 | + }); |
| 206 | + }); |
| 207 | + |
| 208 | + describe("response object", () => { |
| 209 | + test.each([200, 404, 500] as const)("%s", async (status) => { |
| 210 | + const client = createClient<paths>({ baseUrl }); |
| 211 | + const result = await client.GET(status === 200 ? "/resources" : `/error-${status}`); |
| 212 | + expect(result.response.status).toBe(status); |
| 213 | + }); |
| 214 | + }); |
| 215 | +}); |
0 commit comments