dynamite
Version:
promise-based DynamoDB client
287 lines (255 loc) • 7.04 kB
JavaScript
/**
* Provides ultility function for unit testing.
*
* @module utils
**/
var AWS = require('aws-sdk')
var localDynamo = require('local-dynamo')
var Q = require('kew')
var dynamite = require('../../dynamite')
var AWSName = require('../../lib/common').AWSName
var utils = {}
var apiVersion = AWSName.API_VERSION_2012
// These options make dynamite connect to the fake Dynamo DB instance.
// The good thing here is that we can initialize a dynamite.Client
// using the exact same way as we use in production.
var options = {
apiVersion: apiVersion,
sslEnabled: false,
endpoint: 'localhost:4567',
accessKeyId: 'xxx',
secretAccessKey: 'xxx',
region: 'xxx',
retryHandler: function (method, table, response) {
console.log('retrying', method, table, response)
}
}
utils.getMockDatabase = function () {
AWS.config.update(options)
return new AWS.DynamoDB()
}
utils.getMockDatabaseClient = function () {
return new dynamite.Client(options)
}
var localDynamoProc = null
utils.ensureLocalDynamo = function () {
if (!localDynamoProc) {
localDynamoProc = localDynamo.launch({
port: 4567,
detached: true,
heap: '1g'
})
localDynamoProc.on('exit', function () {
localDynamoProc = null
})
localDynamoProc.unref()
}
return localDynamoProc
}
process.on('exit', function () {
if (localDynamoProc) {
localDynamoProc.kill()
}
})
/*
* A helper function that delete the testing table.
*
* @param db {AWS.DynamoDB} The database instance.
* @return {Promise}
*/
utils.deleteTable = function (db, tableName) {
var defer = Q.defer()
db.deleteTable(
{TableName: tableName},
defer.makeNodeResolver()
)
return defer.promise
}
/*
* A helper to generate an index name for testing indices.
*
* @param hashKey {string}
* @param rangeKey {string}
*/
utils.indexNameGenerator = function (hashKey, rangeKey) {
var name = 'index-' + hashKey
if (rangeKey) name = name + '-' + rangeKey
return name
}
/*
* A helper function that creates the testing table.
*
* @param db {AWS.DynamoDB} The database instance.
* @return {Promise}
*/
utils.createTable = function (db, tableName, hashKey, rangeKey, gsiDefinitions) {
var defer = Q.defer()
var opts = {}
if (apiVersion === AWSName.API_VERSION_2011) {
opts = {
TableName: tableName,
KeySchema: {
HashKeyElement: {AttributeName: hashKey, AttributeType: "S"}
},
ProvisionedThroughput: {ReadCapacityUnits: 1, WriteCapacityUnits: 1}
}
if (rangeKey) {
opts.KeySchema.RangeKeyElement = {AttributeName: rangeKey, AttributeType: "S"}
}
db.createTable(opts, defer.makeNodeResolver())
} else if (apiVersion === AWSName.API_VERSION_2012) {
var attributeDefinitions = {}
attributeDefinitions[hashKey] = "S"
opts = {
TableName: tableName,
AttributeDefinitions: [],
KeySchema: [
{AttributeName: hashKey, KeyType: "HASH"}
],
ProvisionedThroughput: {ReadCapacityUnits: 1, WriteCapacityUnits: 1}
}
if (rangeKey) {
attributeDefinitions[rangeKey] = "S"
opts.KeySchema.push({
AttributeName: rangeKey,
KeyType: "RANGE"
})
}
if (gsiDefinitions) {
opts.GlobalSecondaryIndexes = gsiDefinitions.map(function (index) {
var keySchema = [
{AttributeName: index.hashKey, KeyType: "HASH"}
]
var hashKeyType = index.hashKeyType || "S"
attributeDefinitions[index.hashKey] = hashKeyType
if (index.rangeKey) {
var rangeKeyType = index.rangeKeyType || "S"
keySchema.push({AttributeName: index.rangeKey, KeyType: "RANGE"})
attributeDefinitions[index.rangeKey] = rangeKeyType
}
return {
IndexName: utils.indexNameGenerator(index.hashKey, index.rangeKey),
KeySchema: keySchema,
Projection: {
ProjectionType: "ALL"
},
ProvisionedThroughput: {ReadCapacityUnits: 1, WriteCapacityUnits: 1}
}
})
}
for (var field in attributeDefinitions) {
opts.AttributeDefinitions.push({AttributeName: field, AttributeType: attributeDefinitions[field]})
}
db.createTable(opts, defer.makeNodeResolver())
} else {
defer.reject(new Error('No api version found'))
}
return defer.promise
}
/*
* A helper function that converts raw data JSON into AWS JSON format.
*
* Example:
*
* raw data JSON: { userId: 'userA', column: '@', age: '29' }
*
* AWS JSON: { userId: { S: 'userA' }, column: { S: '@' }, age: { N: '29' } }
*
* @param obj {Object} The raw JSON data
* @return {Object} The same data in AWS JSON
*/
var convert = function (obj) {
var items = {}
for (var key in obj) {
if (Array.isArray(obj[key]) && isNaN(obj[key][0])) {
items[key] = {"SS": obj[key]}
} else if (Array.isArray(obj[key])) {
var numArray = []
for (var i in obj[key]) {
numArray.push(String(obj[key][i]))
}
items[key] = {"NS": numArray}
} else if (isNaN(obj[key])) {
items[key] = {"S": obj[key]}
} else {
items[key] = {"N": obj[key]}
}
}
return items
}
/*
* A helper function that incert one record directly using AWS API (not
* our own putItem)
*
* @param db {Object} The database instance
* @param tableName {String} The name of the table to insert
* @param record {Object} The raw JSON data
* @return {Q.Promise}
*/
var putOneRecord = function(db, tableName, record) {
var defer = Q.defer()
db.putItem(
{TableName: tableName, Item: convert(record)},
defer.makeNodeResolver()
)
return defer.promise
}
/*
* A helper function that initializes the testing database with
* some data.
*
* @return {Promise}
*/
utils.initTable = function (context) {
var db = context.db
var promises = []
for (var i = 0; i < context.data.length; i += 1) {
promises.push(putOneRecord(db, context.tableName, context.data[i]))
}
return Q.all(promises)
}
/*
* Get a record from the database with the original AWS SDK.
* The reason we don't use dynamite.getItem() here is to focus this test suite
* on putItem().
*
* @param db {AWS.DynamoDB} The database instance.
* @param hashKey {String}
* @param rangeKey {String}
*/
utils.getItemWithSDK = function (db, hashKey, rangeKey, table) {
var defer = Q.defer()
var opts = {}
table = table || 'user'
if (apiVersion === AWSName.API_VERSION_2011) {
opts = {
TableName: table,
Key: {
HashKeyElement: {"S": hashKey}
}
}
if (rangeKey) {
opts.Key.RangeKeyElement = {"S": rangeKey}
}
db.getItem(
opts,
defer.makeNodeResolver()
)
} else if (apiVersion === AWSName.API_VERSION_2012) {
opts = {
TableName: table,
Key: {
userId: {"S": hashKey}
}
}
if (rangeKey) {
opts.Key.column = {"S": rangeKey}
}
db.getItem(
opts,
defer.makeNodeResolver()
)
}
return defer.promise
}
exports = module.exports = utils