UNPKG

azure-storage

Version:

Microsoft Azure Storage Client Library for Node.js

602 lines (483 loc) 22.3 kB
// // Copyright (c) Microsoft and contributors. All rights reserved. // // Licensed 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 CONDITIONS OF ANY KIND, either express or implied. // // See the License for the specific language governing permissions and // limitations under the License. // // Module Includes var assert = require('assert'); var guid = require('node-uuid'); // Test includes var testutil = require('../../framework/util'); var tabletestutil = require('./table-test-utils'); var TestSuite = require('../../framework/test-suite'); // Lib includes var azure = testutil.libRequire('azure-storage'); var azureutil = testutil.libRequire('common/util/util'); var SR = azure.SR; var TableBatch = azure.TableBatch; var TableQuery = azure.TableQuery; var eg = azure.TableUtilities.entityGenerator; var HttpConstants = azure.Constants.HttpConstants; var tableService; var tablePrefix = 'batchtests'; var tables = []; var tableName1; var tableName2; var suite = new TestSuite('tableservice-batch-tests'); function listTables (prefix, options, token, callback) { tableService.listTablesSegmentedWithPrefix(prefix, token, options, function(error, result) { assert.equal(error, null); tables.push.apply(tables, result.entries); var token = result.continuationToken; if(token) { listTables(prefix, options, token, callback); } else { callback(); } }); } function generateEntities(count) { var entities = []; for(var i = 0 ; i < count ; i++) { var entity = { PartitionKey: eg.String('partition1'), RowKey: eg.String('row' + (i + 1).toString()), StringProperty: eg.String('stringSample'), BooleanProperty: eg.Boolean(true), BinaryProperty: eg.Binary(new Buffer('SampleStringInBuffer!')), Int32Property: eg.Int32(42), Int64Property: eg.Int64('5432873627392'), DoubleProperty: eg.Double(4.81516), DateTimeProperty: eg.DateTime(new Date(Date.UTC(2014, 04, 07, 08, 20, 53))), }; entities.push(entity); } return entities; }; function insertEntities (tableService, table, entities, callback) { var batch = new TableBatch(); var returnEntities = []; for(var i = 0; i < entities.length; i++) { batch.insertEntity(entities[i], {echoContent: true}); } tableService.executeBatch(table, batch, function (error, insertEntities, response) { assert.equal(error, null); assert.notEqual(insertEntities, null); assert.equal(response.statusCode, HttpConstants.HttpResponseCodes.Accepted); for(var i = 0; i < insertEntities.length; i++) { returnEntities.push(insertEntities[i].entity); } callback(returnEntities); }); } function compareProperties (propFromService, prop) { // check type if exists if (propFromService['$']) { assert.strictEqual(propFromService['$'], prop['$']); } // check value, make sure typeof is equal as Number.NaN should not equal 'Nan' assert.strictEqual(typeof propFromService['_'], typeof prop['_']); assert.strictEqual(propFromService['_'].toString(), prop['_'].toString()); } // The order of inputs here matters, entities from the service will have TimeStamps that we don't expect in the source. function compareEntities (entityFromService, entity) { var count = 0; for (var propName in entityFromService) { if(propName === 'Timestamp') { assert.notEqual(entityFromService.Timestamp, null); count += entity.Timestamp ? 0 : 1; } else if (propName === '.metadata') { assert.notEqual(entityFromService['.metadata'], null); assert.notEqual(entityFromService['.metadata'].etag, null); count += entity['.metadata'] ? 0 : 1; } else { compareProperties(entityFromService[propName], entity[propName]); } } assert.strictEqual(Object.keys(entityFromService).length, Object.keys(entity).length + count); }; describe('batchserviceclient-tests', function () { before(function (done) { if (suite.isMocked) { testutil.POLL_REQUEST_INTERVAL = 0; } suite.setupSuite(function () { tableService = azure.createTableService().withFilter(new azure.ExponentialRetryPolicyFilter()); done(); }); }); after(function (done) { suite.teardownSuite(done); }); beforeEach(function (done) { tableName1 = suite.getName(tablePrefix).replace(/-/g,''); tableName2 = suite.getName(tablePrefix).replace(/-/g,''); suite.setupTest(done); }); afterEach(function (done) { listTables(tablePrefix, null, null, function() { var deleteTables = function(tablesToDelete) { if (tablesToDelete.length === 0) { suite.teardownTest(done); } else { tableService.deleteTable(tablesToDelete[0], function (createError, table, createResponse) { deleteTables(tablesToDelete.slice(1)); }); } }; deleteTables(tables.slice(0)); }); }); describe('BatchUtilities', function () { it('remove', function (done) { var batch = new TableBatch(); var entities = generateEntities(3); batch.insertEntity(entities[0]); batch.insertEntity(entities[1]); batch.insertEntity(entities[2]); assert.ok(batch.hasOperations()); assert.equal(batch.operations.length, 3); var removed = batch.removeOperation(1); assert.ok(batch.hasOperations()); assert.equal(batch.operations.length, 2); assert.equal(removed.entity.RowKey._, 'row2'); removed = batch.removeOperation(3); assert.equal(removed, null); assert.equal(batch.operations.length, 2); done(); }); it('add', function (done) { var batch = new TableBatch(); var entities = generateEntities(2); batch.addOperation('INSERT', entities[0], {echoContent: true}); batch.addOperation('INSERT_OR_REPLACE', entities[1]); assert.ok(batch.hasOperations()); assert.equal(batch.operations.length, 2); assert.equal(batch.operations[0].options.echoContent, true) assert.equal(batch.operations[0].type, 'INSERT') assert.ok(batch.operations[1].options) assert.equal(batch.operations[1].type, 'INSERT_OR_REPLACE') done(); }); it('get', function (done) { var batch = new TableBatch(); var entities = generateEntities(3); batch.insertEntity(entities[0]); batch.insertEntity(entities[1]); batch.insertEntity(entities[2]); assert.ok(batch.hasOperations()); assert.equal(batch.operations.length, 3); var retrieved = batch.getOperation(0); assert.ok(retrieved); retrieved = batch.getOperation(3); assert.equal(retrieved, null); done(); }); it('retrieveLock', function (done) { var batch = new TableBatch(); var entities = generateEntities(2); batch.retrieveEntity(entities[0].PartitionKey._, entities[0].RowKey._); assert.ok(batch.hasOperations()); assert.throws(function () { batch.insertEntity(entities[1]); }, function (err) {return (err instanceof Error) && err.message === SR.BATCH_ONE_RETRIEVE}); batch.removeOperation(0); assert.equal(batch.hasOperations(), false); batch.insertEntity(entities[1]); done(); }); it('partitionKeyLock', function (done) { var batch = new TableBatch(); var entities = generateEntities(2); batch.insertEntity(entities[0]); assert.ok(batch.hasOperations()); assert.ok(batch.pk); entities[1].PartitionKey._ = 'foo'; assert.throws(function () { batch.insertEntity(entities[1]); }, function (err) {return (err instanceof Error) && err.message === SR.BATCH_ONE_PARTITION_KEY}); batch.removeOperation(0); assert.equal(batch.hasOperations(), false); assert.equal(batch.pk, null); batch.insertEntity(entities[1]); done(); }); }); describe('Insert', function () { it('shouldWork', function (done) { tableService.createTable(tableName1, function (createError, table, createResponse) { assert.equal(createError, null); assert.notEqual(table, null); assert.ok(createResponse.isSuccessful); assert.equal(createResponse.statusCode, HttpConstants.HttpResponseCodes.NoContent); assert.strictEqual(table.TableName, tableName1); // check that the table exists var batch = new TableBatch(); var entity = generateEntities(1)[0]; assert.notEqual(batch.hasOperations(), true); batch.insertEntity(entity); assert.ok(batch.hasOperations()); tableService.executeBatch(tableName1, batch, function (error, operationResponses, response) { assert.equal(error, null); assert.notEqual(operationResponses, null); assert.ok(response.isSuccessful); assert.equal(response.statusCode, HttpConstants.HttpResponseCodes.Accepted); assert.ok(batch.hasOperations()); tableService.retrieveEntity(tableName1, entity.PartitionKey['_'], entity.RowKey['_'], function (error4, entityResult) { assert.equal(error4, null); assert.notEqual(entityResult, null); compareEntities(entityResult, entity); done(); }); }); }); }); it('conflict', function (done) { tableService.createTable(tableName1, function (createError, table, createResponse) { assert.equal(createError, null); assert.notEqual(table, null); assert.ok(createResponse.isSuccessful); assert.equal(createResponse.statusCode, HttpConstants.HttpResponseCodes.NoContent); assert.strictEqual(table.TableName, tableName1); var entities = generateEntities(1); insertEntities(tableService, tableName1, entities, function (entities) { var batch = new TableBatch(); var insertEntity = entities[0]; batch.insertEntity(insertEntity); assert.ok(batch.hasOperations()); tableService.executeBatch(tableName1, batch, function (error, operationResponses, response) { assert.notEqual(error, null); assert.equal(error.code, 'EntityAlreadyExists'); assert.equal(operationResponses, null); assert.equal(response.isSuccessful, false); assert.equal(response.statusCode, HttpConstants.HttpResponseCodes.Conflict); done(); }); }); }); }); }); describe('Retrieve', function () { it('shouldWork', function (done) { tableService.createTable(tableName1, function (createError, table, createResponse) { assert.equal(createError, null); assert.notEqual(table, null); assert.ok(createResponse.isSuccessful); assert.equal(createResponse.statusCode, HttpConstants.HttpResponseCodes.NoContent); assert.strictEqual(table.TableName, tableName1); var entity = generateEntities(1)[0]; tableService.insertEntity(tableName1, entity, function (insertError, insertEntity, insertResponse) { assert.equal(insertError, null); assert.notEqual(insertEntity, null); assert.ok(insertResponse.isSuccessful); assert.equal(insertResponse.statusCode, HttpConstants.HttpResponseCodes.NoContent); var batch = new TableBatch(); assert.notEqual(batch.hasOperations(), true); batch.retrieveEntity(entity.PartitionKey._, entity.RowKey._); assert.ok(batch.hasOperations()); tableService.executeBatch(tableName1, batch, function (error, operationResponses, response) { assert.equal(error, null); assert.notEqual(operationResponses, null); assert.ok(response.isSuccessful); assert.equal(response.statusCode, HttpConstants.HttpResponseCodes.Accepted); assert.equal(operationResponses.length, 1); compareEntities(operationResponses[0].entity, entity) done(); }); }); }); }); it('resourceNotFound', function (done) { tableService.createTable(tableName1, function (createError, table, createResponse) { assert.equal(createError, null); assert.notEqual(table, null); assert.ok(createResponse.isSuccessful); assert.equal(createResponse.statusCode, HttpConstants.HttpResponseCodes.NoContent); assert.strictEqual(table.TableName, tableName1); var entity = generateEntities(1)[0]; var batch = new TableBatch(); assert.notEqual(batch.hasOperations(), true); batch.retrieveEntity(entity.PartitionKey._, entity.RowKey._); assert.ok(batch.hasOperations()); tableService.executeBatch(tableName1, batch, function (error, operationResponses, response) { assert.notEqual(error, null); assert.equal(error.code, 'ResourceNotFound'); assert.equal(operationResponses, null); assert.equal(response.isSuccessful, false); assert.equal(response.statusCode, HttpConstants.HttpResponseCodes.NotFound); done(); }); }); }); }); describe('BadBatches', function () { it('Empty', function (done) { tableService.createTable(tableName1, function (createError, table, createResponse) { assert.equal(createError, null); assert.notEqual(table, null); assert.ok(createResponse.isSuccessful); assert.equal(createResponse.statusCode, HttpConstants.HttpResponseCodes.NoContent); assert.strictEqual(table.TableName, tableName1); var batch = new TableBatch(); assert.throws(function () { tableService.executeBatch(tableName1, batch, function () {}); }, function (err) {return (err instanceof Error) && err.message === SR.EMPTY_BATCH}); done(); }); }); it('TooManyOperations', function (done) { tableService.createTable(tableName1, function (createError, table, createResponse) { assert.equal(createError, null); assert.notEqual(table, null); assert.ok(createResponse.isSuccessful); assert.equal(createResponse.statusCode, HttpConstants.HttpResponseCodes.NoContent); assert.strictEqual(table.TableName, tableName1); var entities = generateEntities(101); var batch = new TableBatch(); for (var i = 0; i < 100; i++) { batch.insertEntity(entities[i]); } assert.throws(function () { batch.insertEntity(entities[100]); }, function (err) {return (err instanceof Error) && err.message === SR.BATCH_TOO_LARGE}); done(); }); }); it('BadOperations', function (done) { tableService.createTable(tableName1, function (createError, table, createResponse) { assert.equal(createError, null); assert.notEqual(table, null); assert.ok(createResponse.isSuccessful); assert.equal(createResponse.statusCode, HttpConstants.HttpResponseCodes.NoContent); assert.strictEqual(table.TableName, tableName1); var batch = new TableBatch(); assert.throws(function () { batch.insertEntity(null); }, /Required argument entity for function addOperation is not defined/); assert.throws(function () { batch.insertEntity('foo'); }, /Parameter entity for function addOperation should be an object/); assert.throws(function () { batch.insertEntity({}); }, /Required argument entity.PartitionKey for function addOperation is not defined/); assert.throws(function () { batch.insertEntity({PartitionKey: 'foo'}); }, /Parameter entity.PartitionKey for function addOperation should be an object/); assert.throws(function () { batch.insertEntity({PartitionKey: eg.String('foo')}); }, /Required argument entity.RowKey for function addOperation is not defined/); assert.throws(function () { batch.insertEntity({PartitionKey: eg.String('foo'), RowKey: 'foo'}); }, /Parameter entity.RowKey for function addOperation should be an object/); done(); }); }); it('SameEntity', function (done) { tableService.createTable(tableName1, function (createError, table, createResponse) { assert.equal(createError, null); assert.notEqual(table, null); assert.ok(createResponse.isSuccessful); assert.equal(createResponse.statusCode, HttpConstants.HttpResponseCodes.NoContent); assert.strictEqual(table.TableName, tableName1); var entity = generateEntities(1)[0]; var batch = new TableBatch(); batch.insertEntity(entity); // Doing an update on the same entity within the same batch should make the batch fail entity.MyField = eg.String('othervalue'); batch.updateEntity(entity); tableService.executeBatch(tableName1, batch, function (error, operationResponses, response) { assert.notEqual(error, null); assert.equal(error.code, 'InvalidInput'); assert.equal(operationResponses, null); assert.equal(response.isSuccessful, false); assert.equal(response.statusCode, HttpConstants.HttpResponseCodes.BadRequest); done(); }); }); }); }); it('MaxOperations', function (done) { tableService.createTable(tableName1, function (createError, table, createResponse) { assert.equal(createError, null); assert.notEqual(table, null); assert.ok(createResponse.isSuccessful); assert.equal(createResponse.statusCode, HttpConstants.HttpResponseCodes.NoContent); assert.strictEqual(table.TableName, tableName1); var entities = generateEntities(100); var batch = new TableBatch(); for (var i = 0; i < 100; i++) { batch.insertEntity(entities[i]); } assert.ok(batch.hasOperations()); tableService.executeBatch(tableName1, batch, function (error, operationResponses, response) { assert.equal(error, null); assert.notEqual(operationResponses, null); assert.ok(response.isSuccessful); assert.equal(response.statusCode, HttpConstants.HttpResponseCodes.Accepted); assert.ok(batch.hasOperations()); tableService.queryEntities(tableName1, null, null, function (error4, entities1) { assert.equal(error4, null); assert.notEqual(entities1, null); assert.notEqual(entities1.entries, null); assert.equal(entities1.entries.length, 100); for (var i = 0; i < 100; i++) { // ignore row key to make comparison easier delete entities1.entries[i].RowKey; delete entities[i].RowKey; compareEntities(entities1.entries[i], entities[i]); } done(); }); }); }); }); it('AllOperations', function (done) { tableService.createTable(tableName1, function (createError, table, createResponse) { assert.equal(createError, null); assert.notEqual(table, null); assert.ok(createResponse.isSuccessful); assert.equal(createResponse.statusCode, HttpConstants.HttpResponseCodes.NoContent); assert.strictEqual(table.TableName, tableName1); var entities = generateEntities(6); var insertEntity = entities[0]; var insertOrMergeEntity = entities[1]; var insertOrReplaceEntity = entities[2]; insertEntities(tableService, tableName1, entities.slice(3), function (entities) { var mergeEntity = entities[0]; mergeEntity.StringProperty._ = 'newProperty'; var updateEntity = entities[1]; delete updateEntity.BooleanProperty; var deleteEntity = entities[2]; var batch = new TableBatch(); assert.notEqual(batch.hasOperations(), true); batch.insertEntity(insertEntity); batch.deleteEntity(deleteEntity); batch.mergeEntity(mergeEntity); batch.updateEntity(updateEntity); batch.insertOrMergeEntity(insertOrMergeEntity); batch.insertOrReplaceEntity(insertOrReplaceEntity); assert.ok(batch.hasOperations()); tableService.executeBatch(tableName1, batch, function (error, operationResponses, response) { assert.equal(error, null); assert.notEqual(operationResponses, null); assert.ok(response.isSuccessful); assert.equal(response.statusCode, HttpConstants.HttpResponseCodes.Accepted); assert.ok(batch.hasOperations()); tableService.queryEntities(tableName1, null, null, function (error4, entities1) { assert.equal(error4, null); assert.notEqual(entities1, null); assert.notEqual(entities1.entries, null); assert.equal(entities1.entries.length, 5); compareEntities(entities1.entries[0], insertEntity); compareEntities(entities1.entries[1], insertOrMergeEntity); compareEntities(entities1.entries[2], insertOrReplaceEntity); compareEntities(entities1.entries[3], mergeEntity); compareEntities(entities1.entries[4], updateEntity); done(); }); }); }); }); }); });