UNPKG

dynogels

Version:
638 lines (505 loc) 19.8 kB
'use strict'; const helper = require('./test-helper'); const Schema = require('../lib/schema'); const Query = require('../lib//query'); const Serializer = require('../lib/serializer'); const Table = require('../lib/table'); const chai = require('chai'); const expect = chai.expect; const assert = require('assert'); const Joi = require('joi'); chai.should(); describe('Query', () => { let serializer; let table; beforeEach(() => { serializer = helper.mockSerializer(); table = helper.mockTable(); table.config = { name: 'accounts' }; table.docClient = helper.mockDocClient(); }); describe('#exec', () => { it('should run query against table', done => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string() } }; table.schema = new Schema(config); table.runQuery.yields(null, {}); serializer.serializeItem.returns({ name: { S: 'tim' } }); new Query('tim', table, serializer).exec((err, results) => { results.should.eql({ Items: [], Count: 0 }); done(); }); }); it('should return error', done => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string() } }; const s = new Schema(config); const t = new Table('accounts', s, Serializer, helper.mockDocClient(), helper.testLogger()); t.docClient.query.yields(new Error('Fail')); new Query('tim', t, Serializer).exec((err, results) => { expect(err).to.exist; expect(results).to.not.exist; done(); }); }); it('should stream error', done => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string() } }; const s = new Schema(config); const t = new Table('accounts', s, Serializer, helper.mockDocClient(), helper.testLogger()); t.docClient.query.yields(new Error('Fail')); const stream = new Query('tim', t, Serializer).exec(); stream.on('error', err => { expect(err).to.exist; return done(); }); stream.on('readable', () => { assert(false, 'readable should not be called'); }); }); it('should stream data after handling retryable error', done => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string() } }; const s = new Schema(config); const t = new Table('accounts', s, Serializer, helper.mockDocClient(), helper.testLogger()); const err = new Error('RetryableException'); err.retryable = true; t.docClient.query .onCall(0).yields(err) .onCall(1).yields(null, { Items: [{ name: 'Tim Tester', email: 'test@test.com' }] }); const stream = new Query('tim', t, Serializer).exec(); let called = false; stream.on('readable', () => { called = true; const data = stream.read(); if (data) { expect(data.Items).to.have.length.above(0); } }); stream.on('end', () => { expect(called).to.be.true; return done(); }); }); }); describe('#limit', () => { beforeEach(() => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string() } }; table.schema = new Schema(config); }); it('should set the limit', () => { const query = new Query('tim', table, serializer).limit(10); query.request.Limit.should.equal(10); }); it('should throw when limit is zero', () => { const query = new Query('tim', table, serializer); expect(() => { query.limit(0); }).to.throw('Limit must be greater than 0'); }); }); describe('#filterExpression', () => { it('should set filter expression', () => { const config = { hashKey: 'name', schema: { name: Joi.string(), } }; table.schema = new Schema(config); const query = new Query('tim', table, serializer).filterExpression('Postedby = :val'); query.request.FilterExpression.should.equal('Postedby = :val'); }); }); describe('#expressionAttributeValues', () => { it('should set expression attribute values', () => { const config = { hashKey: 'name', schema: { name: Joi.string(), } }; table.schema = new Schema(config); const query = new Query('tim', table, serializer).expressionAttributeValues({ ':val': 'test' }); query.request.ExpressionAttributeValues.should.eql({ ':val': 'test' }); }); }); describe('#expressionAttributeNames', () => { it('should set expression attribute names', () => { const config = { hashKey: 'name', schema: { name: Joi.string(), } }; table.schema = new Schema(config); const query = new Query('tim', table, serializer).expressionAttributeNames({ '#name': 'name' }); query.request.ExpressionAttributeNames.should.eql({ '#name': 'name' }); }); }); describe('#projectionExpression', () => { it('should set projection expression', () => { const config = { hashKey: 'name', schema: { name: Joi.string(), } }; table.schema = new Schema(config); const query = new Query('tim', table, serializer).projectionExpression('#name, #email'); query.request.ProjectionExpression.should.eql('#name, #email'); }); }); describe('#usingIndex', () => { it('should set the index name to use', () => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), created: Joi.date() }, indexes: [{ hashKey: 'name', rangeKey: 'created', type: 'local', name: 'CreatedIndex' }] }; table.schema = new Schema(config); const query = new Query('tim', table, serializer).usingIndex('CreatedIndex'); query.request.IndexName.should.equal('CreatedIndex'); }); it('should create key condition for global index hash key', () => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), age: Joi.number() }, indexes: [{ hashKey: 'age', type: 'global', name: 'UserAgeIndex' }] }; table.schema = new Schema(config); serializer.serializeItem.returns({ age: { N: '18' } }); const query = new Query(18, table, serializer).usingIndex('UserAgeIndex'); query.exec(); query.request.IndexName.should.equal('UserAgeIndex'); query.request.ExpressionAttributeNames.should.eql({ '#age': 'age' }); query.request.ExpressionAttributeValues.should.eql({ ':age': 18 }); query.request.KeyConditionExpression.should.eql('(#age = :age)'); }); }); describe('#consistentRead', () => { beforeEach(() => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), created: Joi.date() }, indexes: [{ hashKey: 'name', rangeKey: 'created', type: 'local', name: 'CreatedIndex' }] }; table.schema = new Schema(config); }); it('should set Consistent Read to true', () => { const query = new Query('tim', table, serializer).consistentRead(true); query.request.ConsistentRead.should.be.true; }); it('should set Consistent Read to true when passing no args', () => { const query = new Query('tim', table, serializer).consistentRead(); query.request.ConsistentRead.should.be.true; }); it('should set Consistent Read to false', () => { const query = new Query('tim', table, serializer).consistentRead(false); query.request.ConsistentRead.should.be.false; }); }); describe('#attributes', () => { beforeEach(() => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), created: Joi.date() }, indexes: [{ hashKey: 'name', rangeKey: 'created', type: 'local', name: 'CreatedIndex' }] }; table.schema = new Schema(config); }); it('should set array attributes to get', () => { const query = new Query('tim', table, serializer).attributes(['created', 'email']); query.request.ProjectionExpression.should.eql('#created,#email'); query.request.ExpressionAttributeNames.should.eql({ '#created': 'created', '#email': 'email' }); }); it('should set single attribute to get', () => { const query = new Query('tim', table, serializer).attributes('email'); query.request.ProjectionExpression.should.eql('#email'); query.request.ExpressionAttributeNames.should.eql({ '#email': 'email' }); }); }); describe('#order', () => { beforeEach(() => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), created: Joi.date() }, indexes: [{ hashKey: 'name', rangeKey: 'created', type: 'local', name: 'CreatedIndex' }] }; table.schema = new Schema(config); }); it('should set scan index forward to true', () => { const query = new Query('tim', table, serializer).ascending(); query.request.ScanIndexForward.should.be.true; }); it('should set scan index forward to false', () => { const query = new Query('tim', table, serializer).descending(); query.request.ScanIndexForward.should.be.false; }); }); describe('#startKey', () => { beforeEach(() => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), created: Joi.date() }, indexes: [{ hashKey: 'name', rangeKey: 'created', type: 'local', name: 'CreatedIndex' }] }; table.schema = new Schema(config); }); it('should set start Key', () => { const key = { name: { S: 'tim' }, email: { S: 'foo@example.com' } }; serializer.buildKey.returns(key); const query = new Query('tim', table, serializer).startKey({ name: 'tim', email: 'foo@example.com' }); query.request.ExclusiveStartKey.should.eql(key); }); }); describe('#select', () => { it('should set select Key', () => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), created: Joi.date() }, indexes: [{ hashKey: 'name', rangeKey: 'created', type: 'local', name: 'CreatedIndex' }] }; table.schema = new Schema(config); const query = new Query('tim', table, serializer).select('COUNT'); query.request.Select.should.eql('COUNT'); }); }); describe('#ReturnConsumedCapacity', () => { beforeEach(() => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), created: Joi.date() }, indexes: [{ hashKey: 'name', rangeKey: 'created', type: 'local', name: 'CreatedIndex' }] }; table.schema = new Schema(config); }); it('should set return consumed capacity Key to passed in value', () => { const query = new Query('tim', table, serializer).returnConsumedCapacity('TOTAL'); query.request.ReturnConsumedCapacity.should.eql('TOTAL'); }); it('should set return consumed capacity Key', () => { const query = new Query('tim', table, serializer).returnConsumedCapacity(); query.request.ReturnConsumedCapacity.should.eql('TOTAL'); }); }); describe('#where', () => { let query; beforeEach(() => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), created: Joi.date() }, indexes: [{ hashKey: 'name', rangeKey: 'created', type: 'local', name: 'CreatedIndex' }] }; table.schema = new Schema(config); query = new Query('tim', table, serializer); }); it('should have hash key and range key equals clauses', () => { query = query.where('email').equals('foo@example.com'); query.exec(); query.request.ExpressionAttributeNames.should.eql({ '#email': 'email', '#name': 'name' }); query.request.ExpressionAttributeValues.should.eql({ ':email': 'foo@example.com', ':name': 'tim' }); query.request.KeyConditionExpression.should.eql('(#email = :email) AND (#name = :name)'); }); it('should have equals clause', () => { query = query.where('email').equals('foo@example.com'); query.request.ExpressionAttributeNames.should.eql({ '#email': 'email' }); query.request.ExpressionAttributeValues.should.eql({ ':email': 'foo@example.com' }); query.request.KeyConditionExpression.should.eql('(#email = :email)'); }); it('should have less than or equal clause', () => { query = query.where('email').lte('foo@example.com'); query.request.ExpressionAttributeNames.should.eql({ '#email': 'email' }); query.request.ExpressionAttributeValues.should.eql({ ':email': 'foo@example.com' }); query.request.KeyConditionExpression.should.eql('(#email <= :email)'); }); it('should have less than clause', () => { query = query.where('email').lt('foo@example.com'); query.request.ExpressionAttributeNames.should.eql({ '#email': 'email' }); query.request.ExpressionAttributeValues.should.eql({ ':email': 'foo@example.com' }); query.request.KeyConditionExpression.should.eql('(#email < :email)'); }); it('should have greater than or equal clause', () => { query = query.where('email').gte('foo@example.com'); query.request.ExpressionAttributeNames.should.eql({ '#email': 'email' }); query.request.ExpressionAttributeValues.should.eql({ ':email': 'foo@example.com' }); query.request.KeyConditionExpression.should.eql('(#email >= :email)'); }); it('should have greater than clause', () => { query = query.where('email').gt('foo@example.com'); query.request.ExpressionAttributeNames.should.eql({ '#email': 'email' }); query.request.ExpressionAttributeValues.should.eql({ ':email': 'foo@example.com' }); query.request.KeyConditionExpression.should.eql('(#email > :email)'); }); it('should have begins with clause', () => { query = query.where('email').beginsWith('foo'); query.request.ExpressionAttributeNames.should.eql({ '#email': 'email' }); query.request.ExpressionAttributeValues.should.eql({ ':email': 'foo' }); query.request.KeyConditionExpression.should.eql('(begins_with(#email, :email))'); }); it('should have between clause', () => { query = query.where('email').between('bob@bob.com', 'foo@foo.com'); query.request.ExpressionAttributeNames.should.eql({ '#email': 'email' }); query.request.ExpressionAttributeValues.should.eql({ ':email': 'bob@bob.com', ':email_2': 'foo@foo.com' }); query.request.KeyConditionExpression.should.eql('(#email BETWEEN :email AND :email_2)'); }); it('should support multiple clauses on same attribute', () => { query = query.where('email').gt('foo@example.com').where('email').lt('moo@foo.com'); query.request.ExpressionAttributeNames.should.eql({ '#email': 'email' }); query.request.ExpressionAttributeValues.should.eql({ ':email': 'foo@example.com', ':email_2': 'moo@foo.com' }); query.request.KeyConditionExpression.should.eql('(#email > :email) AND (#email < :email_2)'); }); }); describe('#filter', () => { let query; beforeEach(() => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), created: Joi.date(), age: Joi.number(), data: Joi.object() }, indexes: [{ hashKey: 'name', rangeKey: 'created', type: 'local', name: 'CreatedIndex' }] }; table.schema = new Schema(config); query = new Query('tim', table, serializer); }); it('should have equals clause', () => { query = query.filter('age').equals(5); query.request.ExpressionAttributeNames.should.eql({ '#age': 'age' }); query.request.ExpressionAttributeValues.should.eql({ ':age': 5 }); query.request.FilterExpression.should.eql('(#age = :age)'); }); it('should have exists clause', () => { query = query.filter('age').exists(); query.request.ExpressionAttributeNames.should.eql({ '#age': 'age' }); expect(query.request.ExpressionAttributeValues).to.not.exist; query.request.FilterExpression.should.eql('(attribute_exists(#age))'); }); it('should have not exists clause', () => { query = query.filter('age').exists(false); query.request.ExpressionAttributeNames.should.eql({ '#age': 'age' }); expect(query.request.ExpressionAttributeValues).to.not.exist; query.request.FilterExpression.should.eql('(attribute_not_exists(#age))'); }); it('should have between clause', () => { query = query.filter('age').between(5, 7); query.request.ExpressionAttributeNames.should.eql({ '#age': 'age' }); query.request.ExpressionAttributeValues.should.eql({ ':age': 5, ':age_2': 7 }); query.request.FilterExpression.should.eql('(#age BETWEEN :age AND :age_2)'); }); it('should have IN clause', () => { query = query.filter('age').in([5, 7, 12]); query.request.ExpressionAttributeNames.should.eql({ '#age': 'age' }); query.request.ExpressionAttributeValues.should.eql({ ':age': 5, ':age_2': 7, ':age_3': 12 }); query.request.FilterExpression.should.eql('(#age IN (:age,:age_2,:age_3))'); }); it('should support multiple filters on same attribute', () => { query = query.filter('age').gt(5).filter('age').lt(20).filter('age').ne(15); query.request.ExpressionAttributeNames.should.eql({ '#age': 'age' }); query.request.ExpressionAttributeValues.should.eql({ ':age': 5, ':age_2': 20, ':age_3': 15 }); query.request.FilterExpression.should.eql('(#age > :age) AND (#age < :age_2) AND (#age <> :age_3)'); }); it('should support Map.Attr document paths', () => { query = query.filter('data.attr').equals(15); query.request.ExpressionAttributeNames.should.eql({ '#data': 'data', '#attr': 'attr' }); query.request.ExpressionAttributeValues.should.eql({ ':data_attr': 15 }); query.request.FilterExpression.should.eql('(#data.#attr = :data_attr)'); }); }); describe('#loadAll', () => { it('should set load all option to true', () => { const config = { hashKey: 'name', rangeKey: 'email', schema: { name: Joi.string(), email: Joi.string(), } }; table.schema = new Schema(config); const query = new Query('tim', table, serializer).loadAll(); query.options.loadAll.should.be.true; }); }); });