This repository has been archived by the owner on Apr 11, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 387
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1039 from Shopify/ml-graphql-client-add-utils
[GraphQL Client] Add utility factories to generate common API client functions
- Loading branch information
Showing
5 changed files
with
206 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@shopify/graphql-client": minor | ||
--- | ||
|
||
Add API client utility factories for generating the `getHeaders()` and `getGQLClientParams()` functions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
147 changes: 147 additions & 0 deletions
147
packages/graphql-client/src/api-client-utilities/tests/utilities.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import { generateGetHeaders, generateGetGQLClientParams } from "../utilities"; | ||
|
||
describe("generateGetHeaders()", () => { | ||
const config = { | ||
storeDomain: "https://test.shopify.io", | ||
apiUrl: "https://test.shopify.io/api/2023-10/graphql.json", | ||
apiVersion: "2023-10", | ||
headers: { | ||
"X-Shopify-Storefront-Access-Token": "public-access-token", | ||
}, | ||
}; | ||
|
||
let getHeader: ReturnType<typeof generateGetHeaders>; | ||
|
||
beforeEach(() => { | ||
getHeader = generateGetHeaders(config); | ||
}); | ||
|
||
it("returns a function ", () => { | ||
expect(getHeader).toEqual(expect.any(Function)); | ||
}); | ||
|
||
describe("returned function", () => { | ||
it("returns the config headers if no custom headers were passed in", () => { | ||
expect(getHeader()).toEqual(config.headers); | ||
}); | ||
|
||
it("returns a set of headers that includes both the provided custom headers and the config headers", () => { | ||
const customHeaders = { | ||
"Shopify-Storefront-Id": "shop-id", | ||
}; | ||
|
||
expect(getHeader(customHeaders)).toEqual({ | ||
...customHeaders, | ||
...config.headers, | ||
}); | ||
}); | ||
|
||
it("returns a set of headers where the client config headers cannot be overwritten with the custom headers", () => { | ||
const customHeaders = { | ||
"Shopify-Storefront-Id": "shop-id", | ||
"X-Shopify-Storefront-Access-Token": "", | ||
}; | ||
|
||
const headers = getHeader(customHeaders); | ||
expect(headers["X-Shopify-Storefront-Access-Token"]).toEqual( | ||
config.headers["X-Shopify-Storefront-Access-Token"] | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
describe("generateGetGQLClientParams()", () => { | ||
const mockHeaders = { | ||
"X-Shopify-Storefront-Access-Token": "public-access-token", | ||
}; | ||
const mockApiUrl = "https://test.shopify.io/api/unstable/graphql.json"; | ||
const operation = ` | ||
query products{ | ||
products(first: 1) { | ||
nodes { | ||
id | ||
title | ||
} | ||
} | ||
} | ||
`; | ||
|
||
let getHeaderMock: jest.Mock; | ||
let getApiUrlMock: jest.Mock; | ||
let getGQLClientParams: ReturnType<typeof generateGetGQLClientParams>; | ||
|
||
beforeEach(() => { | ||
getHeaderMock = jest.fn().mockReturnValue(mockHeaders); | ||
getApiUrlMock = jest.fn().mockReturnValue(mockApiUrl); | ||
getGQLClientParams = generateGetGQLClientParams({ | ||
getHeaders: getHeaderMock, | ||
getApiUrl: getApiUrlMock, | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
it("returns a function", () => { | ||
expect(getGQLClientParams).toEqual(expect.any(Function)); | ||
}); | ||
|
||
describe("returned function", () => { | ||
it("returns an array with only the operation string if no additional options were passed into the function", () => { | ||
const params = getGQLClientParams(operation); | ||
|
||
expect(params).toHaveLength(1); | ||
expect(params[0]).toBe(operation); | ||
}); | ||
|
||
it("returns an array with only the operation string if an empty options object was passed into the function", () => { | ||
const params = getGQLClientParams(operation, {}); | ||
|
||
expect(params).toHaveLength(1); | ||
expect(params[0]).toBe(operation); | ||
}); | ||
|
||
it("returns an array with the operation string and an option with variables when variables were provided", () => { | ||
const variables = { first: 10 }; | ||
const params = getGQLClientParams(operation, { variables }); | ||
|
||
expect(params).toHaveLength(2); | ||
|
||
expect(params[0]).toBe(operation); | ||
expect(params[1]).toEqual({ variables }); | ||
}); | ||
|
||
it("returns an array with the operation string and an option with headers when custom headers were provided", () => { | ||
const customHeaders = { "Shopify-Storefront-Id": "shop-id" }; | ||
const params = getGQLClientParams(operation, { customHeaders }); | ||
|
||
expect(params).toHaveLength(2); | ||
|
||
expect(params[0]).toBe(operation); | ||
expect(getHeaderMock).toHaveBeenCalledWith(customHeaders); | ||
expect(params[1]).toEqual({ headers: mockHeaders }); | ||
}); | ||
|
||
it("returns an array with the operation string and an option with url when an api version was provided", () => { | ||
const apiVersion = "unstable"; | ||
const params = getGQLClientParams(operation, { apiVersion }); | ||
|
||
expect(params).toHaveLength(2); | ||
|
||
expect(params[0]).toBe(operation); | ||
expect(getApiUrlMock).toHaveBeenCalledWith(apiVersion); | ||
expect(params[1]).toEqual({ url: mockApiUrl }); | ||
}); | ||
|
||
it("returns an array with the operation string and an option with retries when a retries value was provided", () => { | ||
const retries = 2; | ||
const params = getGQLClientParams(operation, { retries }); | ||
|
||
expect(params).toHaveLength(2); | ||
|
||
expect(params[0]).toBe(operation); | ||
expect(params[1]).toEqual({ retries }); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
packages/graphql-client/src/api-client-utilities/utilities.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { RequestParams } from "../graphql-client/types"; | ||
|
||
import { ApiClient, ApiClientConfig, ApiClientRequestOptions } from "./types"; | ||
|
||
export function generateGetHeaders( | ||
config: ApiClientConfig | ||
): ApiClient["getHeaders"] { | ||
return (customHeaders) => { | ||
return { ...(customHeaders ?? {}), ...config.headers }; | ||
}; | ||
} | ||
|
||
export function generateGetGQLClientParams({ | ||
getHeaders, | ||
getApiUrl, | ||
}: { | ||
getHeaders: ApiClient["getHeaders"]; | ||
getApiUrl: ApiClient["getApiUrl"]; | ||
}) { | ||
return ( | ||
operation: string, | ||
options?: ApiClientRequestOptions | ||
): RequestParams => { | ||
const props: RequestParams = [operation]; | ||
|
||
if (options && Object.keys(options).length > 0) { | ||
const { | ||
variables, | ||
apiVersion: propApiVersion, | ||
customHeaders, | ||
retries, | ||
} = options; | ||
|
||
props.push({ | ||
...(variables ? { variables } : {}), | ||
...(customHeaders ? { headers: getHeaders(customHeaders) } : {}), | ||
...(propApiVersion ? { url: getApiUrl(propApiVersion) } : {}), | ||
...(retries ? { retries } : {}), | ||
}); | ||
} | ||
|
||
return props; | ||
}; | ||
} |