From a60c21484bc861c839cf7d1e27a4fbbbbb36eba9 Mon Sep 17 00:00:00 2001 From: Daniil Boiko <5n00p4eg@gmail.com> Date: Fri, 5 Apr 2024 12:04:08 +0300 Subject: [PATCH 1/2] Fix error handler in graphql client --- .changeset/pretty-geese-look.md | 7 +++++++ packages/shopify-api/lib/clients/admin/graphql/client.ts | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .changeset/pretty-geese-look.md diff --git a/.changeset/pretty-geese-look.md b/.changeset/pretty-geese-look.md new file mode 100644 index 000000000..ec155af9c --- /dev/null +++ b/.changeset/pretty-geese-look.md @@ -0,0 +1,7 @@ +--- +"@shopify/shopify-api": patch +"@shopify/admin-api-client": patch +"@shopify/graphql-client": patch +--- + +Fix type error in graphql error handler diff --git a/packages/shopify-api/lib/clients/admin/graphql/client.ts b/packages/shopify-api/lib/clients/admin/graphql/client.ts index 969a0263f..6b83a90aa 100644 --- a/packages/shopify-api/lib/clients/admin/graphql/client.ts +++ b/packages/shopify-api/lib/clients/admin/graphql/client.ts @@ -122,7 +122,14 @@ export class GraphqlClient { }); if (response.errors) { - const fetchResponse = response.errors.response!; + const fetchResponse = response.errors.response; + + if (!fetchResponse) { + throw new ShopifyErrors.GraphqlQueryError({ + message: 'fetch api exception', + response: response as Record, + }); + } throwFailedRequest(response, fetchResponse, (options?.retries ?? 0) > 0); } From c0da66a116b55f95a787cdcb1b630221f03ab834 Mon Sep 17 00:00:00 2001 From: Daniil Boiko <5n00p4eg@gmail.com> Date: Mon, 8 Apr 2024 15:31:57 +0300 Subject: [PATCH 2/2] add tests --- packages/shopify-api/lib/auth/oauth/oauth.ts | 2 +- .../lib/auth/oauth/token-exchange.ts | 2 +- .../__tests__/admin_graphql_client.test.ts | 33 ++++++++++++++++++- .../lib/clients/admin/graphql/client.ts | 8 +---- .../lib/clients/admin/rest/client.ts | 2 +- packages/shopify-api/lib/clients/common.ts | 11 ++++++- .../__tests__/storefront_client.test.ts | 28 ++++++++++++++-- .../lib/clients/storefront/client.ts | 5 +-- 8 files changed, 75 insertions(+), 16 deletions(-) diff --git a/packages/shopify-api/lib/auth/oauth/oauth.ts b/packages/shopify-api/lib/auth/oauth/oauth.ts index 5bcc4f5b1..536601630 100644 --- a/packages/shopify-api/lib/auth/oauth/oauth.ts +++ b/packages/shopify-api/lib/auth/oauth/oauth.ts @@ -203,7 +203,7 @@ export function callback(config: ConfigInterface): OAuthCallback { ); if (!postResponse.ok) { - throwFailedRequest(await postResponse.json(), postResponse, false); + throwFailedRequest(await postResponse.json(), false, postResponse); } const session: Session = createSession({ diff --git a/packages/shopify-api/lib/auth/oauth/token-exchange.ts b/packages/shopify-api/lib/auth/oauth/token-exchange.ts index 0d67251b7..fe94843ab 100644 --- a/packages/shopify-api/lib/auth/oauth/token-exchange.ts +++ b/packages/shopify-api/lib/auth/oauth/token-exchange.ts @@ -60,7 +60,7 @@ export function tokenExchange(config: ConfigInterface): TokenExchange { ); if (!postResponse.ok) { - throwFailedRequest(await postResponse.json(), postResponse, false); + throwFailedRequest(await postResponse.json(), false, postResponse); } return { diff --git a/packages/shopify-api/lib/clients/admin/__tests__/admin_graphql_client.test.ts b/packages/shopify-api/lib/clients/admin/__tests__/admin_graphql_client.test.ts index f856f49f8..53e520d8d 100644 --- a/packages/shopify-api/lib/clients/admin/__tests__/admin_graphql_client.test.ts +++ b/packages/shopify-api/lib/clients/admin/__tests__/admin_graphql_client.test.ts @@ -1,3 +1,5 @@ +import {FetchError} from 'node-fetch'; + import * as ShopifyErrors from '../../../error'; import { ApiVersion, @@ -5,11 +7,12 @@ import { LogSeverity, ShopifyHeader, } from '../../../types'; -import {queueMockResponse} from '../../../__tests__/test-helper'; +import {queueError, queueMockResponse} from '../../../__tests__/test-helper'; import {testConfig} from '../../../__tests__/test-config'; import {Session} from '../../../session/session'; import {JwtPayload} from '../../../session/types'; import {DataType, shopifyApi} from '../../..'; +import {HttpRequestError} from '../../../error'; const domain = 'test-shop.myshopify.io'; const QUERY = ` @@ -259,6 +262,34 @@ describe('GraphQL client', () => { }).toMatchMadeHttpRequest(); }); + it('throws error if no response is available', async () => { + const shopify = shopifyApi(testConfig()); + + const client = new shopify.clients.Graphql({session}); + const query = `query getProducts { + products { + edges { + node { + id + } + } + } + }`; + + queueError( + new FetchError( + `uri requested responds with an invalid redirect URL: http://test.com`, + 'invalid-redirect', + ), + ); + + const request = async () => { + await client.request(query); + }; + + await expect(request).rejects.toThrow(HttpRequestError); + }); + it('allows overriding the API version', async () => { const shopify = shopifyApi(testConfig()); diff --git a/packages/shopify-api/lib/clients/admin/graphql/client.ts b/packages/shopify-api/lib/clients/admin/graphql/client.ts index 6b83a90aa..e4242230b 100644 --- a/packages/shopify-api/lib/clients/admin/graphql/client.ts +++ b/packages/shopify-api/lib/clients/admin/graphql/client.ts @@ -124,13 +124,7 @@ export class GraphqlClient { if (response.errors) { const fetchResponse = response.errors.response; - if (!fetchResponse) { - throw new ShopifyErrors.GraphqlQueryError({ - message: 'fetch api exception', - response: response as Record, - }); - } - throwFailedRequest(response, fetchResponse, (options?.retries ?? 0) > 0); + throwFailedRequest(response, (options?.retries ?? 0) > 0, fetchResponse); } return response; diff --git a/packages/shopify-api/lib/clients/admin/rest/client.ts b/packages/shopify-api/lib/clients/admin/rest/client.ts index ce21d4620..d916af720 100644 --- a/packages/shopify-api/lib/clients/admin/rest/client.ts +++ b/packages/shopify-api/lib/clients/admin/rest/client.ts @@ -172,7 +172,7 @@ export class RestClient { ); if (!response.ok) { - throwFailedRequest(body, response, (params.tries ?? 1) > 1); + throwFailedRequest(body, (params.tries ?? 1) > 1, response); } const requestReturn: RestRequestReturn = { diff --git a/packages/shopify-api/lib/clients/common.ts b/packages/shopify-api/lib/clients/common.ts index cb3fc5c3c..2fb1204eb 100644 --- a/packages/shopify-api/lib/clients/common.ts +++ b/packages/shopify-api/lib/clients/common.ts @@ -58,9 +58,18 @@ export function clientLoggerFactory(config: ConfigInterface) { export function throwFailedRequest( body: any, - response: Response, atMaxRetries: boolean, + response?: Response, ): never { + if (typeof response === 'undefined') { + throw new ShopifyErrors.HttpRequestError( + 'Http request error, no response available', + { + body, + }, + ); + } + const responseHeaders = canonicalizeHeaders( Object.fromEntries(response.headers.entries() ?? []), ); diff --git a/packages/shopify-api/lib/clients/storefront/__tests__/storefront_client.test.ts b/packages/shopify-api/lib/clients/storefront/__tests__/storefront_client.test.ts index e539b5884..835345bed 100644 --- a/packages/shopify-api/lib/clients/storefront/__tests__/storefront_client.test.ts +++ b/packages/shopify-api/lib/clients/storefront/__tests__/storefront_client.test.ts @@ -1,4 +1,6 @@ -import {queueMockResponse} from '../../../__tests__/test-helper'; +import {FetchError} from 'node-fetch'; + +import {queueError, queueMockResponse} from '../../../__tests__/test-helper'; import {testConfig} from '../../../__tests__/test-config'; import { ApiVersion, @@ -8,7 +10,7 @@ import { } from '../../../types'; import {Session} from '../../../session/session'; import {JwtPayload} from '../../../session/types'; -import {MissingRequiredArgument} from '../../../error'; +import {HttpRequestError, MissingRequiredArgument} from '../../../error'; import {shopifyApi} from '../../../index'; const domain = 'test-shop.myshopify.io'; @@ -170,4 +172,26 @@ describe('Storefront GraphQL client', () => { ), ); }); + + it('throws error if no response is available', async () => { + const shopify = shopifyApi(testConfig()); + + const client = new shopify.clients.Storefront({ + session, + apiVersion: '2020-01' as any as ApiVersion, + }); + + queueError( + new FetchError( + `uri requested responds with an invalid redirect URL: http://test.com`, + 'invalid-redirect', + ), + ); + + const request = async () => { + await client.request(QUERY); + }; + + await expect(request).rejects.toThrow(HttpRequestError); + }); }); diff --git a/packages/shopify-api/lib/clients/storefront/client.ts b/packages/shopify-api/lib/clients/storefront/client.ts index f0e51b59b..fc1ca61de 100644 --- a/packages/shopify-api/lib/clients/storefront/client.ts +++ b/packages/shopify-api/lib/clients/storefront/client.ts @@ -137,8 +137,9 @@ export class StorefrontClient { }); if (response.errors) { - const fetchResponse = response.errors.response!; - throwFailedRequest(response, fetchResponse, (options?.retries ?? 0) > 0); + const fetchResponse = response.errors.response; + + throwFailedRequest(response, (options?.retries ?? 0) > 0, fetchResponse); } return response;