Skip to content

Commit

Permalink
add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
joeluong-sfcc committed May 2, 2024
1 parent 7d00f3a commit a80d4a7
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 11 deletions.
8 changes: 8 additions & 0 deletions src/static/helpers/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
export const CUSTOM_API_DEFAULT_BASE_URI =
"https://{shortCode}.api.commercecloud.salesforce.com/custom/{apiName}/{apiVersion}";
262 changes: 262 additions & 0 deletions src/static/helpers/customApi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import nock from "nock";
import { callCustomEndpoint } from "./customApi";
import { expect } from "chai";
import sinon from "sinon";
import {
ClientConfig,
Response,
StaticClient,
CommonParameters,
} from "@commerce-apps/core";
import { CUSTOM_API_DEFAULT_BASE_URI } from "./config";

describe("callCustomEndpoint", () => {
const runFetchSpy = sinon.spy(StaticClient, "runFetch");

beforeEach(() => {
runFetchSpy.resetHistory();
nock.cleanAll();
});

const clientConfig: ClientConfig = {
parameters: {
shortCode: "short_code",
organizationId: "organization_id",
clientId: "client_id",
siteId: "site_id",
},
};

const options = {
method: "POST",
parameters: {
queryParam1: "query parameter 1",
queryParam2: "query parameter 2",
},
customApiPathParameters: {
apiName: "api_name",
apiVersion: "v2",
endpointPath: "endpoint_path",
},
headers: {
"Content-Type": "text/plain",
authorization: "Bearer token",
},
body: "Hello World",
};

const queryParamString = new URLSearchParams({
...options.parameters,
siteId: clientConfig.parameters?.siteId as string,
}).toString();

// helper function that creates a copy of the options object
// and adds siteId to the parameters object that comes from clientConfig
const addSiteIdToOptions = (optionsObj: Record<string, unknown>) => ({
...optionsObj,
parameters: {
...(optionsObj.parameters as Record<string, unknown>),
siteId: clientConfig.parameters?.siteId,
},
});

it("throws an error when required path parameters are not passed", async () => {
const copyOptions = {
...options,
// omit endpointPath
customApiPathParameters: {
apiName: "api_name",
},
};

try {
await callCustomEndpoint({ options: copyOptions, clientConfig });
expect(true).to.equal(false); // fails if we don't catch an error
} catch (error) {
expect(runFetchSpy.callCount).to.equal(0);
expect(error.toString()).to.equal(
"Error: Missing required property needed in options.customApiPathParameters or clientConfig.parameters: endpointPath"
);
}
});

it('sets api version to "v1" if not provided', async () => {
const copyOptions = {
...options,
// omit apiVersion
customApiPathParameters: {
endpointPath: "endpoint_path",
apiName: "api_name",
},
};

const { shortCode, organizationId } =
clientConfig.parameters as CommonParameters;
const { apiName, endpointPath } = copyOptions.customApiPathParameters;

const nockBasePath = `https://${shortCode}.api.commercecloud.salesforce.com`;
const nockEndpointPath = `/custom/${apiName}/v1/organizations/${
organizationId as string
}/${endpointPath}`;
nock(nockBasePath).post(nockEndpointPath).query(true).reply(200);

const response = (await callCustomEndpoint({
options: copyOptions,
clientConfig,
rawResponse: true,
})) as Response;

expect(response.status).to.equal(200);

const runFetchPassedArgs = runFetchSpy.getCall(0).args;
expect(runFetchSpy.callCount).to.equal(1);
// commerce-sdk-core expects apiVersion in clientConfig.parameters
expect(
runFetchPassedArgs[1]?.client?.clientConfig?.parameters?.apiVersion
).to.equal("v1");
});

it("runFetch is called with the correct arguments", async () => {
const { shortCode, organizationId } =
clientConfig.parameters as CommonParameters;
const { apiName, endpointPath } = options.customApiPathParameters;

const nockBasePath = `https://${shortCode}.api.commercecloud.salesforce.com`;
const nockEndpointPath = `/custom/${apiName}/v2/organizations/${
organizationId as string
}/${endpointPath}`;
nock(nockBasePath).post(nockEndpointPath).query(true).reply(200);

await callCustomEndpoint({ options, clientConfig, rawResponse: true });

const runFetchPassedArgs = runFetchSpy.getCall(0).args;

const expectedPathParms = {
...clientConfig.parameters,
...options.customApiPathParameters,
};

expect(runFetchSpy.callCount).to.equal(1);
expect(runFetchPassedArgs[0]).to.equal("post");
expect(runFetchPassedArgs[1].client).to.deep.equal({
clientConfig: {
...clientConfig,
baseUri: CUSTOM_API_DEFAULT_BASE_URI,
parameters: expectedPathParms,
},
});
expect(runFetchPassedArgs[1].pathParameters).to.deep.equal(
expectedPathParms
);
expect(runFetchPassedArgs[1].queryParameters).to.deep.equal({
...options.parameters,
siteId: clientConfig.parameters?.siteId,
});
expect(runFetchPassedArgs[1].headers).to.deep.equal(options.headers);
expect(runFetchPassedArgs[1].rawResponse).to.equal(true);
expect(runFetchPassedArgs[1].body).to.equal(options.body);
});

it("uses path params from options and clientConfig, prioritizing options", async () => {
const copyClientConfig = {
...clientConfig,
// Only shortCode will be used
parameters: {
endpointPath: "clientConfig_endpoint_path",
apiName: "clientConfig_api_name",
shortCode: "clientconfig_shortcode",
apiVersion: "v2",
organizationId: "clientConfig_organizationId",
siteId: "site_id",
},
};

const copyOptions = {
...options,
// these parameters will be prioritzed
customApiPathParameters: {
endpointPath: "customApiPathParameters_endpoint_path",
apiName: "customApiPathParameters_api_name",
apiVersion: "v3",
organizationId: "customApiPathParameters_organizationId",
},
};

// nock interception should be using custom API path parameters from options
const { apiName, endpointPath, organizationId, apiVersion } =
copyOptions.customApiPathParameters;
// except shortcode since we didn't implement it in copyOptions.customApiPathParameters
const { shortCode } = copyClientConfig.parameters;

const nockBasePath = `https://${shortCode}.api.commercecloud.salesforce.com`;
const nockEndpointPath = `/custom/${apiName}/${apiVersion}/organizations/${organizationId}/${endpointPath}`;
nock(nockBasePath).post(nockEndpointPath).query(true).reply(200);

await callCustomEndpoint({
options: copyOptions,
clientConfig: copyClientConfig,
});

const runFetchPassedArgs = runFetchSpy.getCall(0).args;

const expectedPathParams = {
...copyClientConfig.parameters,
...copyOptions.customApiPathParameters,
};

expect(runFetchSpy.callCount).to.equal(1);
expect(runFetchPassedArgs[1].pathParameters).to.deep.equal(
expectedPathParams
);
expect(runFetchPassedArgs[1].client.clientConfig.parameters).to.deep.equal(
expectedPathParams
);
});

it("uses application/json as default content type if not provided", async () => {
const copyOptions = {
...options,
// exclude Content-Type
headers: {
authorization: "Bearer token",
},
};

const { apiName, endpointPath, apiVersion } =
copyOptions.customApiPathParameters;
const { shortCode, organizationId } =
clientConfig.parameters as CommonParameters;

const expectedJsonHeaders = {
authorization: "Bearer token",
"Content-Type": "application/json",
};

const nockBasePath = `https://${shortCode}.api.commercecloud.salesforce.com`;
const nockEndpointPath = `/custom/${apiName}/${apiVersion}/organizations/${
organizationId as string
}/${endpointPath}`;
nock(nockBasePath, {
reqheaders: expectedJsonHeaders,
})
.post(nockEndpointPath)
.query(true)
.reply(200);

await callCustomEndpoint({
options: copyOptions,
clientConfig,
});

const runFetchPassedArgs = runFetchSpy.getCall(0).args;
expect(runFetchSpy.callCount).to.equal(1);
expect(runFetchPassedArgs[1].headers).to.deep.equal(expectedJsonHeaders)
});
});
19 changes: 8 additions & 11 deletions src/static/helpers/customApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ import { BodyInit, RequestInit } from "node-fetch";
import { ClientConfig, Response, StaticClient } from "@commerce-apps/core";
import { PathParameters } from "@commerce-apps/core/dist/base/resource";
import type { OperationOptions } from "retry";

// TODO: move into config file
const CUSTOM_API_DEFAULT_BASE_URI =
"https://{shortCode}.api.commercecloud.salesforce.com/custom/{apiName}/{apiVersion}";
import { CUSTOM_API_DEFAULT_BASE_URI } from "./config";

// Helper method to find Content Type header
// returns true if it exists, false otherwise
Expand Down Expand Up @@ -93,13 +90,13 @@ export const callCustomEndpoint = async (args: {
pathParams.apiVersion = "v1";
}

let clientConfigCopy = clientConfig;
if (!clientConfig.baseUri) {
clientConfigCopy = {
...clientConfig,
baseUri: CUSTOM_API_DEFAULT_BASE_URI,
};
}
const clientConfigCopy = {
...clientConfig,
...(!clientConfig.baseUri && { baseUri: CUSTOM_API_DEFAULT_BASE_URI }),
parameters: {
...pathParams,
},
};

// Use siteId from clientConfig if it is not defined in options and is available in clientConfig
const useSiteId = Boolean(
Expand Down

0 comments on commit a80d4a7

Please sign in to comment.