Add failure scenarios tests for connection, appendToStream, deleteStream (wip)
This commit is contained in:
parent
b0a0af536d
commit
0b63df85e7
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
39
src/common/log/fileLogger.js
Normal file
39
src/common/log/fileLogger.js
Normal 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;
|
16
src/common/systemMetadata.js
Normal file
16
src/common/systemMetadata.js
Normal 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;
|
|
@ -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(""));
|
||||||
};
|
};
|
|
@ -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:
|
||||||
|
|
12
src/errors/accessDeniedError.js
Normal file
12
src/errors/accessDeniedError.js
Normal 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;
|
11
src/errors/streamDeletedError.js
Normal file
11
src/errors/streamDeletedError.js
Normal 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;
|
13
src/errors/wrongExpectedVersionError.js
Normal file
13
src/errors/wrongExpectedVersionError.js
Normal 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;
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
126
test/appendToStream_test.js
Normal 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
57
test/connection_test.js
Normal 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
90
test/deleteStream_test.js
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user