UNPKG

dynogels

Version:
1,882 lines (1,481 loc) 52.4 kB
'use strict'; const helper = require('./test-helper'); const _ = require('lodash'); const Joi = require('joi'); const Table = require('../lib/table'); const Schema = require('../lib/schema'); const Query = require('../lib//query'); const Scan = require('../lib//scan'); const Item = require('../lib/item'); const realSerializer = require('../lib/serializer'); const chai = require('chai'); const expect = chai.expect; const sinon = require('sinon'); chai.should(); describe('table', () => { let table; let serializer; let docClient; let dynamodb; let logger; beforeEach(() => { serializer = helper.mockSerializer(); docClient = helper.mockDocClient(); dynamodb = docClient.service; logger = helper.testLogger(); }); describe('#get', () => { it('should get item by hash key', done => { const config = { hashKey: 'email' }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com' } }; const resp = { Item: { email: 'test@test.com', name: 'test dude' } }; docClient.get.withArgs(request).yields(null, resp); table.get('test@test.com', (err, account) => { account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('name').should.equal('test dude'); done(); }); }); it('should get item by hash and range key', done => { const config = { hashKey: 'name', rangeKey: 'email' }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Key: { name: 'Tim Tester', email: 'test@test.com' } }; const resp = { Item: { email: 'test@test.com', name: 'Tim Tester' } }; docClient.get.withArgs(request).yields(null, resp); table.get('Tim Tester', 'test@test.com', (err, account) => { account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('name').should.equal('Tim Tester'); done(); }); }); it('should get item by hash key and options', done => { const config = { hashKey: 'email', }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com' }, ConsistentRead: true }; const resp = { Item: { email: 'test@test.com', name: 'test dude' } }; docClient.get.withArgs(request).yields(null, resp); table.get('test@test.com', { ConsistentRead: true }, (err, account) => { account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('name').should.equal('test dude'); done(); }); }); it('should get item by hashkey, range key and options', done => { const config = { hashKey: 'name', rangeKey: 'email', }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Key: { name: 'Tim Tester', email: 'test@test.com' }, ConsistentRead: true }; const resp = { Item: { email: 'test@test.com', name: 'Tim Tester' } }; docClient.get.withArgs(request).yields(null, resp); table.get('Tim Tester', 'test@test.com', { ConsistentRead: true }, (err, account) => { account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('name').should.equal('Tim Tester'); done(); }); }); it('should get item from dynamic table by hash key', done => { const config = { hashKey: 'email', tableName: function () { return 'accounts_2014'; } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts_2014', Key: { email: 'test@test.com' } }; const resp = { Item: { email: 'test@test.com', name: 'test dude' } }; docClient.get.withArgs(request).yields(null, resp); table.get('test@test.com', (err, account) => { account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('name').should.equal('test dude'); done(); }); }); it('should return error', done => { const config = { hashKey: 'email', }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); docClient.get.yields(new Error('Fail')); table.get('test@test.com', (err, account) => { expect(err).to.exist; expect(account).to.not.exist; done(); }); }); }); describe('#create', () => { it('should create valid item', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Item: { email: 'test@test.com', name: 'Tim Test', age: 23 } }; docClient.put.withArgs(request).yields(null, {}); table.create(request.Item, (err, account) => { expect(err).to.not.exist; account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('name').should.equal('Tim Test'); done(); }); }); it('should call apply defaults', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string().default('Foo'), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Item: { email: 'test@test.com', name: 'Foo', age: 23 } }; docClient.put.withArgs(request).yields(null, {}); table.create({ email: 'test@test.com', age: 23 }, (err, account) => { expect(err).to.not.exist; account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('name').should.equal('Foo'); done(); }); }); it('should omit null values', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), age: Joi.number().allow(null), favoriteNumbers: Schema.types.numberSet().allow(null), luckyNumbers: Schema.types.numberSet().allow(null) } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const numberSet = sinon.match(value => { const s = docClient.createSet([1, 2, 3]); value.type.should.eql('Number'); value.values.should.eql(s.values); return true; }, 'NumberSet'); const request = { TableName: 'accounts', Item: { email: 'test@test.com', name: 'Tim Test', luckyNumbers: numberSet } }; docClient.put.withArgs(request).yields(null, {}); const item = { email: 'test@test.com', name: 'Tim Test', age: null, favoriteNumbers: [], luckyNumbers: [1, 2, 3] }; table.create(item, (err, account) => { account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('name').should.equal('Tim Test'); account.get('luckyNumbers').should.eql([1, 2, 3]); expect(account.toJSON()).to.have.keys(['email', 'name', 'luckyNumbers']); done(); }); }); it('should omit empty values', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string().allow(''), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Item: { email: 'test@test.com', age: 2 } }; docClient.put.withArgs(request).yields(null, {}); table.create({ email: 'test@test.com', name: '', age: 2 }, (err, account) => { expect(err).to.not.exist; account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('age').should.equal(2); done(); }); }); it('should create item with createdAt timestamp', done => { const config = { hashKey: 'email', timestamps: true, schema: { email: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Item: { email: 'test@test.com', createdAt: sinon.match.string } }; docClient.put.withArgs(request).yields(null, {}); table.create({ email: 'test@test.com' }, (err, account) => { expect(err).to.not.exist; account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('createdAt').should.exist; done(); }); }); it('should create item with custom createdAt attribute name', done => { const config = { hashKey: 'email', timestamps: true, createdAt: 'created', schema: { email: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Item: { email: 'test@test.com', created: sinon.match.string } }; docClient.put.withArgs(request).yields(null, {}); table.create({ email: 'test@test.com' }, (err, account) => { expect(err).to.not.exist; account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('created').should.exist; done(); }); }); it('should create item without createdAt param', done => { const config = { hashKey: 'email', timestamps: true, createdAt: false, schema: { email: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Item: { email: 'test@test.com' } }; docClient.put.withArgs(request).yields(null, {}); table.create({ email: 'test@test.com' }, (err, account) => { expect(err).to.not.exist; account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); expect(account.get('createdAt')).to.not.exist; done(); }); }); it('should create item with expected option', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string() } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Item: { email: 'test@test.com', }, ExpressionAttributeNames: { '#name': 'name' }, ExpressionAttributeValues: { ':name': 'Foo Bar' }, ConditionExpression: '(#name = :name)' }; docClient.put.withArgs(request).yields(null, {}); table.create({ email: 'test@test.com' }, { expected: { name: 'Foo Bar' } }, (err, account) => { expect(err).to.not.exist; account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); done(); }); }); it('should create item with no callback', done => { const config = { hashKey: 'email', timestamps: true, schema: { email: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Item: { email: 'test@test.com', } }; docClient.put.withArgs(request).yields(null, {}); table.create({ email: 'test@test.com' }); docClient.put.calledWith(request); return done(); }); it('should return validation error', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string() } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); table.create({ email: 'test@test.com', name: [1, 2, 3] }, (err, account) => { expect(err).to.exist; expect(err).to.match(/ValidationError/); expect(account).to.not.exist; sinon.assert.notCalled(docClient.put); done(); }); }); it('should create item with condition expression on hashkey when overwrite flag is false', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string() } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Item: { email: 'test@test.com', name: 'Bob Tester' }, ExpressionAttributeNames: { '#email': 'email' }, ExpressionAttributeValues: { ':email': 'test@test.com' }, ConditionExpression: '(#email <> :email)' }; docClient.put.withArgs(request).yields(null, {}); table.create({ email: 'test@test.com', name: 'Bob Tester' }, { overwrite: false }, (err, account) => { expect(err).to.not.exist; account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); done(); }); }); it('should create item with condition expression on hash and range key when overwrite flag is false', done => { const config = { hashKey: 'email', rangeKey: 'name', schema: { email: Joi.string(), name: Joi.string() } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Item: { email: 'test@test.com', name: 'Bob Tester' }, ExpressionAttributeNames: { '#email': 'email', '#name': 'name' }, ExpressionAttributeValues: { ':email': 'test@test.com', ':name': 'Bob Tester' }, ConditionExpression: '(#email <> :email) AND (#name <> :name)' }; docClient.put.withArgs(request).yields(null, {}); table.create({ email: 'test@test.com', name: 'Bob Tester' }, { overwrite: false }, (err, account) => { expect(err).to.not.exist; account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); done(); }); }); it('should create item without condition expression when overwrite flag is true', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string() } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Item: { email: 'test@test.com', name: 'Bob Tester' } }; docClient.put.withArgs(request).yields(null, {}); table.create({ email: 'test@test.com', name: 'Bob Tester' }, { overwrite: true }, (err, account) => { expect(err).to.not.exist; account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); done(); }); }); }); describe('#update', () => { it('should update valid item', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), age: Joi.number(), } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com' }, ReturnValues: 'ALL_NEW', UpdateExpression: 'SET #name = :name, #age = :age', ExpressionAttributeValues: { ':name': 'Tim Test', ':age': 23 }, ExpressionAttributeNames: { '#name': 'name', '#age': 'age' } }; const returnedAttributes = { email: 'test@test.com', name: 'Tim Test', age: 23, scores: [97, 86] }; docClient.update.withArgs(request).yields(null, { Attributes: returnedAttributes }); const item = { email: 'test@test.com', name: 'Tim Test', age: 23 }; table.update(item, (err, account) => { account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('name').should.equal('Tim Test'); account.get('age').should.equal(23); account.get('scores').should.eql([97, 86]); done(); }); }); it('should accept falsy key and range values', done => { const config = { hashKey: 'userId', rangeKey: 'timeOffset', schema: { userId: Joi.number(), timeOffset: Joi.number() } }; const s = new Schema(config); table = new Table('users', s, realSerializer, docClient, logger); const request = { TableName: 'users', Key: { userId: 0, timeOffset: 0 }, ReturnValues: 'ALL_NEW' }; const returnedAttributes = { userId: 0, timeOffset: 0 }; docClient.update.withArgs(request).yields(null, { Attributes: returnedAttributes }); const item = { userId: 0, timeOffset: 0 }; table.update(item, (err, user) => { expect(err).to.be.null; user.should.be.instanceof(Item); user.get('userId').should.equal(0); user.get('timeOffset').should.equal(0); done(); }); }); it('should update with passed in options', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), age: Joi.number(), } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com' }, ReturnValues: 'ALL_OLD', UpdateExpression: 'SET #name = :name, #age = :age', ExpressionAttributeValues: { ':name_2': 'Foo Bar', ':name': 'Tim Test', ':age': 23 }, ExpressionAttributeNames: { '#name': 'name', '#age': 'age' }, ConditionExpression: '(#name = :name_2)' }; const returnedAttributes = { email: 'test@test.com', name: 'Tim Test', age: 23, scores: [97, 86] }; const item = { email: 'test@test.com', name: 'Tim Test', age: 23 }; docClient.update.withArgs(request).yields(null, { Attributes: returnedAttributes }); const getOptions = function () { return { ReturnValues: 'ALL_OLD', expected: { name: 'Foo Bar' } }; }; const passedOptions = getOptions(); table.update(item, passedOptions, (err, account) => { account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('name').should.equal('Tim Test'); account.get('age').should.equal(23); account.get('scores').should.eql([97, 86]); expect(passedOptions).to.deep.equal(getOptions()); done(); }); }); it('should update merge update expressions when passed in as options', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), age: Joi.number(), } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com' }, ReturnValues: 'ALL_NEW', UpdateExpression: 'SET #name = :name, #age = :age ADD #color :c', ExpressionAttributeValues: { ':name': 'Tim Test', ':age': 23, ':c': 'red' }, ExpressionAttributeNames: { '#name': 'name', '#age': 'age', '#color': 'color' } }; const returnedAttributes = { email: 'test@test.com', name: 'Tim Test', age: 23, scores: [97, 86], color: 'red' }; const item = { email: 'test@test.com', name: 'Tim Test', age: 23 }; docClient.update.withArgs(request).yields(null, { Attributes: returnedAttributes }); const options = { UpdateExpression: 'ADD #color :c', ExpressionAttributeValues: { ':c': 'red' }, ExpressionAttributeNames: { '#color': 'color' } }; table.update(item, options, (err, account) => { account.should.be.instanceof(Item); account.get('email').should.equal('test@test.com'); account.get('name').should.equal('Tim Test'); account.get('age').should.equal(23); account.get('scores').should.eql([97, 86]); account.get('color').should.eql('red'); done(); }); }); it('should update valid item without a callback', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), age: Joi.number(), } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com' }, ReturnValues: 'ALL_NEW', UpdateExpression: 'SET #name = :name, #age = :age', ExpressionAttributeValues: { ':name': 'Tim Test', ':age': 23 }, ExpressionAttributeNames: { '#name': 'name', '#age': 'age' } }; const returnedAttributes = { email: 'test@test.com', name: 'Tim Test', age: 23, scores: [97, 86] }; docClient.update.withArgs(request).yields(null, { Attributes: returnedAttributes }); const item = { email: 'test@test.com', name: 'Tim Test', age: 23 }; table.update(item); docClient.update.calledWith(request); return done(); }); it('should return error', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), age: Joi.number(), } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); docClient.update.yields(new Error('Fail')); const item = { email: 'test@test.com', name: 'Tim Test', age: 23 }; table.update(item, (err, account) => { expect(err).to.exist; expect(account).to.not.exist; done(); }); }); it('should handle errors regarding invalid expressions', (done) => { const config = { hashKey: 'name', schema: { name: Joi.string(), birthday: Joi.date().iso() } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const item = { name: 'Dr. Who', birthday: undefined }; table.update(item, (err, account) => { expect(err).to.exist; expect(account).to.not.exist; done(); }); }); }); describe('#query', () => { it('should return query object', () => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); table.query('Bob').should.be.instanceof(Query); }); }); describe('#scan', () => { it('should return scan object', () => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); table.scan().should.be.instanceof(Scan); }); }); describe('#destroy', () => { it('should destroy valid item', done => { const config = { hashKey: 'email', schema: { name: Joi.string(), email: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com' } }; docClient.delete.yields(null, {}); serializer.buildKey.returns(request.Key); table.destroy('test@test.com', () => { serializer.buildKey.calledWith('test@test.com', null, s).should.be.true; docClient.delete.calledWith(request).should.be.true; done(); }); }); it('should destroy valid item with falsy hash and range keys', done => { const config = { hashKey: 'userId', rangeKey: 'timeOffset', schema: { hashKey: Joi.number(), rangeKey: Joi.number() } }; const s = new Schema(config); table = new Table('users', s, serializer, docClient, logger); const request = { TableName: 'users', Key: { userId: 0, timeOffset: 0 } }; docClient.delete.yields(null, {}); serializer.buildKey.returns(request.Key); table.destroy({ userId: 0, timeOffset: 0 }, () => { serializer.buildKey.calledWith(0, 0, s).should.be.true; docClient.delete.calledWith(request).should.be.true; done(); }); }); it('should take optional params', done => { const config = { hashKey: 'email', schema: { name: Joi.string(), email: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: { S: 'test@test.com' } }, ReturnValues: 'ALL_OLD' }; docClient.delete.yields(null, {}); serializer.buildKey.returns(request.Key); table.destroy('test@test.com', { ReturnValues: 'ALL_OLD' }, () => { serializer.buildKey.calledWith('test@test.com', null, s).should.be.true; docClient.delete.calledWith(request).should.be.true; done(); }); }); it('should parse and return attributes', done => { const config = { hashKey: 'email', schema: { name: Joi.string(), email: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com' }, ReturnValues: 'ALL_OLD' }; const returnedAttributes = { email: 'test@test.com', name: 'Foo Bar' }; docClient.delete.yields(null, { Attributes: returnedAttributes }); serializer.buildKey.returns(request.Key); serializer.deserializeItem.withArgs(returnedAttributes).returns( { email: 'test@test.com', name: 'Foo Bar' }); table.destroy('test@test.com', { ReturnValues: 'ALL_OLD' }, (err, item) => { serializer.buildKey.calledWith('test@test.com', null, s).should.be.true; docClient.delete.calledWith(request).should.be.true; item.get('name').should.equal('Foo Bar'); done(); }); }); it('should accept hash and range key', done => { const config = { hashKey: 'email', rangeKey: 'name', schema: { name: Joi.string(), email: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com', name: 'Foo Bar' } }; const returnedAttributes = { email: 'test@test.com', name: 'Foo Bar' }; docClient.delete.yields(null, { Attributes: returnedAttributes }); serializer.buildKey.returns(request.Key); serializer.deserializeItem.withArgs(returnedAttributes).returns( { email: 'test@test.com', name: 'Foo Bar' }); table.destroy('test@test.com', 'Foo Bar', (err, item) => { serializer.buildKey.calledWith('test@test.com', 'Foo Bar', s).should.be.true; docClient.delete.calledWith(request).should.be.true; item.get('name').should.equal('Foo Bar'); done(); }); }); it('should accept hashkey rangekey and options', done => { const config = { hashKey: 'email', rangeKey: 'name', schema: { name: Joi.string(), email: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com', name: 'Foo Bar' }, ReturnValues: 'ALL_OLD' }; const returnedAttributes = { email: 'test@test.com', name: 'Foo Bar' }; docClient.delete.yields(null, { Attributes: returnedAttributes }); serializer.buildKey.returns(request.Key); serializer.deserializeItem.withArgs(returnedAttributes).returns( { email: 'test@test.com', name: 'Foo Bar' }); table.destroy('test@test.com', 'Foo Bar', { ReturnValues: 'ALL_OLD' }, (err, item) => { serializer.buildKey.calledWith('test@test.com', 'Foo Bar', s).should.be.true; docClient.delete.calledWith(request).should.be.true; item.get('name').should.equal('Foo Bar'); done(); }); }); it('should serialize expected option', done => { const config = { hashKey: 'email', schema: { name: Joi.string(), email: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com' }, ExpressionAttributeNames: { '#name': 'name' }, ExpressionAttributeValues: { ':name': 'Foo Bar' }, ConditionExpression: '(#name = :name)' }; docClient.delete.yields(null, {}); serializer.serializeItem.withArgs(s, { name: 'Foo Bar' }, { expected: true }).returns(request.Expected); serializer.buildKey.returns(request.Key); table.destroy('test@test.com', { expected: { name: 'Foo Bar' } }, () => { serializer.buildKey.calledWith('test@test.com', null, s).should.be.true; docClient.delete.calledWith(request).should.be.true; done(); }); }); it('should call delete item without callback', done => { const config = { hashKey: 'email', schema: { name: Joi.string(), email: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com' } }; docClient.delete.yields(null, {}); table.destroy('test@test.com'); docClient.delete.calledWith(request); return done(); }); it('should call delete item with hash key, options and no callback', done => { const config = { hashKey: 'email', schema: { name: Joi.string(), email: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, realSerializer, docClient, logger); const request = { TableName: 'accounts', Key: { email: 'test@test.com' }, Expected: { name: { Value: 'Foo Bar' } } }; docClient.delete.yields(null, {}); table.destroy('test@test.com', { expected: { name: 'Foo Bar' } }); docClient.delete.calledWith(request); return done(); }); }); describe('#createTable', () => { it('should create table with hash key', done => { const config = { hashKey: 'email', schema: { name: Joi.string(), email: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const request = { TableName: 'accounts', AttributeDefinitions: [ { AttributeName: 'email', AttributeType: 'S' } ], KeySchema: [ { AttributeName: 'email', KeyType: 'HASH' } ], ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 } }; dynamodb.createTable.yields(null, {}); table.createTable({ readCapacity: 5, writeCapacity: 5 }, err => { expect(err).to.be.null; dynamodb.createTable.calledWith(request).should.be.true; done(); }); }); it('should create table with range key', done => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const request = { TableName: 'accounts', AttributeDefinitions: [ { AttributeName: 'name', AttributeType: 'S' }, { AttributeName: 'email', AttributeType: 'S' } ], KeySchema: [ { AttributeName: 'name', KeyType: 'HASH' }, { AttributeName: 'email', KeyType: 'RANGE' } ], ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 } }; dynamodb.createTable.yields(null, {}); table.createTable({ readCapacity: 5, writeCapacity: 5 }, err => { expect(err).to.be.null; dynamodb.createTable.calledWith(request).should.be.true; done(); }); }); it('should create table with stream specification', done => { const config = { hashKey: 'name', schema: { name: Joi.string(), email: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const request = { TableName: 'accounts', AttributeDefinitions: [ { AttributeName: 'name', AttributeType: 'S' } ], KeySchema: [ { AttributeName: 'name', KeyType: 'HASH' } ], ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 }, StreamSpecification: { StreamEnabled: true, StreamViewType: 'NEW_IMAGE' } }; dynamodb.createTable.yields(null, {}); table.createTable({ readCapacity: 5, writeCapacity: 5, streamSpecification: { streamEnabled: true, streamViewType: 'NEW_IMAGE' } }, err => { expect(err).to.be.null; dynamodb.createTable.calledWith(request).should.be.true; done(); }); }); it('should create table with secondary index', done => { const config = { hashKey: 'name', rangeKey: 'email', indexes: [ { hashKey: 'name', rangeKey: 'age', name: 'ageIndex', type: 'local' } ], schema: { name: Joi.string(), email: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const request = { TableName: 'accounts', AttributeDefinitions: [ { AttributeName: 'name', AttributeType: 'S' }, { AttributeName: 'email', AttributeType: 'S' }, { AttributeName: 'age', AttributeType: 'N' } ], KeySchema: [ { AttributeName: 'name', KeyType: 'HASH' }, { AttributeName: 'email', KeyType: 'RANGE' } ], LocalSecondaryIndexes: [ { IndexName: 'ageIndex', KeySchema: [ { AttributeName: 'name', KeyType: 'HASH' }, { AttributeName: 'age', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' } } ], ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 } }; dynamodb.createTable.yields(null, {}); table.createTable({ readCapacity: 5, writeCapacity: 5 }, err => { expect(err).to.be.null; dynamodb.createTable.calledWith(request).should.be.true; done(); }); }); it('should create table with global secondary index', done => { const config = { hashKey: 'userId', rangeKey: 'gameTitle', indexes: [ { hashKey: 'gameTitle', rangeKey: 'topScore', name: 'GameTitleIndex', type: 'global' } ], schema: { userId: Joi.string(), gameTitle: Joi.string(), topScore: Joi.number() } }; const s = new Schema(config); table = new Table('gameScores', s, serializer, docClient, logger); const request = { TableName: 'gameScores', AttributeDefinitions: [ { AttributeName: 'userId', AttributeType: 'S' }, { AttributeName: 'gameTitle', AttributeType: 'S' }, { AttributeName: 'topScore', AttributeType: 'N' } ], KeySchema: [ { AttributeName: 'userId', KeyType: 'HASH' }, { AttributeName: 'gameTitle', KeyType: 'RANGE' } ], GlobalSecondaryIndexes: [ { IndexName: 'GameTitleIndex', KeySchema: [ { AttributeName: 'gameTitle', KeyType: 'HASH' }, { AttributeName: 'topScore', KeyType: 'RANGE' } ], Projection: { ProjectionType: 'ALL' }, ProvisionedThroughput: { ReadCapacityUnits: 1, WriteCapacityUnits: 1 } } ], ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 } }; dynamodb.createTable.yields(null, {}); table.createTable({ readCapacity: 5, writeCapacity: 5 }, err => { expect(err).to.be.null; dynamodb.createTable.calledWith(request).should.be.true; done(); }); }); it('should create table with global secondary index', done => { const config = { hashKey: 'userId', rangeKey: 'gameTitle', indexes: [{ hashKey: 'gameTitle', rangeKey: 'topScore', name: 'GameTitleIndex', type: 'global', readCapacity: 10, writeCapacity: 5, projection: { NonKeyAttributes: ['wins'], ProjectionType: 'INCLUDE' } }], schema: { userId: Joi.string(), gameTitle: Joi.string(), topScore: Joi.number() } }; const s = new Schema(config); table = new Table('gameScores', s, serializer, docClient, logger); const request = { TableName: 'gameScores', AttributeDefinitions: [ { AttributeName: 'userId', AttributeType: 'S' }, { AttributeName: 'gameTitle', AttributeType: 'S' }, { AttributeName: 'topScore', AttributeType: 'N' } ], KeySchema: [ { AttributeName: 'userId', KeyType: 'HASH' }, { AttributeName: 'gameTitle', KeyType: 'RANGE' } ], GlobalSecondaryIndexes: [ { IndexName: 'GameTitleIndex', KeySchema: [ { AttributeName: 'gameTitle', KeyType: 'HASH' }, { AttributeName: 'topScore', KeyType: 'RANGE' } ], Projection: { NonKeyAttributes: ['wins'], ProjectionType: 'INCLUDE' }, ProvisionedThroughput: { ReadCapacityUnits: 10, WriteCapacityUnits: 5 } } ], ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 } }; dynamodb.createTable.yields(null, {}); table.createTable({ readCapacity: 5, writeCapacity: 5 }, err => { expect(err).to.be.null; dynamodb.createTable.calledWith(request).should.be.true; done(); }); }); }); describe('#describeTable', () => { it('should make describe table request', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const request = { TableName: 'accounts' }; dynamodb.describeTable.yields(null, {}); table.describeTable(err => { expect(err).to.be.null; dynamodb.describeTable.calledWith(request).should.be.true; done(); }); }); }); describe('#updateTable', () => { beforeEach(() => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); }); it('should make update table request', done => { const request = { TableName: 'accounts', ProvisionedThroughput: { ReadCapacityUnits: 4, WriteCapacityUnits: 2 } }; dynamodb.describeTable.yields(null, {}); dynamodb.updateTable.yields(null, {}); table.updateTable({ readCapacity: 4, writeCapacity: 2 }, err => { expect(err).to.be.null; dynamodb.updateTable.calledWith(request).should.be.true; done(); }); }); it('should make update table request without callback', done => { const request = { TableName: 'accounts', ProvisionedThroughput: { ReadCapacityUnits: 2, WriteCapacityUnits: 1 } }; table.updateTable({ readCapacity: 2, writeCapacity: 1 }); dynamodb.updateTable.calledWith(request).should.be.true; return done(); }); }); describe('#deleteTable', () => { beforeEach(() => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); }); it('should make delete table request', done => { const request = { TableName: 'accounts' }; dynamodb.deleteTable.yields(null, {}); table.deleteTable(err => { expect(err).to.be.null; dynamodb.deleteTable.calledWith(request).should.be.true; done(); }); }); it('should make delete table request without callback', done => { const request = { TableName: 'accounts', }; table.deleteTable(); dynamodb.deleteTable.calledWith(request).should.be.true; return done(); }); }); describe('#tableName', () => { it('should return given name', () => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); table.tableName().should.eql('accounts'); }); it('should return table name set on schema', () => { const config = { hashKey: 'email', tableName: 'accounts-2014-03', schema: { email: Joi.string(), name: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); table.tableName().should.eql('accounts-2014-03'); }); it('should return table name returned from function on schema', () => { const d = new Date(); const dateString = [d.getFullYear(), d.getMonth() + 1].join('_'); const nameFunc = () => `accounts_${dateString}`; const config = { hashKey: 'email', tableName: nameFunc, schema: { email: Joi.string(), name: Joi.string(), } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); table.tableName().should.eql(`accounts_${dateString}`); }); }); describe('hooks', () => { describe('#create', () => { it('should call before hooks', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const item = { email: 'test@test.com', name: 'Tim Test', age: 23 }; docClient.put.yields(null, {}); serializer.serializeItem.withArgs(s, { email: 'test@test.com', name: 'Tommy', age: 23 }).returns({}); table.before('create', (data, next) => { expect(data).to.exist; data.name = 'Tommy'; return next(null, data); }); table.before('create', (data, next) => { expect(data).to.exist; data.age = '25'; return next(null, data); }); table.create(item, (err, item) => { expect(err).to.not.exist; item.get('name').should.equal('Tommy'); item.get('age').should.equal('25'); return done(); }); }); it('should return error when before hook returns error', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); table.before('create', (data, next) => next(new Error('fail'))); table.create({ email: 'foo@bar.com' }, (err, item) => { expect(err).to.exist; expect(item).to.not.exist; return done(); }); }); it('should call after hook', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const item = { email: 'test@test.com', name: 'Tim Test', age: 23 }; docClient.put.yields(null, {}); serializer.serializeItem.withArgs(s, item).returns({}); table.after('create', data => { expect(data).to.exist; return done(); }); table.create(item, () => {}); }); }); describe('#update', () => { it('should call before hook', done => { const config = { hashKey: 'email', schema: { email: Joi.string(), name: Joi.string(), age: Joi.number() } }; const s = new Schema(config); table = new Table('accounts', s, serializer, docClient, logger); const item = { email: 'test@test.com', name: 'Tim Test', age: 23 }; docClient.update.yields(null, {}); serializer.serializeItem.withArgs(s, item).returns({}); serializer.buildKey.returns({ email: { S: 'test@test.com' } }); const modified = { email: 'test@test.com', name: 'Tim Test', age: 44 }; serializer.serializeItemForUpdate.withArgs(s, 'PUT', modified).returns({}); serializer.deserializeI