Skip to content

Commit 28cceb8

Browse files
committed
fix: add tests
1 parent 28e4374 commit 28cceb8

13 files changed

+775
-35
lines changed

.eslintrc.cjs

+7
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ module.exports = {
4646
// parameters is definitely desirable
4747
'no-param-reassign': 'off',
4848

49+
'import/no-extraneous-dependencies': [
50+
'error',
51+
{
52+
devDependencies: true,
53+
},
54+
],
55+
4956
// Allow while (true) infinite loops
5057
// 'no-constant-condition': ['error', { checkLoops: false }],
5158

.vscode/launch.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"type": "node",
6+
"request": "launch",
7+
"name": "Debug Current Mocha Test",
8+
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
9+
"args": [
10+
"--timeout", "999999",
11+
"--colors",
12+
"--no-timeouts",
13+
"${file}"
14+
],
15+
"console": "integratedTerminal",
16+
"internalConsoleOptions": "neverOpen",
17+
"skipFiles": ["<node_internals>/**"],
18+
"cwd": "${workspaceFolder}"
19+
}
20+
]
21+
}

.vscode/settings.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"editor.codeActionsOnSave": {
3+
"source.fixAll.eslint": "explicit",
4+
"source.fixAll.stylelint": "explicit"
5+
},
6+
"eslint.enable": true,
7+
"prettier.enable": true,
8+
"eslint.validate": ["javascript"],
9+
"stylelint.enable": true,
10+
"[css]": {
11+
"editor.defaultFormatter": "stylelint.vscode-stylelint"
12+
},
13+
"[json]": {
14+
"editor.defaultFormatter": "esbenp.prettier-vscode"
15+
},
16+
"liveServer.settings.port": 5501,
17+
}

package-lock.json

+125
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"prepare": "husky",
1010
"build": "node build.js",
1111
"lint": "eslint . --ext js,cjs,mjs",
12-
"test": "c8 mocha -i -g 'Post-Deploy' --spec test/*.test.js",
12+
"test": "c8 --all --include 'src/**/*.js' mocha -i -g 'Post-Deploy' --spec 'test/**/*.test.js'",
1313
"dev": "if test -e .dev.vars; then wrangler dev --; else echo \"Need a .dev.vars files before starting local dev server\"; fi",
1414
"dev:remote": "wrangler dev --remote",
1515
"test-postdeploy": "mocha --spec test/post-deploy.test.js",
@@ -52,12 +52,14 @@
5252
"eslint": "8.57.0",
5353
"eslint-plugin-header": "3.1.1",
5454
"eslint-plugin-import": "2.29.1",
55+
"esmock": "^2.6.9",
5556
"husky": "9.1.4",
5657
"lint-staged": "15.2.9",
5758
"mocha": "10.7.3",
5859
"mocha-multi-reporters": "1.5.1",
5960
"nock": "13.5.4",
6061
"semantic-release": "24.0.0",
62+
"sinon": "^19.0.2",
6163
"wrangler": "3.71.0"
6264
},
6365
"lint-staged": {

src/catalog/update.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { saveProducts } from '../utils/r2.js';
2323
* @param {Object} product - The product object to be saved.
2424
* @returns {Promise<Object>} - A promise that resolves to the saved product.
2525
*/
26-
async function putProduct(ctx, config, product) {
26+
export async function putProduct(ctx, config, product) {
2727
if (!product.sku) {
2828
throw errorWithResponse(400, 'invalid request body: missing sku');
2929
}
@@ -41,7 +41,14 @@ async function putProduct(ctx, config, product) {
4141
*/
4242
export async function handleProductSaveRequest(ctx, config, request) {
4343
try {
44-
const requestBody = await request.json();
44+
let requestBody;
45+
46+
try {
47+
requestBody = await request.json();
48+
} catch (jsonError) {
49+
ctx.log.error('Invalid JSON in request body:', jsonError);
50+
return errorResponse(400, 'invalid JSON');
51+
}
4552

4653
if (config.sku === '*') {
4754
return errorResponse(501, 'not implemented');

test/catalog/fetch.test.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2024 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import assert from 'node:assert';
14+
import sinon from 'sinon';
15+
import esmock from 'esmock';
16+
import { ResponseError } from '../../src/utils/http.js';
17+
18+
describe('handleProductFetchRequest', () => {
19+
let handleProductFetchRequest;
20+
let fetchProductStub;
21+
let errorResponseStub;
22+
let ctx;
23+
let config;
24+
25+
beforeEach(async () => {
26+
fetchProductStub = sinon.stub();
27+
errorResponseStub = sinon.stub();
28+
29+
const moduleUnderTest = await esmock('../../src/catalog/fetch.js', {
30+
'../../src/utils/r2.js': { fetchProduct: fetchProductStub },
31+
'../../src/utils/http.js': { errorResponse: errorResponseStub },
32+
});
33+
34+
({ handleProductFetchRequest } = moduleUnderTest);
35+
36+
ctx = {
37+
url: new URL('https://example.com/products/12345'),
38+
log: {
39+
error: sinon.stub(),
40+
},
41+
};
42+
config = {};
43+
});
44+
45+
afterEach(async () => {
46+
await esmock.purge(handleProductFetchRequest);
47+
sinon.restore();
48+
});
49+
50+
it('should return the product response when fetchProduct succeeds', async () => {
51+
const sku = '12345';
52+
const product = { id: sku, name: 'Test Product' };
53+
fetchProductStub.resolves(product);
54+
55+
const response = await handleProductFetchRequest(ctx, config);
56+
57+
assert.equal(response.headers.get('Content-Type'), 'application/json');
58+
const responseBody = await response.text();
59+
assert.equal(responseBody, JSON.stringify(product));
60+
sinon.assert.calledWith(fetchProductStub, ctx, config, sku);
61+
});
62+
63+
it('should return e.response when fetchProduct throws an error with a response property', async () => {
64+
const errorResponse = new Response('Not Found', { status: 404 });
65+
const error = new ResponseError('Product not found', errorResponse);
66+
fetchProductStub.rejects(error);
67+
68+
const response = await handleProductFetchRequest(ctx, config);
69+
70+
assert.strictEqual(response, errorResponse);
71+
sinon.assert.notCalled(ctx.log.error);
72+
});
73+
74+
it('should log error and return 500 response when fetchProduct throws an error without a response property', async () => {
75+
const error = new Error('Internal Server Error');
76+
fetchProductStub.rejects(error);
77+
const errorResp = new Response('Internal Server Error', { status: 500 });
78+
errorResponseStub.returns(errorResp);
79+
80+
const response = await handleProductFetchRequest(ctx, config);
81+
82+
assert.equal(response.status, 500);
83+
const responseBody = await response.text();
84+
assert.equal(responseBody, 'Internal Server Error');
85+
sinon.assert.calledOnce(ctx.log.error);
86+
sinon.assert.calledWith(ctx.log.error, error);
87+
sinon.assert.calledWith(errorResponseStub, 500, 'internal server error');
88+
});
89+
});

0 commit comments

Comments
 (0)