Skip to content
This repository has been archived by the owner on Apr 11, 2024. It is now read-only.

Commit

Permalink
Update to use common API client utils and enable server and browser t…
Browse files Browse the repository at this point in the history
…esting
melissaluu committed Nov 8, 2023
1 parent c61e32a commit 431b8f2
Showing 9 changed files with 130 additions and 163 deletions.
2 changes: 2 additions & 0 deletions .changeset/tricky-sheep-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
4 changes: 2 additions & 2 deletions packages/storefront-api-client/README.md
Original file line number Diff line number Diff line change
@@ -66,8 +66,8 @@ const client = createStorefrontApiClient({
| -------------- | ------------------------ | ---------------------------------------------------- |
| storeDomain | `string` | The secure store domain |
| apiVersion | `string` | The Storefront API version to use in the API request |
| publicAccessToken | `string \| null` | The provided public access token |
| privateAccessToken | `string \| null` | The provided private access token |
| publicAccessToken | `string \| never` | The provided public access token. If `privateAccessToken` was provided, `publicAccessToken` will not be available. |
| privateAccessToken | `string \| never` | The provided private access token. If `publicAccessToken` was provided, `privateAccessToken` will not be available. |
| headers | `{[key: string]: string}` | The headers generated by the client during initialization |
| apiUrl | `string` | The API URL generated from the provided store domain and api version |
| clientName? | `string` | The provided client name |
16 changes: 15 additions & 1 deletion packages/storefront-api-client/package.json
Original file line number Diff line number Diff line change
@@ -41,7 +41,21 @@
"release": "yarn build && changeset publish"
},
"jest": {
"testEnvironment": "jsdom",
"projects": [
{
"displayName": {
"name": "test:browser",
"color": "blue"
},
"testEnvironment": "jsdom"
},
{
"displayName": {
"name": "test:server",
"color": "yellow"
}
}
],
"setupFilesAfterEnv": [
"./src/tests/setupTests.ts"
],
55 changes: 6 additions & 49 deletions packages/storefront-api-client/src/storefront-api-client.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {
createGraphQLClient,
RequestParams as GQLClientRequestParams,
getCurrentSupportedApiVersions,
validateDomainAndGetStoreUrl,
validateApiVersion,
generateGetGQLClientParams,
generateGetHeaders,
ApiClientRequestParams,
ApiClientRequestOptions,
} from "@shopify/graphql-client";

import {
@@ -92,22 +92,20 @@ export function createStorefrontApiClient({
logger,
});

const getHeaders = generateGetHeader(config);
const getHeaders = generateGetHeaders(config);
const getApiUrl = generateGetApiUrl(config, apiUrlFormatter);

const getGQLClientRequestProps = generateGetGQLClientProps({
const getGQLClientParams = generateGetGQLClientParams({
getHeaders,
getApiUrl,
});

const fetch = (...props: ApiClientRequestParams) => {
const requestProps = getGQLClientRequestProps(...props);
return graphqlClient.fetch(...requestProps);
return graphqlClient.fetch(...getGQLClientParams(...props));
};

const request = <TData>(...props: ApiClientRequestParams) => {
const requestProps = getGQLClientRequestProps(...props);
return graphqlClient.request<TData>(...requestProps);
return graphqlClient.request<TData>(...getGQLClientParams(...props));
};

const client: StorefrontApiClient = {
@@ -143,14 +141,6 @@ function generateApiUrlFormatter(
};
}

function generateGetHeader(
config: StorefrontApiClientConfig
): StorefrontApiClient["getHeaders"] {
return (customHeaders) => {
return { ...(customHeaders ?? {}), ...config.headers };
};
}

function generateGetApiUrl(
config: StorefrontApiClientConfig,
apiUrlFormatter: (version?: string) => string
@@ -159,36 +149,3 @@ function generateGetApiUrl(
return propApiVersion ? apiUrlFormatter(propApiVersion) : config.apiUrl;
};
}

function generateGetGQLClientProps({
getHeaders,
getApiUrl,
}: {
getHeaders: StorefrontApiClient["getHeaders"];
getApiUrl: StorefrontApiClient["getApiUrl"];
}) {
return (
operation: string,
options?: ApiClientRequestOptions
): GQLClientRequestParams => {
const props: GQLClientRequestParams = [operation];

if (options) {
const {
variables,
apiVersion: propApiVersion,
customHeaders,
retries,
} = options;

props.push({
variables,
headers: customHeaders ? getHeaders(customHeaders) : undefined,
url: propApiVersion ? getApiUrl(propApiVersion) : undefined,
retries,
});
}

return props;
};
}
134 changes: 66 additions & 68 deletions packages/storefront-api-client/src/tests/storefront-api-client.test.ts
Original file line number Diff line number Diff line change
@@ -210,7 +210,7 @@ describe("Storefront API Client", () => {

it("console warns when a unsupported api version is provided", () => {
const consoleWarnSpy = jest
.spyOn(window.console, "warn")
.spyOn(global.console, "warn")
.mockImplementation(jest.fn());

createStorefrontApiClient({
@@ -238,38 +238,40 @@ describe("Storefront API Client", () => {
);
});

it("throws an error when a private access token is provided in a browser environment (window is defined)", () => {
expect(() =>
createStorefrontApiClient({
...config,
publicAccessToken: undefined as any,
privateAccessToken: "private-access-token",
})
).toThrow(
new Error(
"Storefront API Client: private access tokens and headers should only be used in a server-to-server implementation. Use the public API access token in nonserver environments."
)
);
});

it("throws an error when both public and private access tokens are provided in a server environment (window is undefined)", () => {
const windowSpy = jest
.spyOn(window, "window", "get")
.mockImplementation(() => undefined as any);

expect(() =>
createStorefrontApiClient({
...config,
privateAccessToken: "private-token",
} as any)
).toThrow(
new Error(
`Storefront API Client: only provide either a public or private access token`
)
);
if (typeof window === "object") {
describe("browser environment", () => {
it("throws an error when a private access token is provided in a browser environment", () => {
expect(() =>
createStorefrontApiClient({
...config,
publicAccessToken: undefined as any,
privateAccessToken: "private-access-token",
})
).toThrow(
new Error(
"Storefront API Client: private access tokens and headers should only be used in a server-to-server implementation. Use the public API access token in nonserver environments."
)
);
});
});
}

windowSpy.mockRestore();
});
if (typeof window === "undefined") {
describe("server enviornment", () => {
it("throws an error when both public and private access tokens are provided in a server environment", () => {
expect(() =>
createStorefrontApiClient({
...config,
privateAccessToken: "private-token",
} as any)
).toThrow(
new Error(
`Storefront API Client: only provide either a public or private access token`
)
);
});
});
}
});
});

@@ -285,23 +287,21 @@ describe("Storefront API Client", () => {
expect(client.config.privateAccessToken).toBeUndefined();
});

it("returns a config object that includes the provided private access token and not a public access token when in a server environment (window is undefined)", () => {
const windowSpy = jest
.spyOn(window, "window", "get")
.mockImplementation(() => undefined as any);

const privateAccessToken = "private-token";
if (typeof window === "undefined") {
describe("server environment", () => {
it("returns a config object that includes the provided private access token and not a public access token when in a server environment", () => {
const privateAccessToken = "private-token";

const client = createStorefrontApiClient({
...config,
publicAccessToken: undefined,
privateAccessToken,
const client = createStorefrontApiClient({
...config,
publicAccessToken: undefined,
privateAccessToken,
});
expect(client.config.privateAccessToken).toBe(privateAccessToken);
expect(client.config.publicAccessToken).toBeUndefined();
});
});
expect(client.config.privateAccessToken).toBe(privateAccessToken);
expect(client.config.publicAccessToken).toBeUndefined();

windowSpy.mockRestore();
});
}

it("returns a config object that includes the provided client name", () => {
const clientName = "test-client";
@@ -382,25 +382,23 @@ describe("Storefront API Client", () => {
);
});

it("returns a header object that includes the private headers when a private access token is provided", () => {
const windowSpy = jest
.spyOn(window, "window", "get")
.mockImplementation(() => undefined as any);
if (typeof window === "undefined") {
describe("server environment", () => {
it("returns a header object that includes the private headers when a private access token is provided", () => {
const privateAccessToken = "private-token";

const privateAccessToken = "private-token";
const client = createStorefrontApiClient({
...config,
publicAccessToken: undefined,
privateAccessToken,
});

const client = createStorefrontApiClient({
...config,
publicAccessToken: undefined,
privateAccessToken,
expect(
client.config.headers[PRIVATE_ACCESS_TOKEN_HEADER]
).toEqual(privateAccessToken);
});
});

expect(client.config.headers[PRIVATE_ACCESS_TOKEN_HEADER]).toEqual(
privateAccessToken
);

windowSpy.mockRestore();
});
}

it("returns a header object that includes the SDK variant source header when client name is provided", () => {
const clientName = "test-client";
@@ -474,7 +472,7 @@ describe("Storefront API Client", () => {

it("console warns when a unsupported api version is provided", () => {
const consoleWarnSpy = jest
.spyOn(window.console, "warn")
.spyOn(global.console, "warn")
.mockImplementation(jest.fn());

const version = "2021-01";
@@ -514,8 +512,8 @@ describe("Storefront API Client", () => {
});

describe("parameters", () => {
it("calls the graphql client fetch() with just the operation string when there is no provided options", async () => {
const test = await client.fetch(operation);
it("calls the graphql client fetch() with just the operation string when there are no options provided", async () => {
await client.fetch(operation);

expect(graphqlClientMock.fetch).toHaveBeenCalledWith(operation);
});
@@ -598,8 +596,8 @@ describe("Storefront API Client", () => {
});

describe("parameters", () => {
it("calls the graphql client request() with just the operation string when there is no provided options", async () => {
const test = await client.request(operation);
it("calls the graphql client request() with just the operation string when there are no options provided", async () => {
await client.request(operation);

expect(graphqlClientMock.request).toHaveBeenCalledWith(operation);
});
44 changes: 21 additions & 23 deletions packages/storefront-api-client/src/tests/validations.test.ts
Original file line number Diff line number Diff line change
@@ -19,27 +19,25 @@ describe("validateRequiredAccessToken()", () => {
});

describe("validatePrivateAccessTokenUsage()", () => {
it("throws an error when private token is provided within a browser environment (window is defined)", () => {
const privateAccessToken = "private-token";

expect(() => validatePrivateAccessTokenUsage(privateAccessToken)).toThrow(
new Error(
"Storefront API Client: private access tokens and headers should only be used in a server-to-server implementation. Use the public API access token in nonserver environments."
)
);
});

it("does not throw an error when only the private token is provided when within a server environment (window is undefined)", () => {
const windowSpy = jest
.spyOn(window, "window", "get")
.mockImplementation(() => undefined as any);

const privateAccessToken = "private-token";

expect(() =>
validatePrivateAccessTokenUsage(privateAccessToken)
).not.toThrow();

windowSpy.mockRestore();
});
if (typeof window === "object") {
it("throws an error when a private token is provided within a browser environment (window is defined)", () => {
const privateAccessToken = "private-token";

expect(() => validatePrivateAccessTokenUsage(privateAccessToken)).toThrow(
new Error(
"Storefront API Client: private access tokens and headers should only be used in a server-to-server implementation. Use the public API access token in nonserver environments."
)
);
});
}

if (typeof window === "undefined") {
it("does not throw an error when only the private token is provided when within a server environment", () => {
const privateAccessToken = "private-token";

expect(() =>
validatePrivateAccessTokenUsage(privateAccessToken)
).not.toThrow();
});
}
});
Loading

0 comments on commit 431b8f2

Please sign in to comment.