1
0
Эх сурвалжийг харах

refactor(core): Replace graphql-request lib with own implementation

It does not offer the ability to set query parameters on the url, which will be needed to properly test the language handling.
Michael Bromley 6 жил өмнө
parent
commit
ac6e14ec83

+ 96 - 86
packages/core/mock-data/simple-graphql-client.ts

@@ -1,9 +1,9 @@
 /// <reference types="../typings" />
 import { SUPER_ADMIN_USER_IDENTIFIER, SUPER_ADMIN_USER_PASSWORD } from '@vendure/common/lib/shared-constants';
 import { DocumentNode } from 'graphql';
-import { GraphQLClient } from 'graphql-request';
 import gql from 'graphql-tag';
 import { print } from 'graphql/language/printer';
+import fetch, { Response } from 'node-fetch';
 import { Curl } from 'node-libcurl';
 
 import { ImportInfo } from '../e2e/graphql/generated-e2e-admin-types';
@@ -11,22 +11,32 @@ import { getConfig } from '../src/config/config-helpers';
 
 import { createUploadPostData } from './create-upload-post-data';
 
+const LOGIN = gql`
+    mutation($username: String!, $password: String!) {
+        login(username: $username, password: $password) {
+            user {
+                id
+                identifier
+                channelTokens
+            }
+        }
+    }
+`;
+
 // tslint:disable:no-console
 /**
  * A minimalistic GraphQL client for populating and querying test data.
  */
 export class SimpleGraphQLClient {
-    private client: GraphQLClient;
     private authToken: string;
     private channelToken: string;
+    private headers: { [key: string]: any } = {};
 
-    constructor(private apiUrl: string = '') {
-        this.client = new GraphQLClient(apiUrl);
-    }
+    constructor(private apiUrl: string = '') {}
 
     setAuthToken(token: string) {
         this.authToken = token;
-        this.setHeaders();
+        this.headers.Authorization = `Bearer ${this.authToken}`;
     }
 
     getAuthToken(): string {
@@ -34,45 +44,30 @@ export class SimpleGraphQLClient {
     }
 
     setChannelToken(token: string) {
-        this.channelToken = token;
-        this.setHeaders();
+        this.headers[getConfig().channelTokenKey] = token;
     }
 
     /**
      * Performs both query and mutation operations.
      */
     async query<T = any, V = Record<string, any>>(query: DocumentNode, variables?: V): Promise<T> {
-        const queryString = print(query);
-        const result = await this.client.rawRequest<T>(queryString, variables);
-
-        const authToken = result.headers.get(getConfig().authOptions.authTokenHeaderKey);
-        if (authToken != null) {
-            this.setAuthToken(authToken);
+        const response = await this.request(query, variables);
+        const result = await this.getResult(response);
+
+        if (response.ok && !result.errors && result.data) {
+            return result.data;
+        } else {
+            const errorResult = typeof result === 'string' ? { error: result } : result;
+            throw new ClientError(
+                { ...errorResult, status: response.status },
+                { query: print(query), variables },
+            );
         }
-        return result.data as T;
     }
 
     async queryStatus<T = any, V = Record<string, any>>(query: DocumentNode, variables?: V): Promise<number> {
-        const queryString = print(query);
-        const result = await this.client.rawRequest<T>(queryString, variables);
-        return result.status;
-    }
-
-    uploadAssets(filePaths: string[]): Promise<any> {
-        return this.fileUploadMutation({
-            mutation: gql`
-                mutation CreateAssets($input: [CreateAssetInput!]!) {
-                    createAssets(input: $input) {
-                        id
-                        name
-                    }
-                }
-            `,
-            filePaths,
-            mapVariables: fp => ({
-                input: fp.map(() => ({ file: null })),
-            }),
-        });
+        const response = await this.request(query, variables);
+        return response.status;
     }
 
     importProducts(csvFilePath: string): Promise<{ importProducts: ImportInfo }> {
@@ -91,6 +86,63 @@ export class SimpleGraphQLClient {
         });
     }
 
+    async asUserWithCredentials(username: string, password: string) {
+        // first log out as the current user
+        if (this.authToken) {
+            await this.query(
+                gql`
+                    mutation {
+                        logout
+                    }
+                `,
+            );
+        }
+        const result = await this.query(LOGIN, { username, password });
+        return result.login;
+    }
+
+    async asSuperAdmin() {
+        await this.asUserWithCredentials(SUPER_ADMIN_USER_IDENTIFIER, SUPER_ADMIN_USER_PASSWORD);
+    }
+
+    async asAnonymousUser() {
+        await this.query(
+            gql`
+                mutation {
+                    logout
+                }
+            `,
+        );
+    }
+
+    private async request(query: DocumentNode, variables?: { [key: string]: any }): Promise<Response> {
+        const queryString = print(query);
+        const body = JSON.stringify({
+            query: queryString,
+            variables: variables ? variables : undefined,
+        });
+
+        const response = await fetch(this.apiUrl, {
+            method: 'POST',
+            headers: { 'Content-Type': 'application/json', ...this.headers },
+            body,
+        });
+        const authToken = response.headers.get(getConfig().authOptions.authTokenHeaderKey || '');
+        if (authToken != null) {
+            this.setAuthToken(authToken);
+        }
+        return response;
+    }
+
+    private async getResult(response: Response): Promise<any> {
+        const contentType = response.headers.get('Content-Type');
+        if (contentType && contentType.startsWith('application/json')) {
+            return response.json();
+        } else {
+            return response.text();
+        }
+    }
+
     /**
      * Uses curl to post a multipart/form-data request to the server. Due to differences between the Node and browser
      * environments, we cannot just use an existing library like apollo-upload-client.
@@ -151,59 +203,17 @@ export class SimpleGraphQLClient {
             });
         });
     }
+}
 
-    async asUserWithCredentials(username: string, password: string) {
-        // first log out as the current user
-        if (this.authToken) {
-            await this.query(
-                gql`
-                    mutation {
-                        logout
-                    }
-                `,
-            );
-        }
-        const result = await this.query(
-            gql`
-                mutation($username: String!, $password: String!) {
-                    login(username: $username, password: $password) {
-                        user {
-                            id
-                            identifier
-                            channelTokens
-                        }
-                    }
-                }
-            `,
-            {
-                username,
-                password,
-            },
-        );
-        return result.login;
-    }
-
-    async asSuperAdmin() {
-        await this.asUserWithCredentials(SUPER_ADMIN_USER_IDENTIFIER, SUPER_ADMIN_USER_PASSWORD);
-    }
-
-    async asAnonymousUser() {
-        await this.query(
-            gql`
-                mutation {
-                    logout
-                }
-            `,
-        );
+export class ClientError extends Error {
+    constructor(public response: any, public request: any) {
+        super(ClientError.extractMessage(response));
     }
-
-    private setHeaders() {
-        const headers: any = {
-            [getConfig().channelTokenKey]: this.channelToken,
-        };
-        if (this.authToken) {
-            headers.Authorization = `Bearer ${this.authToken}`;
+    private static extractMessage(response: any): string {
+        if (response.errors) {
+            return response.errors[0].message;
+        } else {
+            return `GraphQL Error (Code: ${response.status})`;
         }
-        this.client.setHeaders(headers);
     }
 }

+ 2 - 1
packages/core/package.json

@@ -83,12 +83,13 @@
     "@types/ms": "^0.7.30",
     "@types/nanoid": "^1.2.0",
     "@types/node": "^10.12.18",
+    "@types/node-fetch": "^2.3.7",
     "@types/progress": "^2.0.3",
     "@types/prompts": "^1.2.0",
     "faker": "^4.1.0",
-    "graphql-request": "^1.8.2",
     "gulp": "^4.0.0",
     "mysql": "^2.16.0",
+    "node-fetch": "^2.6.0",
     "node-libcurl": "^2.0.0",
     "pg": "^7.8.0",
     "rimraf": "^2.6.3",

+ 9 - 2
yarn.lock

@@ -1826,6 +1826,13 @@
   dependencies:
     "@types/node" "*"
 
+"@types/node-fetch@^2.3.7":
+  version "2.3.7"
+  resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.3.7.tgz#b7212e895100f8642dbdab698472bab5f3c1d2f1"
+  integrity sha512-+bKtuxhj/TYSSP1r4CZhfmyA0vm/aDRQNo7vbAgf6/cZajn0SAniGGST07yvI4Q+q169WTa2/x9gEHfJrkcALw==
+  dependencies:
+    "@types/node" "*"
+
 "@types/node@*":
   version "12.0.5"
   resolved "https://registry.npmjs.org/@types/node/-/node-12.0.5.tgz#ac14404c33d1a789973c45379a67f7f7e58a01b9"
@@ -5386,7 +5393,7 @@ graphql-query-complexity@^0.2.3:
   dependencies:
     lodash.get "^4.4.2"
 
-graphql-request@^1.5.0, graphql-request@^1.8.2:
+graphql-request@^1.5.0:
   version "1.8.2"
   resolved "https://registry.npmjs.org/graphql-request/-/graphql-request-1.8.2.tgz#398d10ae15c585676741bde3fc01d5ca948f8fbe"
   integrity sha512-dDX2M+VMsxXFCmUX0Vo0TopIZIX4ggzOtiCsThgtrKR4niiaagsGTDIHj3fsOMFETpa064vzovI+4YV4QnMbcg==
@@ -8411,7 +8418,7 @@ node-fetch@2.1.2:
   resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
   integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=
 
-node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.3.0:
+node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0:
   version "2.6.0"
   resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
   integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==