Skip to content
Closed
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
10 changes: 9 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/spacecat-shared-dynamo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
},
"dependencies": {
"@aws-sdk/client-dynamodb": "3.454.0",
"@aws-sdk/lib-dynamodb": "3.454.0"
"@aws-sdk/lib-dynamodb": "3.454.0",
"@adobe/spacecat-shared-utils": "1.0.1"
},
"devDependencies": {
"chai": "4.3.10"
Expand Down
11 changes: 4 additions & 7 deletions packages/spacecat-shared-dynamo/src/modules/getItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import { performance } from 'perf_hooks';

import { guardKey, guardTableName } from '../utils/guards.js';

/**
* Retrieves an item from DynamoDB using a table name and key object.
*
Expand All @@ -23,13 +25,8 @@ import { performance } from 'perf_hooks';
* @throws {Error} Throws an error if the DynamoDB get operation fails or input validation fails.
*/
async function getItem(docClient, tableName, key, log = console) {
if (!tableName || typeof tableName !== 'string') {
throw new Error('Invalid tableName: must be a non-empty string.');
}

if (!key || typeof key !== 'object' || !key.partitionKey) {
throw new Error('Invalid key: must be an object with a partitionKey.');
}
guardTableName(tableName);
guardKey(key);

const params = {
TableName: tableName,
Expand Down
6 changes: 3 additions & 3 deletions packages/spacecat-shared-dynamo/src/modules/putItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import { performance } from 'perf_hooks';

import { guardTableName } from '../utils/guards.js';

/**
* Inserts or updates an item in a DynamoDB table.
*
Expand All @@ -23,9 +25,7 @@ import { performance } from 'perf_hooks';
* @throws {Error} Throws an error if the DynamoDB put operation fails.
*/
async function putItem(docClient, tableName, item, log = console) {
if (!tableName || typeof tableName !== 'string') {
throw new Error('Invalid tableName: must be a non-empty string.');
}
guardTableName(tableName);

const params = {
TableName: tableName,
Expand Down
3 changes: 3 additions & 0 deletions packages/spacecat-shared-dynamo/src/modules/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import { performance } from 'perf_hooks';
import { guardQueryParameters } from '../utils/guards.js';

/**
* Queries DynamoDB and automatically handles pagination to retrieve all items.
Expand All @@ -22,6 +23,8 @@ import { performance } from 'perf_hooks';
* @throws {Error} Throws an error if the DynamoDB query operation fails.
*/
async function query(docClient, originalParams, log = console) {
guardQueryParameters(originalParams);

let items = [];
const params = { ...originalParams };

Expand Down
11 changes: 4 additions & 7 deletions packages/spacecat-shared-dynamo/src/modules/removeItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import { performance } from 'perf_hooks';

import { guardKey, guardTableName } from '../utils/guards.js';

/**
* Removes an item from a DynamoDB table.
*
Expand All @@ -23,13 +25,8 @@ import { performance } from 'perf_hooks';
* @throws {Error} Throws an error if the DynamoDB delete operation fails or input validation fails.
*/
async function removeItem(docClient, tableName, key, log = console) {
if (!tableName || typeof tableName !== 'string') {
throw new Error('Invalid tableName: must be a non-empty string.');
}

if (!key || typeof key !== 'object' || !key.partitionKey) {
throw new Error('Invalid key: must be an object with a partitionKey.');
}
guardTableName(tableName);
guardKey(key);

const params = {
TableName: tableName,
Expand Down
44 changes: 44 additions & 0 deletions packages/spacecat-shared-dynamo/src/utils/guards.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2023 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { hasText, isObject } from '@adobe/spacecat-shared-utils';

const guardTableName = (tableName) => {
if (!hasText(tableName)) {
throw new Error('Table name is required.');
}
};

const guardKey = (key) => {
if (!isObject(key) || !key.partitionKey) {
throw new Error('Key must be an object with a partitionKey.');
}
};

const guardQueryParameters = (params) => {
if (!isObject(params)) {
throw new Error('Query parameters must be an object.');
}

const requiredProps = ['TableName', 'KeyConditionExpression', 'ExpressionAttributeValues'];
for (const prop of requiredProps) {
if (!Object.prototype.hasOwnProperty.call(params, prop)) {
throw new Error(`Query parameters is missing required parameter: ${prop}`);
}
}
};

export {
guardKey,
guardQueryParameters,
guardTableName,
};
4 changes: 2 additions & 2 deletions packages/spacecat-shared-dynamo/test/modules/getItem.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('getItem', () => {
await dynamoDbClient.getItem('', key);
expect.fail('getItem did not throw with empty tableName');
} catch (error) {
expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
expect(error.message).to.equal('Table name is required.');
}
});

Expand All @@ -54,7 +54,7 @@ describe('getItem', () => {
await dynamoDbClient.getItem('TestTable', null);
expect.fail('getItem did not throw with invalid key');
} catch (error) {
expect(error.message).to.equal('Invalid key: must be an object with a partitionKey.');
expect(error.message).to.equal('Key must be an object with a partitionKey.');
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('putItem', () => {
await dynamoDbClient.putItem('', { someKey: 'someValue' });
expect.fail('putItem did not throw with empty tableName');
} catch (error) {
expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
expect(error.message).to.equal('Table name is required.');
}
});

Expand Down
14 changes: 11 additions & 3 deletions packages/spacecat-shared-dynamo/test/modules/query.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ import { expect } from 'chai';
import { createClient } from '../../src/index.js';

describe('query', () => {
const queryParams = {
TableName: 'TestTable',
KeyConditionExpression: 'partitionKey = :partitionKey',
ExpressionAttributeValues: {
':partitionKey': 'testPartitionKey',
},
};

let dynamoDbClient;
let mockDocClient;

Expand All @@ -35,12 +43,12 @@ describe('query', () => {
});

it('queries items from the database', async () => {
const result = await dynamoDbClient.query({ TableName: 'TestTable' });
const result = await dynamoDbClient.query(queryParams);
expect(result).to.be.an('array');
});

it('queries items from the database with pagination', async () => {
const result = await dynamoDbClient.query({ TableName: 'TestTable' });
const result = await dynamoDbClient.query(queryParams);
expect(result).to.have.lengthOf(3);
expect(result).to.deep.equal(['item1', 'item2', 'item3']);
});
Expand All @@ -51,7 +59,7 @@ describe('query', () => {
};

try {
await dynamoDbClient.query({ TableName: 'TestTable' });
await dynamoDbClient.query(queryParams);
expect.fail('queryDb did not throw as expected');
} catch (error) {
expect(error.message).to.equal('Query failed');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('removeItem', () => {
await dynamoDbClient.removeItem('', key);
expect.fail('removeItem did not throw with empty tableName');
} catch (error) {
expect(error.message).to.equal('Invalid tableName: must be a non-empty string.');
expect(error.message).to.equal('Table name is required.');
}
});

Expand All @@ -54,7 +54,7 @@ describe('removeItem', () => {
await dynamoDbClient.removeItem('TestTable', null);
expect.fail('removeItem did not throw with invalid key');
} catch (error) {
expect(error.message).to.equal('Invalid key: must be an object with a partitionKey.');
expect(error.message).to.equal('Key must be an object with a partitionKey.');
}
});

Expand Down
63 changes: 63 additions & 0 deletions packages/spacecat-shared-dynamo/test/utils/guards.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2023 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/* eslint-env mocha */

import { expect } from 'chai';
import { guardKey, guardQueryParameters, guardTableName } from '../../src/utils/guards.js';

describe('Query Parameter Guards', () => {
// Test guardTableName
describe('guardTableName', () => {
it('should throw an error if tableName is empty', () => {
expect(() => guardTableName('')).to.throw('Table name is required.');
});

it('should not throw an error for valid tableName', () => {
expect(() => guardTableName('validTableName')).to.not.throw();
});
});

// Test guardKey
describe('guardKey', () => {
it('should throw an error if key is not an object', () => {
expect(() => guardKey('notAnObject')).to.throw('Key must be an object with a partitionKey.');
});

it('should throw an error if key does not have partitionKey', () => {
expect(() => guardKey({})).to.throw('Key must be an object with a partitionKey.');
});

it('should not throw an error for a valid key', () => {
expect(() => guardKey({ partitionKey: 'value' })).to.not.throw();
});
});

describe('guardQueryParameters', () => {
it('should throw an error if params is not an object', () => {
expect(() => guardQueryParameters('notAnObject')).to.throw('Query parameters must be an object.');
});

it('should throw an error if any required parameter is missing', () => {
expect(() => guardQueryParameters({ TableName: 'table' })).to.throw('Query parameters is missing required parameter: KeyConditionExpression');
});

it('should not throw an error for valid params', () => {
const validParams = {
TableName: 'table',
KeyConditionExpression: 'expression',
ExpressionAttributeValues: {},
};
expect(() => guardQueryParameters(validParams)).to.not.throw();
});
});
});