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/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 969a0263f..e4242230b 100644 --- a/packages/shopify-api/lib/clients/admin/graphql/client.ts +++ b/packages/shopify-api/lib/clients/admin/graphql/client.ts @@ -122,8 +122,9 @@ export class GraphqlClient { }); 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; 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;