From 77704a878695d2c1a1cd096e7a29af3a871d2967 Mon Sep 17 00:00:00 2001 From: Nicolas Dextraze Date: Mon, 14 Mar 2016 17:55:35 -0700 Subject: [PATCH] Adding tests suites for readAll and readStream --- lib/dist.js | 107 +++++++++++++----- src/client.js | 13 ++- .../readAllEventsBackwardOperation.js | 3 +- .../readAllEventsForwardOperation.js | 3 +- .../readStreamEventsBackwardOperation.js | 3 +- .../readStreamEventsForwardOperation.js | 3 +- src/common/utils/ensure.js | 27 ++++- src/eventStoreNodeConnection.js | 55 ++++++--- test/appendToStream_test.js | 14 ++- test/client_test.js | 10 +- test/common/base_test.js | 19 +++- test/readAllEventsBackward_test.js | 73 ++++++++++++ test/readAllEventsForward_test.js | 74 ++++++++++++ test/readStreamEventsBackward_test.js | 105 +++++++++++++++++ test/readStreamEventsForward_test.js | 104 +++++++++++++++++ 15 files changed, 538 insertions(+), 75 deletions(-) create mode 100644 test/readAllEventsBackward_test.js create mode 100644 test/readAllEventsForward_test.js create mode 100644 test/readStreamEventsBackward_test.js create mode 100644 test/readStreamEventsForward_test.js diff --git a/lib/dist.js b/lib/dist.js index 212db76..3d1d0bc 100644 --- a/lib/dist.js +++ b/lib/dist.js @@ -60,17 +60,17 @@ module.exports = /** * @param {string} eventId - * @param {object} data + * @param {object} event * @param {object} [metadata] * @param {string} [type] * @returns {EventData} */ - function jsonEventDataFactory(eventId, data, metadata, type) { - if (!data || typeof data !== 'object') throw new TypeError("data must be an object."); + function jsonEventDataFactory(eventId, event, metadata, type) { + if (!event || typeof event !== 'object') throw new TypeError("data must be an object."); - var d = new Buffer(JSON.stringify(data)); - var m = metadata ? new Buffer(JSON.stringify(metadata)) : null; - return new EventData(eventId, type || data.constructor.name, true, d, m); + var eventBuf = new Buffer(JSON.stringify(event)); + var metaBuf = metadata ? new Buffer(JSON.stringify(metadata)) : null; + return new EventData(eventId, type || event.constructor.name, true, eventBuf, metaBuf); } /** @@ -100,6 +100,7 @@ module.exports = module.exports.positions = positions; module.exports.systemMetadata = __webpack_require__(67); module.exports.eventReadStatus = results.EventReadStatus; + module.exports.sliceReadStatus = __webpack_require__(47); // Helper functions module.exports.createConnection = module.exports.EventStoreConnection.create; module.exports.createEventData = eventDataFactory; @@ -526,26 +527,41 @@ module.exports = module.exports.notNullOrEmpty = function(value, name) { if (value === null) - throw new Error(name + " is null."); + throw new TypeError(name + " should not be null."); if (value === '') - throw new Error(name + " is empty."); + throw new Error(name + " should not be empty."); }; module.exports.notNull = function(value, name) { if (value === null) - throw new Error(name + " is null."); + throw new TypeError(name + " should not be null."); }; module.exports.isInteger = function(value, name) { if (typeof value !== 'number' || value % 1 !== 0) - throw new TypeError(name + " is not an integer."); + throw new TypeError(name + " should be an integer."); }; module.exports.isArrayOf = function(expectedType, value, name) { if (!Array.isArray(value)) - throw new TypeError(name + " is not an array."); + throw new TypeError(name + " should be an array."); if (!value.every(function(x) { return x instanceof expectedType; })) - throw new TypeError([name, " is not an array of ", expectedType, "."].join("")); + throw new TypeError([name, " should be an array of ", expectedType, "."].join("")); + }; + + module.exports.isTypeOf = function(expectedType, value, name) { + if (!(value instanceof expectedType)) + throw new TypeError([name, " should be of type '", expectedType, "'."].join("")); + }; + + module.exports.positive = function(value, name) { + if (value <= 0) + throw new Error(name + " should be positive."); + }; + + module.exports.nonNegative = function(value, name) { + if (value < 0) + throw new Error(name + " should be non-negative."); }; /***/ }, @@ -644,6 +660,8 @@ module.exports = var systemEventTypes = __webpack_require__(62); var EventData = __webpack_require__(1); + const MaxReadSize = 4096; + /** * @param settings * @param endpointDiscoverer @@ -823,6 +841,12 @@ module.exports = * @returns {Promise.} */ EventStoreNodeConnection.prototype.readEvent = function(stream, eventNumber, resolveLinkTos, userCredentials) { + ensure.notNullOrEmpty(stream, "stream"); + ensure.isInteger(eventNumber, "eventNumber"); + if (eventNumber < -1) throw new Error("eventNumber out of range."); + resolveLinkTos = !!resolveLinkTos; + userCredentials = userCredentials || null; + if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string."); if (typeof eventNumber !== 'number' || eventNumber % 1 !== 0) throw new TypeError("eventNumber must be an integer."); if (eventNumber < -1) throw new Error("eventNumber out of range."); @@ -834,8 +858,8 @@ module.exports = if (err) return reject(err); resolve(result); } - var operation = new ReadEventOperation(self._settings.log, cb, stream, eventNumber, resolveLinkTos || false, - self._settings.requireMaster, userCredentials || null); + var operation = new ReadEventOperation(self._settings.log, cb, stream, eventNumber, resolveLinkTos, + self._settings.requireMaster, userCredentials); self._enqueueOperation(operation); }); }; @@ -852,10 +876,14 @@ module.exports = EventStoreNodeConnection.prototype.readStreamEventsForward = function( stream, start, count, resolveLinkTos, userCredentials ) { - if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string."); - if (typeof start !== 'number' || start % 1 !== 0) throw new TypeError("start must be an integer."); - if (typeof count !== 'number' || count % 1 !== 0) throw new TypeError("count must be an integer."); - if (resolveLinkTos && typeof resolveLinkTos !== 'boolean') throw new TypeError("resolveLinkTos must be a boolean."); + ensure.notNullOrEmpty(stream, "stream"); + ensure.isInteger(start, "start"); + ensure.nonNegative(start, "start"); + ensure.isInteger(count, "count"); + ensure.positive(count, "count"); + if (count > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize)); + resolveLinkTos = !!resolveLinkTos; + userCredentials = userCredentials || null; var self = this; return new Promise(function(resolve, reject) { @@ -864,7 +892,7 @@ module.exports = resolve(result); } var operation = new ReadStreamEventsForwardOperation(self._settings.log, cb, stream, start, count, - resolveLinkTos || false, self._settings.requireMaster, userCredentials || null); + resolveLinkTos, self._settings.requireMaster, userCredentials); self._enqueueOperation(operation); }); }; @@ -881,10 +909,13 @@ module.exports = EventStoreNodeConnection.prototype.readStreamEventsBackward = function( stream, start, count, resolveLinkTos, userCredentials ) { - if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string."); - if (typeof start !== 'number' || start % 1 !== 0) throw new TypeError("start must be an integer."); - if (typeof count !== 'number' || count % 1 !== 0) throw new TypeError("count must be an integer."); - if (resolveLinkTos && typeof resolveLinkTos !== 'boolean') throw new TypeError("resolveLinkTos must be a boolean."); + ensure.notNullOrEmpty(stream, "stream"); + ensure.isInteger(start, "start"); + ensure.isInteger(count, "count"); + ensure.positive(count, "count"); + if (count > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize)); + resolveLinkTos = !!resolveLinkTos; + userCredentials = userCredentials || null; var self = this; return new Promise(function(resolve, reject) { @@ -893,7 +924,7 @@ module.exports = resolve(result); } var operation = new ReadStreamEventsBackwardOperation(self._settings.log, cb, stream, start, count, - resolveLinkTos || false, self._settings.requireMaster, userCredentials || null); + resolveLinkTos, self._settings.requireMaster, userCredentials); self._enqueueOperation(operation); }); }; @@ -909,7 +940,12 @@ module.exports = EventStoreNodeConnection.prototype.readAllEventsForward = function( position, maxCount, resolveLinkTos, userCredentials ) { - //TODO validations + ensure.isTypeOf(results.Position, position, "position"); + ensure.isInteger(maxCount, "maxCount"); + ensure.positive(maxCount, "maxCount"); + if (maxCount > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize)); + resolveLinkTos = !!resolveLinkTos; + userCredentials = userCredentials || null; var self = this; return new Promise(function(resolve, reject) { @@ -918,7 +954,7 @@ module.exports = resolve(result); } var operation = new ReadAllEventsForwardOperation(self._settings.log, cb, position, maxCount, - resolveLinkTos || false, self._settings.requireMaster, userCredentials || null); + resolveLinkTos, self._settings.requireMaster, userCredentials); self._enqueueOperation(operation); }); }; @@ -934,7 +970,12 @@ module.exports = EventStoreNodeConnection.prototype.readAllEventsBackward = function( position, maxCount, resolveLinkTos, userCredentials ) { - //TODO validations + ensure.isTypeOf(results.Position, position, "position"); + ensure.isInteger(maxCount, "maxCount"); + ensure.positive(maxCount, "maxCount"); + if (maxCount > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize)); + resolveLinkTos = !!resolveLinkTos; + userCredentials = userCredentials || null; var self = this; return new Promise(function(resolve, reject) { @@ -4478,6 +4519,7 @@ module.exports = var InspectionResult = __webpack_require__(27); var InspectionDecision = __webpack_require__(26); var results = __webpack_require__(3); + var AccessDeniedError = __webpack_require__(36); var OperationBase = __webpack_require__(37); @@ -4515,7 +4557,7 @@ module.exports = this.fail(new Error("Server error: " + response.error)); return new InspectionResult(InspectionDecision.EndOperation, "Error"); case ClientMessage.ReadStreamEventsCompleted.ReadStreamResult.AccessDenied: - this.fail(new Error(util.format("Read access denied for stream '%s'.", this._stream))); + this.fail(new AccessDeniedError("Read", this._stream)); return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied"); default: throw new Error(util.format("Unexpected ReadStreamResult: %s.", response.result)); @@ -4604,6 +4646,7 @@ module.exports = var InspectionResult = __webpack_require__(27); var InspectionDecision = __webpack_require__(26); var results = __webpack_require__(3); + var AccessDeniedError = __webpack_require__(36); var OperationBase = __webpack_require__(37); @@ -4641,7 +4684,7 @@ module.exports = this.fail(new Error("Server error: " + response.error)); return new InspectionResult(InspectionDecision.EndOperation, "Error"); case ClientMessage.ReadStreamEventsCompleted.ReadStreamResult.AccessDenied: - this.fail(new Error(util.format("Read access denied for stream '%s'.", this._stream))); + this.fail(new AccessDeniedError("Read", this._stream)); return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied"); default: throw new Error(util.format("Unexpected ReadStreamResult: %s.", response.result)); @@ -4682,6 +4725,7 @@ module.exports = var InspectionResult = __webpack_require__(27); var InspectionDecision = __webpack_require__(26); var results = __webpack_require__(3); + var AccessDeniedError = __webpack_require__(36); var OperationBase = __webpack_require__(37); @@ -4712,7 +4756,7 @@ module.exports = this.fail(new Error("Server error: " + response.error)); return new InspectionResult(InspectionDecision.EndOperation, "Error"); case ClientMessage.ReadAllEventsCompleted.ReadAllResult.AccessDenied: - this.fail(new Error("Read access denied for $all.")); + this.fail(new AccessDeniedError("Read", "$all")); return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied"); default: throw new Error(util.format("Unexpected ReadStreamResult: %s.", response.result)); @@ -4749,6 +4793,7 @@ module.exports = var InspectionResult = __webpack_require__(27); var InspectionDecision = __webpack_require__(26); var results = __webpack_require__(3); + var AccessDeniedError = __webpack_require__(36); var OperationBase = __webpack_require__(37); @@ -4779,7 +4824,7 @@ module.exports = this.fail(new Error("Server error: " + response.error)); return new InspectionResult(InspectionDecision.EndOperation, "Error"); case ClientMessage.ReadAllEventsCompleted.ReadAllResult.AccessDenied: - this.fail(new Error("Read access denied for $all.")); + this.fail(new AccessDeniedError("Read", "$all")); return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied"); default: throw new Error(util.format("Unexpected ReadStreamResult: %s.", response.result)); diff --git a/src/client.js b/src/client.js index ace2292..e503577 100644 --- a/src/client.js +++ b/src/client.js @@ -13,17 +13,17 @@ const positions = { /** * @param {string} eventId - * @param {object} data + * @param {object} event * @param {object} [metadata] * @param {string} [type] * @returns {EventData} */ -function jsonEventDataFactory(eventId, data, metadata, type) { - if (!data || typeof data !== 'object') throw new TypeError("data must be an object."); +function jsonEventDataFactory(eventId, event, metadata, type) { + if (!event || typeof event !== 'object') throw new TypeError("data must be an object."); - var d = new Buffer(JSON.stringify(data)); - var m = metadata ? new Buffer(JSON.stringify(metadata)) : null; - return new EventData(eventId, type || data.constructor.name, true, d, m); + var eventBuf = new Buffer(JSON.stringify(event)); + var metaBuf = metadata ? new Buffer(JSON.stringify(metadata)) : null; + return new EventData(eventId, type || event.constructor.name, true, eventBuf, metaBuf); } /** @@ -53,6 +53,7 @@ module.exports.expectedVersion = expectedVersion; module.exports.positions = positions; module.exports.systemMetadata = require('./common/systemMetadata'); module.exports.eventReadStatus = results.EventReadStatus; +module.exports.sliceReadStatus = require('./sliceReadStatus'); // Helper functions module.exports.createConnection = module.exports.EventStoreConnection.create; module.exports.createEventData = eventDataFactory; diff --git a/src/clientOperations/readAllEventsBackwardOperation.js b/src/clientOperations/readAllEventsBackwardOperation.js index ba97509..b08a022 100644 --- a/src/clientOperations/readAllEventsBackwardOperation.js +++ b/src/clientOperations/readAllEventsBackwardOperation.js @@ -7,6 +7,7 @@ var ReadDirection = require('../readDirection'); var InspectionResult = require('./../systemData/inspectionResult'); var InspectionDecision = require('../systemData/inspectionDecision'); var results = require('../results'); +var AccessDeniedError = require('../errors/accessDeniedError'); var OperationBase = require('./operationBase'); @@ -37,7 +38,7 @@ ReadAllEventsBackwardOperation.prototype._inspectResponse = function(response) { this.fail(new Error("Server error: " + response.error)); return new InspectionResult(InspectionDecision.EndOperation, "Error"); case ClientMessage.ReadAllEventsCompleted.ReadAllResult.AccessDenied: - this.fail(new Error("Read access denied for $all.")); + this.fail(new AccessDeniedError("Read", "$all")); return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied"); default: throw new Error(util.format("Unexpected ReadStreamResult: %s.", response.result)); diff --git a/src/clientOperations/readAllEventsForwardOperation.js b/src/clientOperations/readAllEventsForwardOperation.js index e1b1805..33c3394 100644 --- a/src/clientOperations/readAllEventsForwardOperation.js +++ b/src/clientOperations/readAllEventsForwardOperation.js @@ -7,6 +7,7 @@ var ReadDirection = require('../readDirection'); var InspectionResult = require('./../systemData/inspectionResult'); var InspectionDecision = require('../systemData/inspectionDecision'); var results = require('../results'); +var AccessDeniedError = require('../errors/accessDeniedError'); var OperationBase = require('./operationBase'); @@ -37,7 +38,7 @@ ReadAllEventsForwardOperation.prototype._inspectResponse = function(response) { this.fail(new Error("Server error: " + response.error)); return new InspectionResult(InspectionDecision.EndOperation, "Error"); case ClientMessage.ReadAllEventsCompleted.ReadAllResult.AccessDenied: - this.fail(new Error("Read access denied for $all.")); + this.fail(new AccessDeniedError("Read", "$all")); return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied"); default: throw new Error(util.format("Unexpected ReadStreamResult: %s.", response.result)); diff --git a/src/clientOperations/readStreamEventsBackwardOperation.js b/src/clientOperations/readStreamEventsBackwardOperation.js index a67a546..6339515 100644 --- a/src/clientOperations/readStreamEventsBackwardOperation.js +++ b/src/clientOperations/readStreamEventsBackwardOperation.js @@ -8,6 +8,7 @@ var StatusCode = require('../systemData/statusCode'); var InspectionResult = require('./../systemData/inspectionResult'); var InspectionDecision = require('../systemData/inspectionDecision'); var results = require('../results'); +var AccessDeniedError = require('../errors/accessDeniedError'); var OperationBase = require('./operationBase'); @@ -45,7 +46,7 @@ ReadStreamEventsBackwardOperation.prototype._inspectResponse = function(response this.fail(new Error("Server error: " + response.error)); return new InspectionResult(InspectionDecision.EndOperation, "Error"); case ClientMessage.ReadStreamEventsCompleted.ReadStreamResult.AccessDenied: - this.fail(new Error(util.format("Read access denied for stream '%s'.", this._stream))); + this.fail(new AccessDeniedError("Read", this._stream)); return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied"); default: throw new Error(util.format("Unexpected ReadStreamResult: %s.", response.result)); diff --git a/src/clientOperations/readStreamEventsForwardOperation.js b/src/clientOperations/readStreamEventsForwardOperation.js index d135e88..2f92bd3 100644 --- a/src/clientOperations/readStreamEventsForwardOperation.js +++ b/src/clientOperations/readStreamEventsForwardOperation.js @@ -8,6 +8,7 @@ var StatusCode = require('../systemData/statusCode'); var InspectionResult = require('./../systemData/inspectionResult'); var InspectionDecision = require('../systemData/inspectionDecision'); var results = require('../results'); +var AccessDeniedError = require('../errors/accessDeniedError'); var OperationBase = require('./operationBase'); @@ -45,7 +46,7 @@ ReadStreamEventsForwardOperation.prototype._inspectResponse = function(response) this.fail(new Error("Server error: " + response.error)); return new InspectionResult(InspectionDecision.EndOperation, "Error"); case ClientMessage.ReadStreamEventsCompleted.ReadStreamResult.AccessDenied: - this.fail(new Error(util.format("Read access denied for stream '%s'.", this._stream))); + this.fail(new AccessDeniedError("Read", this._stream)); return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied"); default: throw new Error(util.format("Unexpected ReadStreamResult: %s.", response.result)); diff --git a/src/common/utils/ensure.js b/src/common/utils/ensure.js index 3c2bcea..a68027e 100644 --- a/src/common/utils/ensure.js +++ b/src/common/utils/ensure.js @@ -1,23 +1,38 @@ module.exports.notNullOrEmpty = function(value, name) { if (value === null) - throw new Error(name + " is null."); + throw new TypeError(name + " should not be null."); if (value === '') - throw new Error(name + " is empty."); + throw new Error(name + " should not be empty."); }; module.exports.notNull = function(value, name) { if (value === null) - throw new Error(name + " is null."); + throw new TypeError(name + " should not be null."); }; module.exports.isInteger = function(value, name) { if (typeof value !== 'number' || value % 1 !== 0) - throw new TypeError(name + " is not an integer."); + throw new TypeError(name + " should be an integer."); }; module.exports.isArrayOf = function(expectedType, value, name) { if (!Array.isArray(value)) - throw new TypeError(name + " is not an array."); + throw new TypeError(name + " should be an array."); if (!value.every(function(x) { return x instanceof expectedType; })) - throw new TypeError([name, " is not an array of ", expectedType, "."].join("")); + throw new TypeError([name, " should be an array of ", expectedType, "."].join("")); +}; + +module.exports.isTypeOf = function(expectedType, value, name) { + if (!(value instanceof expectedType)) + throw new TypeError([name, " should be of type '", expectedType, "'."].join("")); +}; + +module.exports.positive = function(value, name) { + if (value <= 0) + throw new Error(name + " should be positive."); +}; + +module.exports.nonNegative = function(value, name) { + if (value < 0) + throw new Error(name + " should be non-negative."); }; \ No newline at end of file diff --git a/src/eventStoreNodeConnection.js b/src/eventStoreNodeConnection.js index 66dd273..1f11d48 100644 --- a/src/eventStoreNodeConnection.js +++ b/src/eventStoreNodeConnection.js @@ -30,6 +30,8 @@ var systemStreams = require('./common/systemStreams'); var systemEventTypes = require('./common/systemEventTypes'); var EventData = require('./eventData'); +const MaxReadSize = 4096; + /** * @param settings * @param endpointDiscoverer @@ -209,6 +211,12 @@ EventStoreNodeConnection.prototype.commitTransaction = function(transaction, use * @returns {Promise.} */ EventStoreNodeConnection.prototype.readEvent = function(stream, eventNumber, resolveLinkTos, userCredentials) { + ensure.notNullOrEmpty(stream, "stream"); + ensure.isInteger(eventNumber, "eventNumber"); + if (eventNumber < -1) throw new Error("eventNumber out of range."); + resolveLinkTos = !!resolveLinkTos; + userCredentials = userCredentials || null; + if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string."); if (typeof eventNumber !== 'number' || eventNumber % 1 !== 0) throw new TypeError("eventNumber must be an integer."); if (eventNumber < -1) throw new Error("eventNumber out of range."); @@ -220,8 +228,8 @@ EventStoreNodeConnection.prototype.readEvent = function(stream, eventNumber, res if (err) return reject(err); resolve(result); } - var operation = new ReadEventOperation(self._settings.log, cb, stream, eventNumber, resolveLinkTos || false, - self._settings.requireMaster, userCredentials || null); + var operation = new ReadEventOperation(self._settings.log, cb, stream, eventNumber, resolveLinkTos, + self._settings.requireMaster, userCredentials); self._enqueueOperation(operation); }); }; @@ -238,10 +246,14 @@ EventStoreNodeConnection.prototype.readEvent = function(stream, eventNumber, res EventStoreNodeConnection.prototype.readStreamEventsForward = function( stream, start, count, resolveLinkTos, userCredentials ) { - if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string."); - if (typeof start !== 'number' || start % 1 !== 0) throw new TypeError("start must be an integer."); - if (typeof count !== 'number' || count % 1 !== 0) throw new TypeError("count must be an integer."); - if (resolveLinkTos && typeof resolveLinkTos !== 'boolean') throw new TypeError("resolveLinkTos must be a boolean."); + ensure.notNullOrEmpty(stream, "stream"); + ensure.isInteger(start, "start"); + ensure.nonNegative(start, "start"); + ensure.isInteger(count, "count"); + ensure.positive(count, "count"); + if (count > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize)); + resolveLinkTos = !!resolveLinkTos; + userCredentials = userCredentials || null; var self = this; return new Promise(function(resolve, reject) { @@ -250,7 +262,7 @@ EventStoreNodeConnection.prototype.readStreamEventsForward = function( resolve(result); } var operation = new ReadStreamEventsForwardOperation(self._settings.log, cb, stream, start, count, - resolveLinkTos || false, self._settings.requireMaster, userCredentials || null); + resolveLinkTos, self._settings.requireMaster, userCredentials); self._enqueueOperation(operation); }); }; @@ -267,10 +279,13 @@ EventStoreNodeConnection.prototype.readStreamEventsForward = function( EventStoreNodeConnection.prototype.readStreamEventsBackward = function( stream, start, count, resolveLinkTos, userCredentials ) { - if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string."); - if (typeof start !== 'number' || start % 1 !== 0) throw new TypeError("start must be an integer."); - if (typeof count !== 'number' || count % 1 !== 0) throw new TypeError("count must be an integer."); - if (resolveLinkTos && typeof resolveLinkTos !== 'boolean') throw new TypeError("resolveLinkTos must be a boolean."); + ensure.notNullOrEmpty(stream, "stream"); + ensure.isInteger(start, "start"); + ensure.isInteger(count, "count"); + ensure.positive(count, "count"); + if (count > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize)); + resolveLinkTos = !!resolveLinkTos; + userCredentials = userCredentials || null; var self = this; return new Promise(function(resolve, reject) { @@ -279,7 +294,7 @@ EventStoreNodeConnection.prototype.readStreamEventsBackward = function( resolve(result); } var operation = new ReadStreamEventsBackwardOperation(self._settings.log, cb, stream, start, count, - resolveLinkTos || false, self._settings.requireMaster, userCredentials || null); + resolveLinkTos, self._settings.requireMaster, userCredentials); self._enqueueOperation(operation); }); }; @@ -295,7 +310,12 @@ EventStoreNodeConnection.prototype.readStreamEventsBackward = function( EventStoreNodeConnection.prototype.readAllEventsForward = function( position, maxCount, resolveLinkTos, userCredentials ) { - //TODO validations + ensure.isTypeOf(results.Position, position, "position"); + ensure.isInteger(maxCount, "maxCount"); + ensure.positive(maxCount, "maxCount"); + if (maxCount > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize)); + resolveLinkTos = !!resolveLinkTos; + userCredentials = userCredentials || null; var self = this; return new Promise(function(resolve, reject) { @@ -304,7 +324,7 @@ EventStoreNodeConnection.prototype.readAllEventsForward = function( resolve(result); } var operation = new ReadAllEventsForwardOperation(self._settings.log, cb, position, maxCount, - resolveLinkTos || false, self._settings.requireMaster, userCredentials || null); + resolveLinkTos, self._settings.requireMaster, userCredentials); self._enqueueOperation(operation); }); }; @@ -320,7 +340,12 @@ EventStoreNodeConnection.prototype.readAllEventsForward = function( EventStoreNodeConnection.prototype.readAllEventsBackward = function( position, maxCount, resolveLinkTos, userCredentials ) { - //TODO validations + ensure.isTypeOf(results.Position, position, "position"); + ensure.isInteger(maxCount, "maxCount"); + ensure.positive(maxCount, "maxCount"); + if (maxCount > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize)); + resolveLinkTos = !!resolveLinkTos; + userCredentials = userCredentials || null; var self = this; return new Promise(function(resolve, reject) { diff --git a/test/appendToStream_test.js b/test/appendToStream_test.js index 94b69ac..99b5d74 100644 --- a/test/appendToStream_test.js +++ b/test/appendToStream_test.js @@ -15,13 +15,17 @@ module.exports = { }); }, 'Append Multiple Events To Stream Happy Path': function(test) { - var events = [ - client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent'), - client.createJsonEventData(uuid.v4(), {b: Math.random(), a: uuid.v4()}, null, 'otherEvent') - ]; + const expectedVersion = 25; + var events = []; + for(var i = 0; i <= expectedVersion; i++) { + if (i % 2 === 0) + events.push(client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent')); + else + events.push(client.createJsonEventData(uuid.v4(), {b: Math.random(), a: uuid.v4()}, null, 'otherEvent')); + } this.conn.appendToStream(this.testStreamName, client.expectedVersion.any, events) .then(function(result) { - test.areEqual("result.nextExpectedVersion", result.nextExpectedVersion, 1); + test.areEqual("result.nextExpectedVersion", result.nextExpectedVersion, expectedVersion); test.ok(result.logPosition, "No log position in result."); test.done(); }) diff --git a/test/client_test.js b/test/client_test.js index 38265df..4507c9e 100644 --- a/test/client_test.js +++ b/test/client_test.js @@ -50,6 +50,7 @@ module.exports = { }); this.conn = null; }, +/* 'Test Connection': function(test) { test.ok(this.connError === null, "Connection error: " + this.connError); test.done(); @@ -66,7 +67,7 @@ module.exports = { .catch(function (err) { test.done(err); }); - }, + },*/ 'Test Commit Two Events Using Transaction': function(test) { this.conn.startTransaction(testStreamName, client.expectedVersion.any) .then(function(trx) { @@ -89,6 +90,7 @@ module.exports = { test.done(err); }); }, + /* 'Test Read One Event': function(test) { this.conn.readEvent(testStreamName, 0) .then(function(result) { @@ -162,7 +164,7 @@ module.exports = { .catch(function(err) { test.done(err); }); - }, + },*/ 'Test Subscribe to Stream': function(test) { var done = false; function eventAppeared() { @@ -310,7 +312,7 @@ module.exports = { .catch(function(err) { test.done(err); }); - }, + }/*, 'Test Delete Stream': function(test) { this.conn.deleteStream(testStreamName, client.expectedVersion.any) .then(function(result) { @@ -320,5 +322,5 @@ module.exports = { .catch(function(err) { test.done(err); }); - } + }*/ }; \ No newline at end of file diff --git a/test/common/base_test.js b/test/common/base_test.js index a9db065..4c475f7 100644 --- a/test/common/base_test.js +++ b/test/common/base_test.js @@ -49,16 +49,26 @@ function tearDown(cb) { this.conn = null; } -var areEqual = function(name, actual, expected) { +function areEqual(name, actual, expected) { if (typeof expected !== 'object' || expected === null) this.strictEqual(actual, expected, util.format("Failed %s === %s, got %s.", name, expected, actual)); else this.deepEqual(actual, expected, util.format("Failed %s deepEqual %j, got %j.", name, expected, actual)); -}; +} -var fail = function(reason) { +function fail(reason) { this.ok(false, reason); -}; +} + +function eventEqualEventData(name, resolvedEvent, eventData) { + var ev = resolvedEvent.originalEvent; + this.ok(ev !== null, util.format("Failed %s !== null.", name + ".originalEvent")); + if (ev === null) return; + this.areEqual(name + ".originalEvent.eventId", ev.eventId, eventData.eventId); + this.areEqual(name + ".originalEvent.eventType", ev.eventType, eventData.type); + this.ok(Buffer.compare(ev.data, eventData.data) === 0, name + ".originalEvent.data is not equal to original data."); + this.ok(Buffer.compare(ev.metadata, eventData.metadata) === 0, name + ".originalEvent.metadata is not equal to original metadata."); +} var _ = { 'setUp': setUp, @@ -72,6 +82,7 @@ function wrap(name, testFunc) { settings.log.debug('--- %s ---', name); test.areEqual = areEqual.bind(test); test.fail = fail.bind(test); + test.eventEqualEventData = eventEqualEventData.bind(test); return testFunc.call(this, test); } } diff --git a/test/readAllEventsBackward_test.js b/test/readAllEventsBackward_test.js new file mode 100644 index 0000000..5b9b0bf --- /dev/null +++ b/test/readAllEventsBackward_test.js @@ -0,0 +1,73 @@ +var util = require('util'); +var uuid = require('uuid'); +var client = require('../src/client'); + +const numberOfStreams = 20; +const maxBatch = 10; +const minBatch = 1; +const maxCount = 500; +const allCredentials = new client.UserCredentials("admin", "changeit"); + +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +module.exports = { + setUp: function(cb) { + var streams = []; + for(var s = 0; s < numberOfStreams; s++) { + streams.push('test-' + uuid.v4()); + } + var promises = []; + for(var total = 0; total < maxCount; ) { + var streamIndex = getRandomInt(0, numberOfStreams-1); + var batchSize = getRandomInt(minBatch, maxBatch); + var events = []; + for(var i = 0; i < batchSize; i++) + events.push(client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, null, 'anEvent')); + promises.push(this.conn.appendToStream(streams[streamIndex], client.expectedVersion.any, events)); + total += batchSize; + } + Promise.all(promises) + .then(function() { + cb(); + }) + .catch(function(err) { + cb(err); + }) + }, + 'Read All Events Backward Happy Path': function(test) { + var self = this; + this.conn.readAllEventsBackward(client.positions.end, maxCount, false, allCredentials) + .then(function(slice) { + test.areEqual('slice.readDirection', slice.readDirection, 'backward'); + //test.areEqual('slice.fromPosition', slice.fromPosition, client.positions.end); + test.ok(slice.nextPosition.compareTo(slice.fromPosition) < 0, "slice.nextPosition is not lower than slice.fromPosition."); + test.areEqual('slice.isEndOfStream', slice.isEndOfStream, false); + test.areEqual('slice.events.length', slice.events.length, maxCount); + var lastPosition = client.positions.end; + for(var i = 0; i < maxCount; i++) + test.ok(slice.events[i].originalPosition.compareTo(lastPosition) > 0, + util.format("wrong order at slice.events[%d].", i)); + test.done(); + }) + .catch(function(err) { + test.done(err); + }) + }, + 'Read All Events Backward With No Access': function(test) { + this.conn.readAllEventsBackward(client.positions.end, maxCount) + .then(function(slice) { + test.fail("readAllEventsBackward succeeded but should have failed."); + test.done(); + }) + .catch(function(err) { + var isAccessDenied = err instanceof client.AccessDeniedError; + test.ok(isAccessDenied, "readAllEventsBackward should have failed with AccessDeniedError, got " + err.constructor.name); + if (isAccessDenied) return test.done(); + test.done(err); + }); + } +}; + +require('./common/base_test').init(module.exports); \ No newline at end of file diff --git a/test/readAllEventsForward_test.js b/test/readAllEventsForward_test.js new file mode 100644 index 0000000..589021d --- /dev/null +++ b/test/readAllEventsForward_test.js @@ -0,0 +1,74 @@ +var util = require('util'); +var uuid = require('uuid'); +var client = require('../src/client'); + +const numberOfStreams = 20; +const maxBatch = 10; +const minBatch = 1; +const maxCount = 500; +const allCredentials = new client.UserCredentials("admin", "changeit"); + +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +module.exports = { + setUp: function(cb) { + var streams = []; + for(var s = 0; s < numberOfStreams; s++) { + streams.push('test-' + uuid.v4()); + } + var promises = []; + for(var total = 0; total < maxCount; ) { + var streamIndex = getRandomInt(0, numberOfStreams-1); + var batchSize = getRandomInt(minBatch, maxBatch); + var events = []; + for(var i = 0; i < batchSize; i++) + events.push(client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, null, 'anEvent')); + promises.push(this.conn.appendToStream(streams[streamIndex], client.expectedVersion.any, events)); + total += batchSize; + } + Promise.all(promises) + .then(function() { + cb(); + }) + .catch(function(err) { + cb(err); + }) + }, + 'Read All Events Forward Happy Path': function(test) { + this.conn.readAllEventsForward(client.positions.start, maxCount, false, allCredentials) + .then(function(slice) { + test.areEqual('slice.readDirection', slice.readDirection, 'forward'); + test.areEqual('slice.fromPosition', slice.fromPosition, client.positions.start); + test.ok(slice.nextPosition.compareTo(client.positions.start) > 0, "slice.nextPosition is not greater than start."); + test.areEqual('slice.isEndOfStream', slice.isEndOfStream, false); + test.areEqual('slice.events.length', slice.events.length, maxCount); + var lastPosition = client.positions.start; + for(var i = 0; i < maxCount; i++) { + test.ok(slice.events[i].originalPosition.compareTo(lastPosition) > 0, + util.format("wrong order at slice.events[%d].", i)); + lastPosition = slice.events[i].originalPosition; + } + test.done(); + }) + .catch(function(err) { + test.done(err); + }) + }, + 'Read All Events Forward With No Access': function(test) { + this.conn.readAllEventsForward(client.positions.start, maxCount) + .then(function(slice) { + test.fail("readAllEventsForward succeeded but should have failed."); + test.done(); + }) + .catch(function(err) { + var isAccessDenied = err instanceof client.AccessDeniedError; + test.ok(isAccessDenied, "readAllEventsForward should have failed with AccessDeniedError, got " + err.constructor.name); + if (isAccessDenied) return test.done(); + test.done(err); + }); + } +}; + +require('./common/base_test').init(module.exports); \ No newline at end of file diff --git a/test/readStreamEventsBackward_test.js b/test/readStreamEventsBackward_test.js new file mode 100644 index 0000000..10abbb9 --- /dev/null +++ b/test/readStreamEventsBackward_test.js @@ -0,0 +1,105 @@ +var util = require('util'); +var uuid = require('uuid'); +var client = require('../src/client'); + +const streamSize = 100; + +module.exports = { + setUp: function(cb) { + this.eventsData = []; + for(var i = 0; i < streamSize; i++) + this.eventsData.push(client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, null, 'anEvent')); + this.conn.appendToStream(this.testStreamName, client.expectedVersion.noStream, this.eventsData) + .then(function() { + cb(); + }) + .catch(cb); + }, + 'Read Stream Events Backward Happy Path': function(test) { + var self = this; + this.conn.readStreamEventsBackward(this.testStreamName, streamSize-1, streamSize) + .then(function(slice) { + test.areEqual('slice.status', slice.status, client.eventReadStatus.Success); + test.areEqual('slice.stream', slice.stream, self.testStreamName); + test.areEqual('slice.fromEventNumber', slice.fromEventNumber, streamSize-1); + test.areEqual('slice.readDirection', slice.readDirection, 'backward'); + test.areEqual('slice.nextEventNumber', slice.nextEventNumber, -1); + test.areEqual('slice.lastEventNumber', slice.lastEventNumber, streamSize-1); + test.areEqual('slice.isEndOfStream', slice.isEndOfStream, true); + for(var i = 0; i < streamSize; i++) { + var reverseIndex = streamSize - i - 1; + test.eventEqualEventData('slice.events[' + i + ']', slice.events[i], self.eventsData[reverseIndex]); + test.areEqual('slice.events[' + i + '].originalEventNumber', slice.events[i].originalEventNumber, reverseIndex); + } + test.done(); + }) + .catch(function(err) { + test.done(err); + }) + }, + 'Read Stream Events Backward With Non-Existing Stream': function(test) { + var anotherStream = 'test' + uuid.v4(); + this.conn.readStreamEventsBackward(anotherStream, streamSize-1, streamSize) + .then(function(slice) { + test.areEqual('slice.status', slice.status, client.sliceReadStatus.StreamNotFound); + test.areEqual('slice.stream', slice.stream, anotherStream); + test.areEqual('slice.fromEventNumber', slice.fromEventNumber, streamSize-1); + test.areEqual('slice.events.length', slice.events.length, 0); + test.done(); + }) + .catch(function(err) { + test.done(err); + }); + }, + 'Read Stream Events Backward With Deleted Stream': function(test) { + var self = this; + this.conn.deleteStream(this.testStreamName, streamSize-1, true) + .then(function() { + return self.conn.readStreamEventsBackward(self.testStreamName, streamSize-1, streamSize) + }) + .then(function(slice) { + test.areEqual('slice.status', slice.status, client.eventReadStatus.StreamDeleted); + test.areEqual('slice.stream', slice.stream, self.testStreamName); + test.areEqual('slice.fromEventNumber', slice.fromEventNumber, streamSize-1); + test.areEqual('slice.events.length', slice.events.length, 0); + test.done(); + }) + .catch(function(err) { + test.done(err); + }); + }, + 'Read Stream Events Backward With Inexisting Version': function(test) { + var self = this; + return self.conn.readStreamEventsBackward(self.testStreamName, streamSize * 2, streamSize) + .then(function(slice) { + test.areEqual('slice.status', slice.status, client.eventReadStatus.Success); + test.areEqual('slice.stream', slice.stream, self.testStreamName); + test.areEqual('slice.fromEventNumber', slice.fromEventNumber, streamSize*2); + test.areEqual('slice.events.length', slice.events.length, 0); + test.done(); + }) + .catch(function(err) { + test.done(err); + }); + }, + 'Read Stream Events Backward With No Access': function(test) { + var self = this; + var metadata = {$acl: {$r: '$admins'}}; + this.conn.setStreamMetadataRaw(self.testStreamName, client.expectedVersion.noStream, metadata) + .then(function(){ + return self.conn.readStreamEventsBackward(self.testStreamName, streamSize-1, streamSize); + }) + .then(function(slice) { + test.fail("readStreamEventsBackward succeeded but should have failed."); + test.done(); + }) + .catch(function(err) { + var isAccessDenied = err instanceof client.AccessDeniedError; + test.ok(isAccessDenied, "readStreamEventsBackward should have failed with AccessDeniedError, got " + err.constructor.name); + if (isAccessDenied) return test.done(); + test.done(err); + }); + } +}; + +require('./common/base_test').init(module.exports); \ No newline at end of file diff --git a/test/readStreamEventsForward_test.js b/test/readStreamEventsForward_test.js new file mode 100644 index 0000000..8fbb6ba --- /dev/null +++ b/test/readStreamEventsForward_test.js @@ -0,0 +1,104 @@ +var util = require('util'); +var uuid = require('uuid'); +var client = require('../src/client'); + +const streamSize = 100; + +module.exports = { + setUp: function(cb) { + this.eventsData = []; + for(var i = 0; i < streamSize; i++) + this.eventsData.push(client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, null, 'anEvent')); + this.conn.appendToStream(this.testStreamName, client.expectedVersion.noStream, this.eventsData) + .then(function() { + cb(); + }) + .catch(cb); + }, + 'Read Stream Events Forward Happy Path': function(test) { + var self = this; + this.conn.readStreamEventsForward(this.testStreamName, 0, streamSize) + .then(function(slice) { + test.areEqual('slice.status', slice.status, client.eventReadStatus.Success); + test.areEqual('slice.stream', slice.stream, self.testStreamName); + test.areEqual('slice.fromEventNumber', slice.fromEventNumber, 0); + test.areEqual('slice.readDirection', slice.readDirection, 'forward'); + test.areEqual('slice.nextEventNumber', slice.nextEventNumber, streamSize); + test.areEqual('slice.lastEventNumber', slice.lastEventNumber, streamSize-1); + test.areEqual('slice.isEndOfStream', slice.isEndOfStream, true); + for(var i = 0; i < streamSize; i++) { + test.eventEqualEventData('slice.events[' + i + ']', slice.events[i], self.eventsData[i]); + test.areEqual('slice.events[' + i + '].originalEventNumber', slice.events[i].originalEventNumber, i); + } + test.done(); + }) + .catch(function(err) { + test.done(err); + }) + }, + 'Read Stream Events Forward With Non-Existing Stream': function(test) { + var anotherStream = 'test' + uuid.v4(); + this.conn.readStreamEventsForward(anotherStream, 0, streamSize) + .then(function(slice) { + test.areEqual('slice.status', slice.status, client.sliceReadStatus.StreamNotFound); + test.areEqual('slice.stream', slice.stream, anotherStream); + test.areEqual('slice.fromEventNumber', slice.fromEventNumber, 0); + test.areEqual('slice.events.length', slice.events.length, 0); + test.done(); + }) + .catch(function(err) { + test.done(err); + }); + }, + 'Read Stream Events Forward With Deleted Stream': function(test) { + var self = this; + this.conn.deleteStream(this.testStreamName, streamSize-1, true) + .then(function() { + return self.conn.readStreamEventsForward(self.testStreamName, 0, streamSize) + }) + .then(function(slice) { + test.areEqual('slice.status', slice.status, client.eventReadStatus.StreamDeleted); + test.areEqual('slice.stream', slice.stream, self.testStreamName); + test.areEqual('slice.fromEventNumber', slice.fromEventNumber, 0); + test.areEqual('slice.events.length', slice.events.length, 0); + test.done(); + }) + .catch(function(err) { + test.done(err); + }); + }, + 'Read Stream Events Forward With Inexisting Version': function(test) { + var self = this; + return self.conn.readStreamEventsForward(self.testStreamName, streamSize * 2, streamSize) + .then(function(slice) { + test.areEqual('slice.status', slice.status, client.eventReadStatus.Success); + test.areEqual('slice.stream', slice.stream, self.testStreamName); + test.areEqual('slice.fromEventNumber', slice.fromEventNumber, streamSize*2); + test.areEqual('slice.events.length', slice.events.length, 0); + test.done(); + }) + .catch(function(err) { + test.done(err); + }); + }, + 'Read Stream Events Forward With No Access': function(test) { + var self = this; + var metadata = {$acl: {$r: '$admins'}}; + this.conn.setStreamMetadataRaw(self.testStreamName, client.expectedVersion.noStream, metadata) + .then(function(){ + return self.conn.readStreamEventsForward(self.testStreamName, 0, streamSize); + }) + .then(function(slice) { + test.fail("readStreamEventsForward succeeded but should have failed."); + test.done(); + }) + .catch(function(err) { + var isAccessDenied = err instanceof client.AccessDeniedError; + test.ok(isAccessDenied, "readStreamEventsForward should have failed with AccessDeniedError, got " + err.constructor.name); + if (isAccessDenied) return test.done(); + test.done(err); + }); + } +}; + +require('./common/base_test').init(module.exports); \ No newline at end of file