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  });