Add failure scenarios tests for connection, appendToStream, deleteStream (wip)

This commit is contained in:
Nicolas Dextraze 2016-03-11 15:55:27 -08:00
parent b0a0af536d
commit 0b63df85e7
16 changed files with 420 additions and 32 deletions

View File

@ -43,8 +43,12 @@ module.exports.UserCredentials = require('./systemData/userCredentials');
module.exports.EventData = EventData; module.exports.EventData = EventData;
module.exports.PersistentSubscriptionSettings = require('./persistentSubscriptionSettings'); module.exports.PersistentSubscriptionSettings = require('./persistentSubscriptionSettings');
module.exports.SystemConsumerStrategies = require('./systemConsumerStrategies'); 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.expectedVersion = expectedVersion;
module.exports.positions = positions; module.exports.positions = positions;
module.exports.systemMetadata = require('./common/systemMetadata');
// Helper functions // Helper functions
module.exports.createConnection = module.exports.EventStoreConnection.create; module.exports.createConnection = module.exports.EventStoreConnection.create;

View File

@ -7,6 +7,9 @@ var InspectionResult = require('./../systemData/inspectionResult');
var ClientMessage = require('../messages/clientMessage'); var ClientMessage = require('../messages/clientMessage');
var WriteResult = require('../results').WriteResult; var WriteResult = require('../results').WriteResult;
var Position = require('../results').Position; 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'); var OperationBase = require('../clientOperations/operationBase');
@ -52,17 +55,16 @@ AppendToStreamOperation.prototype._inspectResponse = function(response) {
this._wasCommitTimeout = true; this._wasCommitTimeout = true;
return new InspectionResult(InspectionDecision.Retry, "CommitTimeout"); return new InspectionResult(InspectionDecision.Retry, "CommitTimeout");
case ClientMessage.OperationResult.WrongExpectedVersion: case ClientMessage.OperationResult.WrongExpectedVersion:
var err = ["Append failed due to WrongExpectedVersion. Stream: ", this._stream,", Expected version: ", this._expectedVersion].join(''); this.fail(new WrongExpectedVersionError("Append", this._stream, this._expectedVersion));
this.fail(new Error(err));
return new InspectionResult(InspectionDecision.EndOperation, "WrongExpectedVersion"); return new InspectionResult(InspectionDecision.EndOperation, "WrongExpectedVersion");
case ClientMessage.OperationResult.StreamDeleted: 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"); return new InspectionResult(InspectionDecision.EndOperation, "StreamDeleted");
case ClientMessage.OperationResult.InvalidTransaction: case ClientMessage.OperationResult.InvalidTransaction:
this.fail(new Error("Invalid transaction.")); this.fail(new Error("Invalid transaction."));
return new InspectionResult(InspectionDecision.EndOperation, "InvalidTransaction"); return new InspectionResult(InspectionDecision.EndOperation, "InvalidTransaction");
case ClientMessage.OperationResult.AccessDenied: 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"); return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied");
default: default:
throw new Error("Unexpected OperationResult: " + response.result); throw new Error("Unexpected OperationResult: " + response.result);

View File

@ -5,7 +5,7 @@ var TcpCommand = require('../systemData/tcpCommand');
var TcpFlags = require('../systemData/tcpFlags'); var TcpFlags = require('../systemData/tcpFlags');
var InspectionDecision = require('../systemData/inspectionDecision'); var InspectionDecision = require('../systemData/inspectionDecision');
var ClientMessage = require('../messages/clientMessage'); var ClientMessage = require('../messages/clientMessage');
var createInspectionResult = require('./../systemData/inspectionResult'); var InspectionResult = require('./../systemData/inspectionResult');
var createBufferSegment = require('../common/bufferSegment'); var createBufferSegment = require('../common/bufferSegment');
function OperationBase(log, cb, requestCommand, responseCommand, userCredentials) { function OperationBase(log, cb, requestCommand, responseCommand, userCredentials) {
@ -79,7 +79,7 @@ OperationBase.prototype.inspectPackage = function(pkg) {
} }
} catch(e) { } catch(e) {
this.fail(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) {} } catch(e) {}
//TODO typed error //TODO typed error
this.fail(new Error("Authentication error: " + message)); this.fail(new Error("Authentication error: " + message));
return createInspectionResult(InspectionDecision.EndOperation, "NotAuthenticated"); return new InspectionResult(InspectionDecision.EndOperation, "NotAuthenticated");
}; };
OperationBase.prototype._inspectBadRequest = function(pkg) OperationBase.prototype._inspectBadRequest = function(pkg)
@ -102,7 +102,7 @@ OperationBase.prototype._inspectBadRequest = function(pkg)
} catch(e) {} } catch(e) {}
//TODO typed error //TODO typed error
this.fail(new Error("Bad request: " + message)); 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) OperationBase.prototype._inspectNotHandled = function(pkg)
@ -111,20 +111,20 @@ OperationBase.prototype._inspectNotHandled = function(pkg)
switch (message.reason) switch (message.reason)
{ {
case ClientMessage.NotHandled.NotHandledReason.NotReady: case ClientMessage.NotHandled.NotHandledReason.NotReady:
return createInspectionResult(InspectionDecision.Retry, "NotHandled - NotReady"); return new InspectionResult(InspectionDecision.Retry, "NotHandled - NotReady");
case ClientMessage.NotHandled.NotHandledReason.TooBusy: case ClientMessage.NotHandled.NotHandledReason.TooBusy:
return createInspectionResult(InspectionDecision.Retry, "NotHandled - TooBusy"); return new InspectionResult(InspectionDecision.Retry, "NotHandled - TooBusy");
case ClientMessage.NotHandled.NotHandledReason.NotMaster: case ClientMessage.NotHandled.NotHandledReason.NotMaster:
var masterInfo = ClientMessage.NotHandled.MasterInfo.decode(message.additional_info); 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_tcp_address, port: masterInfo.external_tcp_port},
{host: masterInfo.external_secure_tcp_address, port: masterInfo.external_secure_tcp_port}); {host: masterInfo.external_secure_tcp_address, port: masterInfo.external_secure_tcp_port});
default: default:
this.log.error("Unknown NotHandledReason: %s.", message.reason); this.log.error("Unknown NotHandledReason: %s.", message.reason);
return createInspectionResult(InspectionDecision.Retry, "NotHandled - <unknown>"); return new InspectionResult(InspectionDecision.Retry, "NotHandled - <unknown>");
} }
}; };
@ -141,7 +141,7 @@ OperationBase.prototype._inspectUnexpectedCommand = function(pkg, expectedComman
this.constructor.name, this, pkg.data); 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)))); 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));
}; };

View File

@ -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;

View File

@ -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;

View File

@ -8,4 +8,16 @@ module.exports.notNullOrEmpty = function(value, name) {
module.exports.notNull = function(value, name) { module.exports.notNull = function(value, name) {
if (value === null) if (value === null)
throw new Error(name + " is 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(""));
}; };

View File

@ -115,15 +115,15 @@ EventStoreConnectionLogicHandler.prototype.enqueueMessage = function(msg) {
EventStoreConnectionLogicHandler.prototype._discoverEndpoint = function(cb) { EventStoreConnectionLogicHandler.prototype._discoverEndpoint = function(cb) {
this._logDebug('DiscoverEndpoint'); this._logDebug('DiscoverEndpoint');
if (this._state != ConnectionState.Connecting) return; if (this._state !== ConnectionState.Connecting) return;
if (this._connectingPhase != ConnectingPhase.Reconnecting) return; if (this._connectingPhase !== ConnectingPhase.Reconnecting) return;
this._connectingPhase = ConnectingPhase.EndPointDiscovery; this._connectingPhase = ConnectingPhase.EndPointDiscovery;
cb = cb || function() {}; cb = cb || function() {};
var self = this; 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){ .then(function(nodeEndpoints){
self.enqueueMessage(new messages.EstablishTcpConnectionMessage(nodeEndpoints)); self.enqueueMessage(new messages.EstablishTcpConnectionMessage(nodeEndpoints));
cb(); cb();
@ -581,7 +581,6 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() {
{ {
case ConnectionState.Init: break; case ConnectionState.Init: break;
case ConnectionState.Connecting: case ConnectionState.Connecting:
{
if (this._connectingPhase == ConnectingPhase.Reconnecting && Date.now() - this._reconnInfo.timeStamp >= this._settings.reconnectionDelay) if (this._connectingPhase == ConnectingPhase.Reconnecting && Date.now() - this._reconnInfo.timeStamp >= this._settings.reconnectionDelay)
{ {
this._logDebug("TimerTick checking reconnection..."); this._logDebug("TimerTick checking reconnection...");
@ -595,17 +594,15 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() {
this._discoverEndpoint(null); 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.emit('authenticationFailed', "Authentication timed out.");
this._goToConnectedState(); this._goToConnectedState();
} }
if (this._connectingPhase > ConnectingPhase.ConnectionEstablishing) else if (this._connectingPhase > ConnectingPhase.ConnectionEstablishing)
this._manageHeartbeats(); this._manageHeartbeats();
break; break;
}
case ConnectionState.Connected: case ConnectionState.Connected:
{
// operations timeouts are checked only if connection is established and check period time passed // operations timeouts are checked only if connection is established and check period time passed
if (Date.now() - this._lastTimeoutsTimeStamp >= this._settings.operationTimeoutCheckPeriod) if (Date.now() - this._lastTimeoutsTimeStamp >= this._settings.operationTimeoutCheckPeriod)
{ {
@ -619,7 +616,6 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() {
} }
this._manageHeartbeats(); this._manageHeartbeats();
break; break;
}
case ConnectionState.Closed: case ConnectionState.Closed:
break; break;
default: default:

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -96,8 +96,10 @@ EventStoreNodeConnection.prototype.close = function() {
* @returns {Promise.<DeleteResult>} * @returns {Promise.<DeleteResult>}
*/ */
EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersion, hardDelete, userCredentials) { EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersion, hardDelete, userCredentials) {
if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string."); ensure.notNullOrEmpty(stream, "stream");
if (typeof expectedVersion !== 'number' || expectedVersion % 1 !== 0) throw new TypeError("expectedVersion must be an integer."); ensure.isInteger(expectedVersion, "expectedVersion");
hardDelete = !!hardDelete;
userCredentials = userCredentials || null;
var self = this; var self = this;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
@ -107,8 +109,7 @@ EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersi
} }
var deleteStreamOperation = new DeleteStreamOperation( var deleteStreamOperation = new DeleteStreamOperation(
self._settings.log, cb, self._settings.requireMaster, stream, expectedVersion, self._settings.log, cb, self._settings.requireMaster, stream, expectedVersion, hardDelete, userCredentials);
!!hardDelete, userCredentials || null);
self._enqueueOperation(deleteStreamOperation); self._enqueueOperation(deleteStreamOperation);
}); });
}; };
@ -117,14 +118,17 @@ EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersi
* Append events to a stream (async) * Append events to a stream (async)
* @param {string} stream The name of the stream to which to append. * @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 {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.<EventData>} events The events to append. * @param {EventData[]|EventData} events The event(s) to append.
* @param {UserCredentials} [userCredentials] User credentials * @param {UserCredentials} [userCredentials] User credentials
* @returns {Promise.<WriteResult>} * @returns {Promise.<WriteResult>}
*/ */
EventStoreNodeConnection.prototype.appendToStream = function(stream, expectedVersion, events, userCredentials) { EventStoreNodeConnection.prototype.appendToStream = function(stream, expectedVersion, events, userCredentials) {
if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string."); ensure.notNullOrEmpty(stream, "stream");
if (typeof expectedVersion !== 'number' || expectedVersion % 1 !== 0) throw new TypeError("expectedVersion must be an integer."); ensure.isInteger(expectedVersion, "expectedVersion");
if (!Array.isArray(events)) throw new TypeError("events must be an array."); if (!Array.isArray(events))
events = [events];
ensure.isArrayOf(EventData, events, "events");
userCredentials = userCredentials || null;
var self = this; var self = this;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
@ -133,7 +137,7 @@ EventStoreNodeConnection.prototype.appendToStream = function(stream, expectedVer
resolve(result); resolve(result);
} }
var operation = new AppendToStreamOperation(self._settings.log, cb, self._settings.requireMaster, stream, var operation = new AppendToStreamOperation(self._settings.log, cb, self._settings.requireMaster, stream,
expectedVersion, events, userCredentials || null); expectedVersion, events, userCredentials);
self._enqueueOperation(operation); self._enqueueOperation(operation);
}); });
}; };
@ -590,7 +594,7 @@ EventStoreNodeConnection.prototype.getStreamMetadataRaw = function(stream, userC
case results.EventReadStatus.NoStream: case results.EventReadStatus.NoStream:
return new results.RawStreamMetadataResult(stream, false, -1, null); return new results.RawStreamMetadataResult(stream, false, -1, null);
case results.EventReadStatus.StreamDeleted: case results.EventReadStatus.StreamDeleted:
return new results.RawStreamMetadataResult(stream, true, Number.MAX_VALUE, null); return new results.RawStreamMetadataResult(stream, true, 0x7fffffff, null);
default: default:
throw new Error(util.format("Unexpected ReadEventResult: %s.", res.status)); throw new Error(util.format("Unexpected ReadEventResult: %s.", res.status));
} }

View File

@ -16,11 +16,13 @@ function TcpConnection(log, connectionId, remoteEndPoint, onConnectionClosed) {
this._receiveQueue = []; this._receiveQueue = [];
Object.defineProperty(this, 'remoteEndPoint', { Object.defineProperty(this, 'remoteEndPoint', {
enumerable: true,
get: function() { get: function() {
return this._remoteEndPoint; return this._remoteEndPoint;
} }
}); });
Object.defineProperty(this, 'localEndPoint', { Object.defineProperty(this, 'localEndPoint', {
enumerable: true,
get: function() { get: function() {
return this._localEndPoint; return this._localEndPoint;
} }

View File

@ -67,21 +67,25 @@ function TcpPackageConnection(
}); });
} }
Object.defineProperty(TcpPackageConnection.prototype, 'connectionId', { Object.defineProperty(TcpPackageConnection.prototype, 'connectionId', {
enumerable: true,
get: function() { get: function() {
return this._connectionId; return this._connectionId;
} }
}); });
Object.defineProperty(TcpPackageConnection.prototype, 'isClosed', { Object.defineProperty(TcpPackageConnection.prototype, 'isClosed', {
enumerable: true,
get: function() { get: function() {
return this._connection.isClosed; return this._connection.isClosed;
} }
}); });
Object.defineProperty(TcpPackageConnection.prototype, 'remoteEndPoint', { Object.defineProperty(TcpPackageConnection.prototype, 'remoteEndPoint', {
enumerable: true,
get: function() { get: function() {
return this._connection.remoteEndPoint; return this._connection.remoteEndPoint;
} }
}); });
Object.defineProperty(TcpPackageConnection.prototype, 'localEndPoint', { Object.defineProperty(TcpPackageConnection.prototype, 'localEndPoint', {
enumerable: true,
get: function() { get: function() {
return this._connection.localEndPoint; return this._connection.localEndPoint;
} }

126
test/appendToStream_test.js Normal file
View File

@ -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();
});
}
};

57
test/connection_test.js Normal file
View File

@ -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();
});
}
};

90
test/deleteStream_test.js Normal file
View File

@ -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);
});
}
*/
};