github.com/99designs/gqlgen@v0.17.45/integration/src/__test__/integration.spec.ts (about) 1 import { afterAll, describe, expect, it } from "vitest"; 2 3 import fetch from 'cross-fetch'; 4 import { 5 ApolloClient, 6 ApolloLink, 7 FetchResult, 8 InMemoryCache, 9 NormalizedCacheObject, 10 Observable, 11 Operation, 12 } from '@apollo/client/core'; 13 import {HttpLink} from '@apollo/client/link/http'; 14 15 import { print } from "graphql"; 16 import { GraphQLWsLink } from "@apollo/client/link/subscriptions"; 17 import { WebSocket } from "ws"; 18 import { createClient as createClientWS } from "graphql-ws"; 19 import { 20 Client as ClientSSE, 21 ClientOptions as ClientOptionsSSE, 22 createClient as createClientSSE, 23 } from "graphql-sse"; 24 import { 25 CoercionDocument, 26 ComplexityDocument, 27 DateDocument, 28 ErrorDocument, 29 ErrorType, 30 JsonEncodingDocument, 31 PathDocument, 32 UserFragmentFragmentDoc, 33 ViewerDocument, 34 } from "../generated/graphql.ts"; 35 import { 36 cacheExchange, 37 Client, 38 dedupExchange, 39 subscriptionExchange, 40 } from "urql"; 41 import { isFragmentReady, useFragment } from "../generated"; 42 import { readFileSync } from "fs"; 43 import { join } from "path"; 44 45 const uri = process.env.VITE_SERVER_URL || "http://localhost:8080/query"; 46 47 function test(client: ApolloClient<NormalizedCacheObject>) { 48 describe("Json", () => { 49 it("should follow json escaping rules", async () => { 50 const res = await client.query({ 51 query: JsonEncodingDocument, 52 }); 53 54 expect(res.data.jsonEncoding).toBe(""); 55 expect(res.errors).toBe(undefined); 56 57 return null; 58 }); 59 }); 60 61 describe("Input defaults", () => { 62 it("should pass default values to resolver", async () => { 63 const res = await client.query({ 64 query: DateDocument, 65 variables: { 66 filter: { 67 value: "asdf", 68 }, 69 }, 70 }); 71 72 expect(res.data.date).toBe(true); 73 expect(res.errors).toBe(undefined); 74 return null; 75 }); 76 }); 77 78 describe("Complexity", () => { 79 it("should fail when complexity is too high", async () => { 80 const res = await client.query({ 81 query: ComplexityDocument, 82 variables: { 83 value: 2000, 84 }, 85 }); 86 87 expect(res.errors).toBeDefined(); 88 if (res.errors) { 89 expect(res.errors[0].message).toBe( 90 "operation has complexity 2000, which exceeds the limit of 1000" 91 ); 92 } 93 return null; 94 }); 95 96 it("should succeed when complexity is not too high", async () => { 97 const res = await client.query({ 98 query: ComplexityDocument, 99 variables: { 100 value: 1000, 101 }, 102 }); 103 104 expect(res.data.complexity).toBe(true); 105 expect(res.errors).toBe(undefined); 106 return null; 107 }); 108 }); 109 110 describe("List Coercion", () => { 111 it("should succeed when nested single values are passed", async () => { 112 const res = await client.query({ 113 query: CoercionDocument, 114 variables: { 115 value: { 116 enumVal: ErrorType.Custom, 117 strVal: "test", 118 intVal: 1, 119 }, 120 }, 121 }); 122 123 expect(res.data.coercion).toBe(true); 124 return null; 125 }); 126 127 it("should succeed when nested array of values are passed", async () => { 128 const res = await client.query({ 129 query: CoercionDocument, 130 variables: { 131 value: { 132 enumVal: [ErrorType.Custom], 133 strVal: ["test"], 134 intVal: [1], 135 }, 136 }, 137 }); 138 139 expect(res.data.coercion).toBe(true); 140 return null; 141 }); 142 143 it("should succeed when single value is passed", async () => { 144 const res = await client.query({ 145 query: CoercionDocument, 146 variables: { 147 value: { 148 enumVal: ErrorType.Custom, 149 }, 150 }, 151 }); 152 153 expect(res.data.coercion).toBe(true); 154 return null; 155 }); 156 157 it("should succeed when single scalar value is passed", async () => { 158 const res = await client.query({ 159 query: CoercionDocument, 160 variables: { 161 value: [ 162 { 163 scalarVal: { 164 key: "someValue", 165 }, 166 }, 167 ], 168 }, 169 }); 170 171 expect(res.data.coercion).toBe(true); 172 return null; 173 }); 174 175 it("should succeed when multiple values are passed", async () => { 176 const res = await client.query({ 177 query: CoercionDocument, 178 variables: { 179 value: [ 180 { 181 enumVal: [ErrorType.Custom, ErrorType.Normal], 182 }, 183 ], 184 }, 185 }); 186 187 expect(res.data.coercion).toBe(true); 188 return null; 189 }); 190 }); 191 192 describe("Errors", () => { 193 it("should respond with correct paths", async () => { 194 const res = await client.query({ 195 query: PathDocument, 196 }); 197 198 expect(res.errors).toBeDefined(); 199 if (res.errors) { 200 expect(res.errors[0].path).toEqual(["path", 0, "cc", "error"]); 201 expect(res.errors[1].path).toEqual(["path", 1, "cc", "error"]); 202 expect(res.errors[2].path).toEqual(["path", 2, "cc", "error"]); 203 expect(res.errors[3].path).toEqual(["path", 3, "cc", "error"]); 204 } 205 return null; 206 }); 207 208 it("should use the error presenter for custom errors", async () => { 209 let res = await client.query({ 210 query: ErrorDocument, 211 variables: { 212 type: ErrorType.Custom, 213 }, 214 }); 215 216 expect(res.errors).toBeDefined(); 217 if (res.errors) { 218 expect(res.errors[0].message).toEqual("User message"); 219 } 220 return null; 221 }); 222 223 it("should pass through for other errors", async () => { 224 const res = await client.query({ 225 query: ErrorDocument, 226 variables: { 227 type: ErrorType.Normal, 228 }, 229 }); 230 231 expect(res.errors).toBeDefined(); 232 if (res.errors) { 233 expect(res.errors[0]?.message).toEqual("normal error"); 234 } 235 return null; 236 }); 237 }); 238 } 239 240 describe("HTTP client", () => { 241 const client = new ApolloClient({ 242 uri: uri, 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 });