github.com/niko0xdev/gqlgen@v0.17.55-0.20240120102243-2ecff98c3e37/integration/src/__test__/integration.spec.ts (about) 1 import { afterAll, describe, expect, it } from "vitest"; 2 import { 3 ApolloClient, 4 ApolloLink, 5 FetchResult, 6 HttpLink, 7 InMemoryCache, 8 NormalizedCacheObject, 9 Observable, 10 Operation, 11 } from "@apollo/client/core"; 12 import { print } from "graphql"; 13 import { GraphQLWsLink } from "@apollo/client/link/subscriptions"; 14 import { WebSocket } from "ws"; 15 import { createClient as createClientWS } from "graphql-ws"; 16 import { 17 Client as ClientSSE, 18 ClientOptions as ClientOptionsSSE, 19 createClient as createClientSSE, 20 } from "graphql-sse"; 21 import { 22 CoercionDocument, 23 ComplexityDocument, 24 DateDocument, 25 ErrorDocument, 26 ErrorType, 27 JsonEncodingDocument, 28 PathDocument, 29 UserFragmentFragmentDoc, 30 ViewerDocument, 31 } from "../generated/graphql.ts"; 32 import { 33 cacheExchange, 34 Client, 35 dedupExchange, 36 subscriptionExchange, 37 } from "urql"; 38 import { isFragmentReady, useFragment } from "../generated"; 39 import { readFileSync } from "fs"; 40 import { join } from "path"; 41 42 const uri = process.env.VITE_SERVER_URL || "http://localhost:8080/query"; 43 44 function test(client: ApolloClient<NormalizedCacheObject>) { 45 describe("Json", () => { 46 it("should follow json escaping rules", async () => { 47 const res = await client.query({ 48 query: JsonEncodingDocument, 49 }); 50 51 expect(res.data.jsonEncoding).toBe(""); 52 expect(res.errors).toBe(undefined); 53 54 return null; 55 }); 56 }); 57 58 describe("Input defaults", () => { 59 it("should pass default values to resolver", async () => { 60 const res = await client.query({ 61 query: DateDocument, 62 variables: { 63 filter: { 64 value: "asdf", 65 }, 66 }, 67 }); 68 69 expect(res.data.date).toBe(true); 70 expect(res.errors).toBe(undefined); 71 return null; 72 }); 73 }); 74 75 describe("Complexity", () => { 76 it("should fail when complexity is too high", async () => { 77 const res = await client.query({ 78 query: ComplexityDocument, 79 variables: { 80 value: 2000, 81 }, 82 }); 83 84 expect(res.errors).toBeDefined(); 85 if (res.errors) { 86 expect(res.errors[0].message).toBe( 87 "operation has complexity 2000, which exceeds the limit of 1000" 88 ); 89 } 90 return null; 91 }); 92 93 it("should succeed when complexity is not too high", async () => { 94 const res = await client.query({ 95 query: ComplexityDocument, 96 variables: { 97 value: 1000, 98 }, 99 }); 100 101 expect(res.data.complexity).toBe(true); 102 expect(res.errors).toBe(undefined); 103 return null; 104 }); 105 }); 106 107 describe("List Coercion", () => { 108 it("should succeed when nested single values are passed", async () => { 109 const res = await client.query({ 110 query: CoercionDocument, 111 variables: { 112 value: { 113 enumVal: ErrorType.Custom, 114 strVal: "test", 115 intVal: 1, 116 }, 117 }, 118 }); 119 120 expect(res.data.coercion).toBe(true); 121 return null; 122 }); 123 124 it("should succeed when nested array of values are passed", async () => { 125 const res = await client.query({ 126 query: CoercionDocument, 127 variables: { 128 value: { 129 enumVal: [ErrorType.Custom], 130 strVal: ["test"], 131 intVal: [1], 132 }, 133 }, 134 }); 135 136 expect(res.data.coercion).toBe(true); 137 return null; 138 }); 139 140 it("should succeed when single value is passed", async () => { 141 const res = await client.query({ 142 query: CoercionDocument, 143 variables: { 144 value: { 145 enumVal: ErrorType.Custom, 146 }, 147 }, 148 }); 149 150 expect(res.data.coercion).toBe(true); 151 return null; 152 }); 153 154 it("should succeed when single scalar value is passed", async () => { 155 const res = await client.query({ 156 query: CoercionDocument, 157 variables: { 158 value: [ 159 { 160 scalarVal: { 161 key: "someValue", 162 }, 163 }, 164 ], 165 }, 166 }); 167 168 expect(res.data.coercion).toBe(true); 169 return null; 170 }); 171 172 it("should succeed when multiple values are passed", async () => { 173 const res = await client.query({ 174 query: CoercionDocument, 175 variables: { 176 value: [ 177 { 178 enumVal: [ErrorType.Custom, ErrorType.Normal], 179 }, 180 ], 181 }, 182 }); 183 184 expect(res.data.coercion).toBe(true); 185 return null; 186 }); 187 }); 188 189 describe("Errors", () => { 190 it("should respond with correct paths", async () => { 191 const res = await client.query({ 192 query: PathDocument, 193 }); 194 195 expect(res.errors).toBeDefined(); 196 if (res.errors) { 197 expect(res.errors[0].path).toEqual(["path", 0, "cc", "error"]); 198 expect(res.errors[1].path).toEqual(["path", 1, "cc", "error"]); 199 expect(res.errors[2].path).toEqual(["path", 2, "cc", "error"]); 200 expect(res.errors[3].path).toEqual(["path", 3, "cc", "error"]); 201 } 202 return null; 203 }); 204 205 it("should use the error presenter for custom errors", async () => { 206 let res = await client.query({ 207 query: ErrorDocument, 208 variables: { 209 type: ErrorType.Custom, 210 }, 211 }); 212 213 expect(res.errors).toBeDefined(); 214 if (res.errors) { 215 expect(res.errors[0].message).toEqual("User message"); 216 } 217 return null; 218 }); 219 220 it("should pass through for other errors", async () => { 221 const res = await client.query({ 222 query: ErrorDocument, 223 variables: { 224 type: ErrorType.Normal, 225 }, 226 }); 227 228 expect(res.errors).toBeDefined(); 229 if (res.errors) { 230 expect(res.errors[0]?.message).toEqual("normal error"); 231 } 232 return null; 233 }); 234 }); 235 } 236 237 describe("HTTP client", () => { 238 const client = new ApolloClient({ 239 link: new HttpLink({ 240 uri, 241 fetch, 242 }), 243 cache: new InMemoryCache(), 244 defaultOptions: { 245 watchQuery: { 246 fetchPolicy: "network-only", 247 errorPolicy: "ignore", 248 }, 249 query: { 250 fetchPolicy: "network-only", 251 errorPolicy: "all", 252 }, 253 }, 254 }); 255 256 test(client); 257 258 afterAll(() => { 259 client.stop(); 260 }); 261 }); 262 263 describe("Schema Introspection", () => { 264 const schemaJson = readFileSync( 265 join(__dirname, "../generated/schema-introspection.json"), 266 "utf-8" 267 ); 268 const schema = JSON.parse(schemaJson); 269 270 it("User.phoneNumber is deprecated and deprecationReason has the default value: `No longer supported`", async () => { 271 const userType = schema.__schema.types.find( 272 (type: any) => type.name === "User" 273 ); 274 275 expect(userType).toBeDefined(); 276 277 const phoneNumberField = userType.fields.find( 278 (field: any) => field.name === "phoneNumber" 279 ); 280 expect(phoneNumberField).toBeDefined(); 281 282 expect(phoneNumberField.isDeprecated).toBe(true); 283 expect(phoneNumberField.deprecationReason).toBe("No longer supported"); 284 }); 285 }); 286 287 describe("Websocket client", () => { 288 const client = new ApolloClient({ 289 link: new GraphQLWsLink( 290 createClientWS({ 291 url: uri 292 .replace("http://", "ws://") 293 .replace("https://", "wss://"), 294 webSocketImpl: WebSocket, 295 }) 296 ), 297 cache: new InMemoryCache(), 298 defaultOptions: { 299 watchQuery: { 300 fetchPolicy: "network-only", 301 errorPolicy: "ignore", 302 }, 303 query: { 304 fetchPolicy: "network-only", 305 errorPolicy: "all", 306 }, 307 }, 308 }); 309 310 test(client); 311 312 afterAll(() => { 313 client.stop(); 314 }); 315 }); 316 317 describe("SSE client", () => { 318 class SSELink extends ApolloLink { 319 private client: ClientSSE; 320 321 constructor(options: ClientOptionsSSE) { 322 super(); 323 this.client = createClientSSE(options); 324 } 325 326 public request(operation: Operation): Observable<FetchResult> { 327 return new Observable((sink) => { 328 return this.client.subscribe<FetchResult>( 329 { ...operation, query: print(operation.query) }, 330 { 331 next: sink.next.bind(sink), 332 complete: sink.complete.bind(sink), 333 error: sink.error.bind(sink), 334 } 335 ); 336 }); 337 } 338 } 339 340 const client = new ApolloClient({ 341 link: new SSELink({ 342 url: uri, 343 }), 344 cache: new InMemoryCache(), 345 defaultOptions: { 346 watchQuery: { 347 fetchPolicy: "network-only", 348 errorPolicy: "ignore", 349 }, 350 query: { 351 fetchPolicy: "network-only", 352 errorPolicy: "all", 353 }, 354 }, 355 }); 356 357 test(client); 358 359 afterAll(() => { 360 client.stop(); 361 }); 362 }); 363 364 describe("URQL SSE client", () => { 365 const wsClient = createClientWS({ 366 url: uri.replace("http://", "ws://").replace("https://", "wss://"), 367 webSocketImpl: WebSocket, 368 }); 369 370 const client = new Client({ 371 url: uri, 372 exchanges: [ 373 dedupExchange, 374 cacheExchange, 375 subscriptionExchange({ 376 enableAllOperations: true, 377 forwardSubscription(request) { 378 const input = { ...request, query: request.query || "" }; 379 return { 380 subscribe(sink) { 381 const unsubscribe = wsClient.subscribe(input, sink); 382 return { unsubscribe }; 383 }, 384 }; 385 }, 386 }), 387 ], 388 }); 389 390 describe("Defer", () => { 391 it("test using defer", async () => { 392 const res = await client.query(ViewerDocument, {}); 393 394 expect(res.error).toBe(undefined); 395 expect(res.data).toBeDefined(); 396 expect(res.data?.viewer).toBeDefined(); 397 expect(res.data?.viewer?.user).toBeDefined(); 398 expect(res.data?.viewer?.user?.name).toBe("Bob"); 399 expect(res.data?.viewer?.user?.query?.jsonEncoding).toBe(""); 400 let ready: boolean; 401 if ( 402 (ready = isFragmentReady( 403 ViewerDocument, 404 UserFragmentFragmentDoc, 405 res.data?.viewer?.user 406 )) 407 ) { 408 const userFragment = useFragment( 409 UserFragmentFragmentDoc, 410 res.data?.viewer?.user 411 ); 412 expect(userFragment).toBeDefined(); 413 expect(userFragment?.likes).toStrictEqual(["Alice"]); 414 } 415 expect(ready).toBeTruthy(); 416 return null; 417 }); 418 }); 419 });