diff --git a/src/client.js b/src/client.js index c401155..5f2391d 100644 --- a/src/client.js +++ b/src/client.js @@ -43,8 +43,12 @@ module.exports.UserCredentials = require('./systemData/userCredentials'); module.exports.EventData = EventData; module.exports.PersistentSubscriptionSettings = require('./persistentSubscriptionSettings'); module.exports.SystemConsumerStrategies = require('./systemConsumerStrategies'); +module.exports.WrongExpectedVersionError = require('./errors/wrongExpectedVersionError'); +module.exports.StreamDeletedError = require('./errors/streamDeletedError'); +module.exports.AccessDeniedError = require('./errors/accessDeniedError'); module.exports.expectedVersion = expectedVersion; module.exports.positions = positions; +module.exports.systemMetadata = require('./common/systemMetadata'); // Helper functions module.exports.createConnection = module.exports.EventStoreConnection.create; diff --git a/src/clientOperations/appendToStreamOperation.js b/src/clientOperations/appendToStreamOperation.js index 277fc73..bfc2777 100644 --- a/src/clientOperations/appendToStreamOperation.js +++ b/src/clientOperations/appendToStreamOperation.js @@ -7,6 +7,9 @@ var InspectionResult = require('./../systemData/inspectionResult'); var ClientMessage = require('../messages/clientMessage'); var WriteResult = require('../results').WriteResult; var Position = require('../results').Position; +var WrongExpectedVersionError = require('../errors/wrongExpectedVersionError'); +var StreamDeletedError = require('../errors/streamDeletedError'); +var AccessDeniedError = require('../errors/accessDeniedError'); var OperationBase = require('../clientOperations/operationBase'); @@ -52,17 +55,16 @@ AppendToStreamOperation.prototype._inspectResponse = function(response) { this._wasCommitTimeout = true; return new InspectionResult(InspectionDecision.Retry, "CommitTimeout"); case ClientMessage.OperationResult.WrongExpectedVersion: - var err = ["Append failed due to WrongExpectedVersion. Stream: ", this._stream,", Expected version: ", this._expectedVersion].join(''); - this.fail(new Error(err)); + this.fail(new WrongExpectedVersionError("Append", this._stream, this._expectedVersion)); return new InspectionResult(InspectionDecision.EndOperation, "WrongExpectedVersion"); case ClientMessage.OperationResult.StreamDeleted: - this.fail(new Error("Stream deleted. Stream: " + this._stream)); + this.fail(new StreamDeletedError(this._stream)); return new InspectionResult(InspectionDecision.EndOperation, "StreamDeleted"); case ClientMessage.OperationResult.InvalidTransaction: this.fail(new Error("Invalid transaction.")); return new InspectionResult(InspectionDecision.EndOperation, "InvalidTransaction"); case ClientMessage.OperationResult.AccessDenied: - this.fail(new Error(["Write access denied for stream '", this._stream, "'."].join(''))); + this.fail(new AccessDeniedError("Write", this._stream)); return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied"); default: throw new Error("Unexpected OperationResult: " + response.result); diff --git a/src/clientOperations/operationBase.js b/src/clientOperations/operationBase.js index 1dc9197..1132463 100644 --- a/src/clientOperations/operationBase.js +++ b/src/clientOperations/operationBase.js @@ -5,7 +5,7 @@ var TcpCommand = require('../systemData/tcpCommand'); var TcpFlags = require('../systemData/tcpFlags'); var InspectionDecision = require('../systemData/inspectionDecision'); var ClientMessage = require('../messages/clientMessage'); -var createInspectionResult = require('./../systemData/inspectionResult'); +var InspectionResult = require('./../systemData/inspectionResult'); var createBufferSegment = require('../common/bufferSegment'); function OperationBase(log, cb, requestCommand, responseCommand, userCredentials) { @@ -79,7 +79,7 @@ OperationBase.prototype.inspectPackage = function(pkg) { } } catch(e) { this.fail(e); - return createInspectionResult(InspectionDecision.EndOperation, "Error - " + e.message); + return new InspectionResult(InspectionDecision.EndOperation, "Error - " + e.message); } }; @@ -91,7 +91,7 @@ OperationBase.prototype._inspectNotAuthenticated = function(pkg) } catch(e) {} //TODO typed error this.fail(new Error("Authentication error: " + message)); - return createInspectionResult(InspectionDecision.EndOperation, "NotAuthenticated"); + return new InspectionResult(InspectionDecision.EndOperation, "NotAuthenticated"); }; OperationBase.prototype._inspectBadRequest = function(pkg) @@ -102,7 +102,7 @@ OperationBase.prototype._inspectBadRequest = function(pkg) } catch(e) {} //TODO typed error this.fail(new Error("Bad request: " + message)); - return createInspectionResult(InspectionDecision.EndOperation, "BadRequest - " + message); + return new InspectionResult(InspectionDecision.EndOperation, "BadRequest - " + message); }; OperationBase.prototype._inspectNotHandled = function(pkg) @@ -111,20 +111,20 @@ OperationBase.prototype._inspectNotHandled = function(pkg) switch (message.reason) { case ClientMessage.NotHandled.NotHandledReason.NotReady: - return createInspectionResult(InspectionDecision.Retry, "NotHandled - NotReady"); + return new InspectionResult(InspectionDecision.Retry, "NotHandled - NotReady"); case ClientMessage.NotHandled.NotHandledReason.TooBusy: - return createInspectionResult(InspectionDecision.Retry, "NotHandled - TooBusy"); + return new InspectionResult(InspectionDecision.Retry, "NotHandled - TooBusy"); case ClientMessage.NotHandled.NotHandledReason.NotMaster: var masterInfo = ClientMessage.NotHandled.MasterInfo.decode(message.additional_info); - return new InspectionResult(InspectionDecision.Reconnect, "NotHandled - NotMaster", + return new new InspectionResult(InspectionDecision.Reconnect, "NotHandled - NotMaster", {host: masterInfo.external_tcp_address, port: masterInfo.external_tcp_port}, {host: masterInfo.external_secure_tcp_address, port: masterInfo.external_secure_tcp_port}); default: this.log.error("Unknown NotHandledReason: %s.", message.reason); - return createInspectionResult(InspectionDecision.Retry, "NotHandled - "); + return new InspectionResult(InspectionDecision.Retry, "NotHandled - "); } }; @@ -141,7 +141,7 @@ OperationBase.prototype._inspectUnexpectedCommand = function(pkg, expectedComman this.constructor.name, this, pkg.data); this.fail(new Error(util.format("Unexpected command. Expecting %s got %s.", TcpCommand.getName(expectedCommand), TcpCommand.getName(pkg.command)))); - return createInspectionResult(InspectionDecision.EndOperation, "Unexpected command - " + TcpCommand.getName(pkg.command)); + return new InspectionResult(InspectionDecision.EndOperation, "Unexpected command - " + TcpCommand.getName(pkg.command)); }; diff --git a/src/common/log/fileLogger.js b/src/common/log/fileLogger.js new file mode 100644 index 0000000..dfcbdc0 --- /dev/null +++ b/src/common/log/fileLogger.js @@ -0,0 +1,39 @@ +var util = require('util'); +var fs = require('fs'); +var os = require('os'); + +function FileLogger(filePath, append) { + this._filePath = filePath; + if (!append) { + try { + fs.unlink(filePath); + } catch(e) {} + } +} + +function createLine(level, args, argsStartIndex) { + var msg = util.format.apply(util, Array.prototype.slice.call(args, argsStartIndex)); + return util.format('%s %s - %s%s', new Date().toISOString(), level, msg, os.EOL); +} + +FileLogger.prototype.debug = function() { + var line = createLine('DEBUG', arguments, 0); + fs.appendFileSync(this._filePath, line); +}; + +FileLogger.prototype.info = function() { + var line = createLine('INFO', arguments, 0); + fs.appendFileSync(this._filePath, line); +}; + +FileLogger.prototype.error = function(e) { + var hasError = e instanceof Error; + var line = createLine('ERROR', arguments, hasError ? 1 : 0); + if (hasError) { + line += e.stack + os.EOL; + } + fs.appendFileSync(this._filePath, line); +}; + + +module.exports = FileLogger; \ No newline at end of file diff --git a/src/common/systemMetadata.js b/src/common/systemMetadata.js new file mode 100644 index 0000000..522b12d --- /dev/null +++ b/src/common/systemMetadata.js @@ -0,0 +1,16 @@ +const SystemMetadata = { + maxAge: '$maxAge', + maxCount: '$maxCount', + truncateBefore: '$tb', + cacheControl: '$cacheControl', + acl: '$acl', + aclRead: '$r', + aclWrite: '$w', + aclDelete: '$d', + aclMetaRead: '$mr', + aclMetaWrite: '$mw', + userStreamAcl: '$userStreamAcl', + systemStreamAcl: '$systemStreamAcl' +}; + +module.exports = SystemMetadata; \ No newline at end of file diff --git a/src/common/utils/ensure.js b/src/common/utils/ensure.js index 9876afe..3c2bcea 100644 --- a/src/common/utils/ensure.js +++ b/src/common/utils/ensure.js @@ -8,4 +8,16 @@ module.exports.notNullOrEmpty = function(value, name) { module.exports.notNull = function(value, name) { if (value === null) throw new Error(name + " is null."); +}; + +module.exports.isInteger = function(value, name) { + if (typeof value !== 'number' || value % 1 !== 0) + throw new TypeError(name + " is not an integer."); +}; + +module.exports.isArrayOf = function(expectedType, value, name) { + if (!Array.isArray(value)) + throw new TypeError(name + " is not an array."); + if (!value.every(function(x) { return x instanceof expectedType; })) + throw new TypeError([name, " is not an array of ", expectedType, "."].join("")); }; \ No newline at end of file diff --git a/src/core/eventStoreConnectionLogicHandler.js b/src/core/eventStoreConnectionLogicHandler.js index 6feffdd..d9c7b8f 100644 --- a/src/core/eventStoreConnectionLogicHandler.js +++ b/src/core/eventStoreConnectionLogicHandler.js @@ -115,15 +115,15 @@ EventStoreConnectionLogicHandler.prototype.enqueueMessage = function(msg) { EventStoreConnectionLogicHandler.prototype._discoverEndpoint = function(cb) { this._logDebug('DiscoverEndpoint'); - if (this._state != ConnectionState.Connecting) return; - if (this._connectingPhase != ConnectingPhase.Reconnecting) return; + if (this._state !== ConnectionState.Connecting) return; + if (this._connectingPhase !== ConnectingPhase.Reconnecting) return; this._connectingPhase = ConnectingPhase.EndPointDiscovery; cb = cb || function() {}; var self = this; - this._endpointDiscoverer.discover(this._connection != null ? this._connection.remoteEndPoint : null) + this._endpointDiscoverer.discover(this._connection !== null ? this._connection.remoteEndPoint : null) .then(function(nodeEndpoints){ self.enqueueMessage(new messages.EstablishTcpConnectionMessage(nodeEndpoints)); cb(); @@ -581,7 +581,6 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() { { case ConnectionState.Init: break; case ConnectionState.Connecting: - { if (this._connectingPhase == ConnectingPhase.Reconnecting && Date.now() - this._reconnInfo.timeStamp >= this._settings.reconnectionDelay) { this._logDebug("TimerTick checking reconnection..."); @@ -595,17 +594,15 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() { this._discoverEndpoint(null); } } - if (this._connectingPhase == ConnectingPhase.Authentication && Date.now() - this._authInfo.timeStamp >= this._settings.operationTimeout) + else if (this._connectingPhase == ConnectingPhase.Authentication && Date.now() - this._authInfo.timeStamp >= this._settings.operationTimeout) { this.emit('authenticationFailed', "Authentication timed out."); this._goToConnectedState(); } - if (this._connectingPhase > ConnectingPhase.ConnectionEstablishing) + else if (this._connectingPhase > ConnectingPhase.ConnectionEstablishing) this._manageHeartbeats(); break; - } case ConnectionState.Connected: - { // operations timeouts are checked only if connection is established and check period time passed if (Date.now() - this._lastTimeoutsTimeStamp >= this._settings.operationTimeoutCheckPeriod) { @@ -619,7 +616,6 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() { } this._manageHeartbeats(); break; - } case ConnectionState.Closed: break; default: diff --git a/src/errors/accessDeniedError.js b/src/errors/accessDeniedError.js new file mode 100644 index 0000000..d8d2beb --- /dev/null +++ b/src/errors/accessDeniedError.js @@ -0,0 +1,12 @@ +var util = require('util'); + +function AccessDeniedError(action, stream) { + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.message = util.format("%s access denied for stream '%s'.", action, stream); + this.action = action; + this.stream = stream; +} +util.inherits(AccessDeniedError, Error); + +module.exports = AccessDeniedError; \ No newline at end of file diff --git a/src/errors/streamDeletedError.js b/src/errors/streamDeletedError.js new file mode 100644 index 0000000..f7c1fcd --- /dev/null +++ b/src/errors/streamDeletedError.js @@ -0,0 +1,11 @@ +var util = require('util'); + +function StreamDeletedError(stream) { + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.message = util.format("Event stream '%s' is deleted.", stream); + this.stream = stream; +} +util.inherits(StreamDeletedError, Error); + +module.exports = StreamDeletedError; \ No newline at end of file diff --git a/src/errors/wrongExpectedVersionError.js b/src/errors/wrongExpectedVersionError.js new file mode 100644 index 0000000..a8ce711 --- /dev/null +++ b/src/errors/wrongExpectedVersionError.js @@ -0,0 +1,13 @@ +var util = require('util'); + +function WrongExpectedVersionError(action, stream, expectedVersion) { + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.message = util.format("%s failed due to WrongExpectedVersion. Stream: %s Expected version: %d.", action, stream, expectedVersion); + this.action = action; + this.stream = stream; + this.expectedVersion = expectedVersion; +} +util.inherits(WrongExpectedVersionError, Error); + +module.exports = WrongExpectedVersionError; \ No newline at end of file diff --git a/src/eventStoreNodeConnection.js b/src/eventStoreNodeConnection.js index a7a44fc..66dd273 100644 --- a/src/eventStoreNodeConnection.js +++ b/src/eventStoreNodeConnection.js @@ -96,8 +96,10 @@ EventStoreNodeConnection.prototype.close = function() { * @returns {Promise.} */ EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersion, hardDelete, userCredentials) { - if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string."); - if (typeof expectedVersion !== 'number' || expectedVersion % 1 !== 0) throw new TypeError("expectedVersion must be an integer."); + ensure.notNullOrEmpty(stream, "stream"); + ensure.isInteger(expectedVersion, "expectedVersion"); + hardDelete = !!hardDelete; + userCredentials = userCredentials || null; var self = this; return new Promise(function(resolve, reject) { @@ -107,8 +109,7 @@ EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersi } var deleteStreamOperation = new DeleteStreamOperation( - self._settings.log, cb, self._settings.requireMaster, stream, expectedVersion, - !!hardDelete, userCredentials || null); + self._settings.log, cb, self._settings.requireMaster, stream, expectedVersion, hardDelete, userCredentials); self._enqueueOperation(deleteStreamOperation); }); }; @@ -117,14 +118,17 @@ EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersi * Append events to a stream (async) * @param {string} stream The name of the stream to which to append. * @param {number} expectedVersion The version at which we currently expect the stream to be in order that an optimistic concurrency check can be performed. - * @param {Array.} events The events to append. + * @param {EventData[]|EventData} events The event(s) to append. * @param {UserCredentials} [userCredentials] User credentials * @returns {Promise.} */ EventStoreNodeConnection.prototype.appendToStream = function(stream, expectedVersion, events, userCredentials) { - if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string."); - if (typeof expectedVersion !== 'number' || expectedVersion % 1 !== 0) throw new TypeError("expectedVersion must be an integer."); - if (!Array.isArray(events)) throw new TypeError("events must be an array."); + ensure.notNullOrEmpty(stream, "stream"); + ensure.isInteger(expectedVersion, "expectedVersion"); + if (!Array.isArray(events)) + events = [events]; + ensure.isArrayOf(EventData, events, "events"); + userCredentials = userCredentials || null; var self = this; return new Promise(function(resolve, reject) { @@ -133,7 +137,7 @@ EventStoreNodeConnection.prototype.appendToStream = function(stream, expectedVer resolve(result); } var operation = new AppendToStreamOperation(self._settings.log, cb, self._settings.requireMaster, stream, - expectedVersion, events, userCredentials || null); + expectedVersion, events, userCredentials); self._enqueueOperation(operation); }); }; @@ -590,7 +594,7 @@ EventStoreNodeConnection.prototype.getStreamMetadataRaw = function(stream, userC case results.EventReadStatus.NoStream: return new results.RawStreamMetadataResult(stream, false, -1, null); case results.EventReadStatus.StreamDeleted: - return new results.RawStreamMetadataResult(stream, true, Number.MAX_VALUE, null); + return new results.RawStreamMetadataResult(stream, true, 0x7fffffff, null); default: throw new Error(util.format("Unexpected ReadEventResult: %s.", res.status)); } diff --git a/src/transport/tcp/tcpConnection.js b/src/transport/tcp/tcpConnection.js index 91ce692..d984484 100644 --- a/src/transport/tcp/tcpConnection.js +++ b/src/transport/tcp/tcpConnection.js @@ -16,11 +16,13 @@ function TcpConnection(log, connectionId, remoteEndPoint, onConnectionClosed) { this._receiveQueue = []; Object.defineProperty(this, 'remoteEndPoint', { + enumerable: true, get: function() { return this._remoteEndPoint; } }); Object.defineProperty(this, 'localEndPoint', { + enumerable: true, get: function() { return this._localEndPoint; } diff --git a/src/transport/tcp/tcpPackageConnection.js b/src/transport/tcp/tcpPackageConnection.js index 13691c1..94a9f60 100644 --- a/src/transport/tcp/tcpPackageConnection.js +++ b/src/transport/tcp/tcpPackageConnection.js @@ -67,21 +67,25 @@ function TcpPackageConnection( }); } Object.defineProperty(TcpPackageConnection.prototype, 'connectionId', { + enumerable: true, get: function() { return this._connectionId; } }); Object.defineProperty(TcpPackageConnection.prototype, 'isClosed', { + enumerable: true, get: function() { return this._connection.isClosed; } }); Object.defineProperty(TcpPackageConnection.prototype, 'remoteEndPoint', { + enumerable: true, get: function() { return this._connection.remoteEndPoint; } }); Object.defineProperty(TcpPackageConnection.prototype, 'localEndPoint', { + enumerable: true, get: function() { return this._connection.localEndPoint; } diff --git a/test/appendToStream_test.js b/test/appendToStream_test.js new file mode 100644 index 0000000..af1a5ca --- /dev/null +++ b/test/appendToStream_test.js @@ -0,0 +1,126 @@ +var uuid = require('uuid'); +var client = require('../src/client'); + +var settings = {}; +if (process.env.TESTS_VERBOSE_LOGGING === '1') { + settings.verboseLogging = true; + var FileLogger = require('../src/common/log/fileLogger'); + settings.log = new FileLogger('appendToStream_test.log'); +} + +module.exports = { + setUp: function(cb) { + this.testStreamName = 'test-' + uuid.v4(); + var connected = false; + this.conn = client.EventStoreConnection.create(settings, {host: 'localhost', port: 1113}); + this.conn.connect() + .then(function() { + //Doesn't mean anything, connection is just initiated + }) + .catch(function(err) { + cb(err); + }); + this.conn.on('closed', function(reason){ + if (connected) return; + cb(new Error("Connection failed: " + reason)); + }); + this.conn.on('connected', function() { + connected = true; + cb(); + }); + }, + tearDown: function(cb) { + this.conn.close(); + this.conn.on('closed', function() { + cb(); + }); + this.conn = null; + }, + 'Append One Event To Stream Happy Path': function(test) { + var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent'); + this.conn.appendToStream(this.testStreamName, client.expectedVersion.any, event) + .then(function(result) { + test.ok(result.nextExpectedVersion === 0, "Expected nextExpectedVersion === 0, but was " + result.nextExpectedVersion); + test.ok(result.logPosition, "No log position in result."); + test.done(); + }) + .catch(function(err) { + test.done(err); + }); + }, + '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') + ]; + this.conn.appendToStream(this.testStreamName, client.expectedVersion.any, events) + .then(function(result) { + test.ok(result.nextExpectedVersion === 1, "Expected nextExpectedVersion === 1, but was " + result.nextExpectedVersion); + test.ok(result.logPosition, "No log position in result."); + test.done(); + }) + .catch(function(err) { + test.done(err); + }); + }, + 'Append To Stream Wrong Expected Version': function(test) { + var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent'); + this.conn.appendToStream(this.testStreamName, 10, event) + .then(function(result) { + test.ok(false, "Append succeeded but should have failed."); + test.done(); + }) + .catch(function(err) { + var isWrongExpectedVersion = err instanceof client.WrongExpectedVersionError; + test.ok(isWrongExpectedVersion, "Expected WrongExpectedVersionError, got " + err.constructor.name); + if (!isWrongExpectedVersion) { + test.done(err); + return; + } + test.done(); + }); + }, + 'Append To Stream Deleted': function(test) { + var self = this; + this.conn.deleteStream(this.testStreamName, client.expectedVersion.noStream, true) + .then(function() { + var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent'); + return self.conn.appendToStream(self.testStreamName, client.expectedVersion.any, event) + }) + .then(function(result) { + test.ok(false, "Append succeeded but should have failed."); + test.done(); + }) + .catch(function(err) { + var isStreamDeleted = err instanceof client.StreamDeletedError; + test.ok(isStreamDeleted, "Expected StreamDeletedError, got " + err.constructor.name); + if (!isStreamDeleted) { + test.done(err); + return; + } + test.done(); + }); + }, + 'Append To Stream Access Denied': function(test) { + var self = this; + var metadata = {$acl: {$w: "$admins"}}; + this.conn.setStreamMetadataRaw(this.testStreamName, client.expectedVersion.noStream, metadata) + .then(function() { + var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent'); + return self.conn.appendToStream(self.testStreamName, client.expectedVersion.any, event) + }) + .then(function(result) { + test.ok(false, "Append succeeded but should have failed."); + test.done(); + }) + .catch(function(err) { + var isStreamDeleted = err instanceof client.AccessDeniedError; + test.ok(isStreamDeleted, "Expected AccessDeniedError, got " + err.constructor.name); + if (!isStreamDeleted) { + test.done(err); + return; + } + test.done(); + }); + } +}; diff --git a/test/connection_test.js b/test/connection_test.js new file mode 100644 index 0000000..f5ab48b --- /dev/null +++ b/test/connection_test.js @@ -0,0 +1,57 @@ +var util = require('util'); +var client = require('../src/client.js'); + +var consoleLogger = { + debug: function() { + var msg = util.format.apply(util, Array.prototype.slice.call(arguments)); + util.log(msg); + }, + info: function() {}, + error: function() {} +}; + +var settings = {};//verboseLogging: true, log: consoleLogger}; + +module.exports = { + 'Connect To Endpoint Happy Path': function(test) { + var tcpEndpoint = {hostname: 'localhost', port: 1113}; + var conn = client.EventStoreConnection.create({}, tcpEndpoint); + conn.connect() + .catch(function(e) { + test.done(e); + }); + conn.on('connected', function(endPoint){ + test.deepEqual(endPoint, tcpEndpoint); + done(); + }); + conn.on('error', done); + + function done(e) { + conn.close(); + if (e) { + test.done(e); + return; + } + test.done(); + } + }, + 'Connect To Endpoint That Don\'t Exist': function(test) { + var tcpEndpoint = {hostname: 'localhost', port: 1114}; + var conn = client.EventStoreConnection.create({maxReconnections: 1}, tcpEndpoint); + conn.connect() + .catch(function (e) { + test.done(e); + }); + conn.on('connected', function () { + test.ok(false, "Should not be able to connect."); + test.done(); + }); + conn.on('error', function (e) { + test.done(e); + }); + conn.on('closed', function(reason) { + test.ok(reason.indexOf("Reconnection limit reached") === 0, "Wrong expected reason."); + test.done(); + }); + } +}; diff --git a/test/deleteStream_test.js b/test/deleteStream_test.js new file mode 100644 index 0000000..72860bb --- /dev/null +++ b/test/deleteStream_test.js @@ -0,0 +1,90 @@ +var uuid = require('uuid'); +var client = require('../src/client'); + +var settings = {}; +if (process.env.TESTS_VERBOSE_LOGGING === '1') { + settings.verboseLogging = true; + var FileLogger = require('../src/common/log/fileLogger'); + settings.log = new FileLogger('deleteStream_test.log'); +} + +module.exports = { + setUp: function(cb) { + this.testStreamName = 'test-' + uuid.v4(); + var connected = false; + this.conn = client.EventStoreConnection.create(settings, {host: 'localhost', port: 1113}); + this.conn.connect() + .then(function() { + //Doesn't mean anything, connection is just initiated + }) + .catch(function(err) { + cb(err); + }); + this.conn.on('closed', function(reason){ + if (connected) return; + cb(new Error("Connection failed: " + reason)); + }); + var self = this; + this.conn.on('connected', function() { + connected = true; + var events = [ + client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent'), + client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent') + ]; + self.conn.appendToStream(self.testStreamName, client.expectedVersion.noStream, events) + .then(function() { + cb(); + }) + .catch(cb); + }); + }, + tearDown: function(cb) { + this.conn.close(); + this.conn.on('closed', function() { + cb(); + }); + this.conn = null; + }, + 'Test Delete Stream Soft Happy Path': function(test) { + var self = this; + this.conn.deleteStream(this.testStreamName, 1, false) + .then(function(result) { + test.ok(result.logPosition, "No log position in result."); + return self.conn.getStreamMetadataRaw(self.testStreamName); + }) + .then(function(metadata) { + test.ok(metadata.stream === self.testStreamName, "Metadata stream doesn't match."); + test.ok(metadata.isStreamDeleted === false, "Metadata says stream is deleted."); + test.ok(metadata.streamMetadata.$tb, "Expected Truncate Before to be set"); + test.done(); + }) + .catch(function(err) { + test.done(err); + }); + }, + /* + This test fails because of a protobufjs error. + Client.ReadEventCompleted fails to decode because ResolvedIndexedEvent.event is null and it's marked as required. + Test will pass if messages.proto is modified so that ResolvedIndexedEvent.event is optional. + Unsure if it's a protobufjs issue or a GES issue. Need to duplicate this test with .Net Client. + + 'Test Delete Stream Hard Happy Path': function(test) { + var self = this; + this.conn.deleteStream(this.testStreamName, 1, true) + .then(function(result) { + test.ok(result.logPosition, "No log position in result."); + return self.conn.getStreamMetadataRaw(self.testStreamName); + }) + .then(function(metadata) { + test.ok(metadata.stream === self.testStreamName, "Metadata stream doesn't match."); + test.ok(metadata.isStreamDeleted === true, "Metadata says stream is deleted."); + test.ok(metadata.streamMetadata === null, "Expected streamMetadata to be null."); + test.done(); + }) + .catch(function(err) { + test.done(err); + }); + } + */ + +};