From ad2e4eb50399d36b3ff3055aaea95ccb7ca77b13 Mon Sep 17 00:00:00 2001 From: Kevin Ulrich Date: Sat, 23 Mar 2024 10:35:12 -0700 Subject: [PATCH] Feat/db tests (#3) chore: add db tests --- cov-badge.svg | 2 +- jest.config.json | 8 ++-- package.json | 3 +- src/services/db.js | 35 +++++++------- src/services/db.test.js | 103 ++++++++++++++++++++++++++++++++++++++++ yarn.lock | 10 +++- 6 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 src/services/db.test.js diff --git a/cov-badge.svg b/cov-badge.svg index 6059151..6dcc4c9 100644 --- a/cov-badge.svg +++ b/cov-badge.svg @@ -1 +1 @@ -Coverage: 9.11%Coverage9.11% \ No newline at end of file +Coverage: 27.61%Coverage27.61% \ No newline at end of file diff --git a/jest.config.json b/jest.config.json index 7818057..bf67c22 100644 --- a/jest.config.json +++ b/jest.config.json @@ -1,7 +1,8 @@ { + "rootDir": "src", "collectCoverage": true, "collectCoverageFrom": [ - "src/**/*.js" + "/**/*.js" ], "coveragePathIgnorePatterns": [ "node_modules", @@ -12,9 +13,10 @@ ".mock.js", "test.js", "debug.js", - "constants" + "constants", + ".aws-sam" ], - "coverageDirectory": "/coverage/", + "coverageDirectory": "/../coverage/", "coverageReporters": [ "json-summary", "html" diff --git a/package.json b/package.json index fa6855b..6437bb3 100755 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "devDependencies": { "@types/jest": "^29.5.12", "aws-sdk-client-mock": "^3.0.1", + "aws-sdk-client-mock-jest": "^4.0.0", "eslint": "^8.57.0", "jest": "^29.7.0", "make-coverage-badge": "^1.2.0", @@ -26,7 +27,7 @@ "destroy": "cd scripts && ./destroy.sh", "dev:destroy": "export AWS_PROFILE=cb-dev && cd scripts && ./destroy.sh", "test": "jest --passWithNoTests src", - "test:coverage": "yarn test --config jest.config.json --no-cache", + "test:coverage": "yarn test --silent --config jest.config.json --no-cache", "test:badge": "yarn test:coverage && yarn make-coverage-badge --output-path ./cov-badge.svg", "generate-template": "cd setup/generate_template && yarn && node generate_template.js", "setup-scheduler": "cd setup/scheduler && ./setup_scheduler.sh", diff --git a/src/services/db.js b/src/services/db.js index e4d0a22..1d0c222 100644 --- a/src/services/db.js +++ b/src/services/db.js @@ -16,6 +16,7 @@ const dynamo = DynamoDBDocumentClient.from(client); // NOTE: these methods return promises const put = (tableId, item) => { + if (!item) return; return dynamo.send( new PutItemCommand({ TableName: tableId, @@ -25,33 +26,29 @@ const put = (tableId, item) => { }; const batchWrite = async function (tableId, items) { + if (!items) return; const marshalledItems = items.map((x) => { const marshalled = marshall(x); return Object.assign({ PutRequest: { Item: marshalled } }); }); - try { - const batches = []; + const batches = []; - while (marshalledItems.length) { - batches.push(marshalledItems.splice(0, 25)); - } + while (marshalledItems.length) { + batches.push(marshalledItems.splice(0, 25)); + } - return Promise.all( - batches.map(async (batch) => { - requestItems = {}; - requestItems[tableId] = batch; + return Promise.all( + batches.map(async (batch) => { + requestItems = {}; + requestItems[tableId] = batch; - const params = { - RequestItems: requestItems, - }; + const params = { + RequestItems: requestItems, + }; - dynamo.send(new BatchWriteItemCommand(params)); - }) - ); - } catch (error) { - console.log(error); - return error; - } + await dynamo.send(new BatchWriteItemCommand(params)); + }) + ); }; const scan = async (tableId) => { diff --git a/src/services/db.test.js b/src/services/db.test.js new file mode 100644 index 0000000..62cb728 --- /dev/null +++ b/src/services/db.test.js @@ -0,0 +1,103 @@ +const { mockClient } = require("aws-sdk-client-mock"); +const { + DynamoDBClient, + ScanCommand, + GetItemCommand, + BatchWriteItemCommand, + PutItemCommand, +} = require("@aws-sdk/client-dynamodb"); +const { db } = require("."); +const { DynamoDBDocumentClient } = require("@aws-sdk/lib-dynamodb"); + +const dbMock = mockClient(DynamoDBClient); +const documentMock = mockClient(DynamoDBDocumentClient); + +describe("db", () => { + beforeEach(() => { + jest.clearAllMocks; + }); + beforeEach(() => { + dbMock.reset(); + documentMock.reset(); + }); + + describe("scan", () => { + test("if no items are received, an empty array should be returned", async () => { + documentMock.on(ScanCommand).resolves({}); + const res = await db.scan("test"); + expect(res).toMatchObject([]); + }); + test("if items are returned, they should be passed back marshalled", async () => { + documentMock.on(ScanCommand).resolves({ + Items: [ + { + id: { S: "test" }, + }, + ], + }); + const res = await db.scan("test"); + expect(res).toHaveLength(1); + expect(res[0]).toHaveProperty("id"); + expect(res[0].id).toBe("test"); + }); + }); + + describe("getItem", () => { + test("if no items are received, null should be returned", async () => { + documentMock.on(GetItemCommand).resolves({}); + const res = await db.getItem("test"); + expect(res).toBeNull(); + }); + test("if an item is returned, it should be passed back marshalled", async () => { + documentMock.on(GetItemCommand).resolves({ + Item: { + id: { S: "test" }, + }, + }); + const res = await db.getItem("test"); + expect(res).toHaveProperty("id"); + expect(res.id).toBe("test"); + }); + }); + + describe("batchWrite", () => { + test("if no items is given, no commands should be issued", async () => { + await db.batchWrite("test"); + expect(documentMock.calls.length).toBe(0); + }); + test("if an empty array is given, no commands should be issued", async () => { + await db.batchWrite("test", []); + expect(documentMock.calls.length).toBe(0); + }); + test("if items are given, batchwrite should be called", async () => { + documentMock.on(BatchWriteItemCommand).resolves({}); + await db.batchWrite("test", [{ item: "test" }]); + expect(documentMock.send.called).toBeTruthy(); + }); + test("if write errors, the function should return the error", async () => { + documentMock.on(BatchWriteItemCommand).rejects("error"); + + const res = db.batchWrite("test", [{ item: "test" }]); + await expect(res).rejects.toEqual(new Error("error")); + }); + }); + + describe("put", () => { + test("if no item is given, no calls should be made", async () => { + await db.put("test"); + expect(documentMock.calls.length).toBe(0); + }); + test("if an item is given, put should be called", async () => { + documentMock.on(PutItemCommand).resolves({}); + await db.put("test", { item: "test" }); + expect(documentMock.send.called).toBeTruthy(); + }); + test("if write errors, the function should return the error", async () => { + documentMock.on(PutItemCommand).rejects("error"); + + await expect(db.put("test", { item: "test" })).rejects.toEqual( + new Error("error") + ); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 32fd5ce..0fa9ae1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -850,6 +850,14 @@ async@^1.5.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w== +aws-sdk-client-mock-jest@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/aws-sdk-client-mock-jest/-/aws-sdk-client-mock-jest-4.0.0.tgz#40a3bb441fcfc73f7f1a82a1b6c44a63a4a0b0fd" + integrity sha512-Q8WWWYpcEZK8m0OA42Lm2LaJgStAfqvmMYVtEs2Ibz+nwjZDZSK/xlsYbdsFz93RO9cUPXbQMeyKUXeqdjh49g== + dependencies: + expect ">28.1.3" + tslib "^2.1.0" + aws-sdk-client-mock@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/aws-sdk-client-mock/-/aws-sdk-client-mock-3.0.1.tgz#f9ecc50ff5190fbe7286155e65ea99ad80f670ff" @@ -1327,7 +1335,7 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0, expect@^29.7.0: +expect@>28.1.3, expect@^29.0.0, expect@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==