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