Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/sdk/jest.config.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { baseConfig } from "@vue-storefront/jest-config";

export default {
...baseConfig,
// globalSetup: "./__tests__/integration/__config__/jest.setup.global.ts",
// globalTeardown: "./__tests__/integration/__config__/jest.teardown.global.ts",
globalSetup: "./src/__tests__/integration/__config__/jest.setup.global.ts",
globalTeardown:
"./src/__tests__/integration/__config__/jest.teardown.global.ts",
// setupFilesAfterEnv: ["./__tests__/integration/__config__/jest.setup.ts"],
};
5 changes: 4 additions & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
"babel-preset-node": "^5.1.1",
"nodemon": "^2.0.20",
"ts-jest": "^29.0.2",
"ts-node-dev": "^2.0.0"
"ts-node-dev": "^2.0.0",
"@vue-storefront/middleware": "*",
"isomorphic-fetch": "^3.0.0",
"axios": "^1.6.7"
},
"engines": {
"npm": ">=7.0.0",
Expand Down
23 changes: 23 additions & 0 deletions packages/sdk/src/__tests__/__mocks__/apiClient/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
const { apiClientFactory } = require("@vue-storefront/middleware");

const onCreate = (settings) => {
return {
config: settings,
client: null,
};
};

const { createApiClient } = apiClientFactory({
onCreate,
api: {
getProduct: async (_context, params) => {
return { id: params.id, name: "Test Product" };
},
getProducts: async (_context, _params) => {
return [{ id: 1, name: "Test Product" }];
},
},
});

exports.createApiClient = createApiClient;
12 changes: 12 additions & 0 deletions packages/sdk/src/__tests__/__mocks__/apiClient/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type Endpoints = {
/**
* Get the product by id.
*/
getProduct: (params: { id: number }) => Promise<{ id: number; name: string }>;
/**
* Get the list of products.
*/
getProducts: (params: {
limit: number;
}) => Promise<{ id: number; name: string }[]>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createServer } from "@vue-storefront/middleware";

async function runMiddleware(app) {
return new Promise((resolve) => {
const server = app.listen(8181, async () => {
resolve(server);
});
});
}

export default async () => {
const app = await createServer({
integrations: {
commerce: {
location: "./src/__tests__/__mocks__/apiClient/server",
configuration: {},
},
},
});
const server = await runMiddleware(app);
globalThis.__MIDDLEWARE__ = server;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default () => {
globalThis.__MIDDLEWARE__.close();
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import "isomorphic-fetch";
import { initSDK, buildModule } from "../../../index";
import { moduleFromEndpoints } from "../../../modules/moduleFromEndpoints";
import {
moduleFromEndpoints,
prepareConfig,
} from "../../../modules/moduleFromEndpoints";
import { Endpoints } from "../../__mocks__/apiClient/types";

type Endpoints = {
getProduct: (params: { id: string }) => Promise<any>;
getProducts: (params: { limit: number }) => Promise<any>;
};
const axios = require("axios/dist/node/axios.cjs");

describe("moduleFromEndpoints", () => {
it("should be able to be used as standard SDK module", async () => {
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints),
commerce: buildModule(moduleFromEndpoints, {
apiUrl: "http://localhost:8181/commerce",
}),
};

const sdk = initSDK(sdkConfig);
Expand All @@ -19,12 +23,284 @@ describe("moduleFromEndpoints", () => {

it("should use generic types to define the endpoints", async () => {
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>),
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "http://localhost:8181/commerce",
}),
};

const sdk = initSDK(sdkConfig);

expect(sdk.commerce.getProduct).toBeInstanceOf(Function);
expect(sdk.commerce.getProducts).toBeInstanceOf(Function);
});

it("should allow to override the default HTTP Client", async () => {
const customHttpClient = jest.fn();
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "http://localhost:8181/commerce",
httpClient: customHttpClient,
}),
};
const sdk = initSDK(sdkConfig);

await sdk.commerce.getProduct({ id: 1 });

expect(customHttpClient).toHaveBeenCalled();
});

it("should send a POST request to <BASE_URL>/<METHOD_NAME> by default", async () => {
const customHttpClient = jest.fn();
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "http://localhost:8181/commerce",
httpClient: customHttpClient,
}),
};
const sdk = initSDK(sdkConfig);

await sdk.commerce.getProduct({ id: 1 });

expect(customHttpClient).toHaveBeenCalledWith(
"http://localhost:8181/commerce/getProduct",
[{ id: 1 }],
expect.objectContaining({
method: "POST",
})
);
});

it("should use default HTTP Client if it's not provided", async () => {
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "http://localhost:8181/commerce",
}),
};
const sdk = initSDK(sdkConfig);

const response = await sdk.commerce.getProduct({ id: 1 });

// To avoid mocking fetch, we're calling the real middleware and verifying the response.
expect(response).toEqual({ id: 1, name: "Test Product" });
});

it("should allow to use GET request with query parameters", async () => {
const customHttpClient = jest.fn();
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "http://localhost:8181/commerce",
httpClient: customHttpClient,
}),
};
const sdk = initSDK(sdkConfig);

await sdk.commerce.getProducts(
{ limit: 1 },
prepareConfig({ method: "GET" })
);

expect(customHttpClient).toHaveBeenCalledWith(
`http://localhost:8181/commerce/getProducts?body=${encodeURIComponent(
JSON.stringify([{ limit: 1 }])
)}`,
[],
expect.objectContaining({
method: "GET",
})
);
});

it("should allow to use GET request when apiUrl is a path", async () => {
const customHttpClient = jest.fn();
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "/api/commerce",
httpClient: customHttpClient,
}),
};
const sdk = initSDK(sdkConfig);

await sdk.commerce.getProducts(
{ limit: 1 },
prepareConfig({ method: "GET" })
);

expect(customHttpClient).toHaveBeenCalledWith(
`/api/commerce/getProducts?body=${encodeURIComponent(
JSON.stringify([{ limit: 1 }])
)}`,
[],
expect.objectContaining({
method: "GET",
})
);
});

it("should normalize the url", async () => {
const customHttpClient = jest.fn();
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "http://localhost:8181/commerce///", // Extra slashes
httpClient: customHttpClient,
}),
};
const sdk = initSDK(sdkConfig);

await sdk.commerce.getProduct({ id: 1 });

expect(customHttpClient).toHaveBeenCalledWith(
"http://localhost:8181/commerce/getProduct",
expect.any(Array),
expect.any(Object)
);
});

it("should allow to use custom headers", async () => {
const customHttpClient = jest.fn();
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "http://localhost:8181/commerce",
httpClient: customHttpClient,
}),
};
const sdk = initSDK(sdkConfig);

await sdk.commerce.getProduct(
{ id: 1 },
prepareConfig({
method: "POST",
headers: {
"X-Test": "x-test-header",
},
})
);

expect(customHttpClient).toHaveBeenCalledWith(
"http://localhost:8181/commerce/getProduct",
expect.any(Array),
expect.objectContaining({
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Test": "x-test-header",
},
})
);
});

it("should allow to define default headers", async () => {
const customHttpClient = jest.fn();
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "http://localhost:8181/commerce",
httpClient: customHttpClient,
defaultRequestConfig: {
headers: {
"X-Test": "x-test-header",
},
},
}),
};
const sdk = initSDK(sdkConfig);

await sdk.commerce.getProduct({ id: 1 });

expect(customHttpClient).toHaveBeenCalledWith(
"http://localhost:8181/commerce/getProduct",
expect.any(Array),
expect.objectContaining({
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Test": "x-test-header",
},
})
);
});

it("should use different base URL during SSR if defined", async () => {
const customHttpClient = jest.fn();
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "/api/commerce",
ssrApiUrl: "http://localhost:8181/commerce",
httpClient: customHttpClient,
}),
};
const sdk = initSDK(sdkConfig);

await sdk.commerce.getProduct({ id: 1 });

expect(customHttpClient).toHaveBeenCalledWith(
"http://localhost:8181/commerce/getProduct",
expect.any(Array),
expect.any(Object)
);
});

it("should be able to use axios as a custom HTTP client", async () => {
expect.assertions(2);

const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "http://localhost:8181/commerce",
httpClient: async (url, params, config) => {
const { data } = await axios(url, {
...config,
data: params,
});
return data;
},
}),
};
const sdk = initSDK(sdkConfig);

const postResponse = await sdk.commerce.getProduct({ id: 1 });
const getResponse = await sdk.commerce.getProduct(
{ id: 2 },
prepareConfig({ method: "GET" })
);

expect(postResponse).toEqual({ id: 1, name: "Test Product" });
expect(getResponse).toEqual({ id: 2, name: "Test Product" });
});

it("should accept headers as Record<string | string[]>", async () => {
const customHttpClient = jest.fn();
const sdkConfig = {
commerce: buildModule(moduleFromEndpoints<Endpoints>, {
apiUrl: "http://localhost:8181/commerce",
httpClient: customHttpClient,
defaultRequestConfig: {
headers: {
"X-Test-Default": ["x-test-header", "x-test-header-2"],
},
},
}),
};
const sdk = initSDK(sdkConfig);

await sdk.commerce.getProduct(
{ id: 1 },
prepareConfig({
method: "POST",
headers: {
"X-Test": ["x-test-header", "x-test-header-2"],
},
})
);

expect(customHttpClient).toHaveBeenCalledWith(
"http://localhost:8181/commerce/getProduct",
expect.any(Array),
expect.objectContaining({
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Test-Default": "x-test-header,x-test-header-2",
"X-Test": "x-test-header,x-test-header-2",
},
})
);
});
});
Loading