feat(cluster): create way to test single/gossip/cluster

* docker-compose files to easily setup cluster or single node eventstore
* programmatic tests for single/gossip/cluster
This commit is contained in:
maniolias 2020-09-16 17:23:46 +02:00
parent e9834daa31
commit da6d059547
13 changed files with 5338 additions and 247 deletions

View File

@ -112,12 +112,27 @@ To generate a test event, open a separate console and run:
## Running the tests ## Running the tests
### Local testing
To run the tests it is recommended that you use an in-memory instance of the eventstore so you don't pollute your dev instance. To run the tests it is recommended that you use an in-memory instance of the eventstore so you don't pollute your dev instance.
EventStore.ClusterNode.exe --run-projections=all --memdb certificate-file=yourcert.pfx EventStore.ClusterNode.exe --run-projections=all --memdb certificate-file=yourcert.pfx
or or
./run-node.sh --run-projections=all --memdb certificate-file=yourcert.p12 ./run-node.sh --run-projections=all --memdb certificate-file=yourcert.p12
You can also use docker-compose :
```bash
# start the single node cluster
npm run compose:single:start
# if you want to wait for the cluster to be available
npm run compose:wait
# run the tests
npm run test
# to cleanup (stop containres, delete volumes)
npm run compose:single:stop
```
For SSL setup see: For SSL setup see:
https://eventstore.org/docs/server/setting_up_ssl/ https://eventstore.org/docs/server/setting_up_ssl/
@ -128,6 +143,29 @@ To execute the tests suites simply run
npm test npm test
### Isolated environment
To be able to run the tests for different connection types (tcp, gossip, cluster) docker-compose files are available to setup the environment and run the tests.
#### Prerequisites
* docker
* docker-compose
#### Run
To execute the tests suites for single node cluster (tcp connection) simply run
npm run test:single
To execute the tests suites for multiple nodes cluster (gossip connection) simply run
npm run test:gossip
To execute the tests suites for multiple nodes cluster (dns discovery connection) simply run
npm run test:cluster
## Porting .Net Task to Node.js ## Porting .Net Task to Node.js
Any async commands returns a [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) object in replacement of .Net Task. Any async commands returns a [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) object in replacement of .Net Task.

147
docker-compose-cluster.yaml Normal file
View File

@ -0,0 +1,147 @@
version: '3.4'
services:
eventstore.db:
image: eventstore/eventstore:release-5.0.8
environment:
- EVENTSTORE_WORKER_THREADS=5
- EVENTSTORE_CLUSTER_SIZE=3
- EVENTSTORE_RUN_PROJECTIONS=All
- EVENTSTORE_DB=/var/lib/eventstore-data
- EVENTSTORE_INDEX=/var/lib/eventstore-index
- EVENTSTORE_LOG=/var/log/eventstore
- EVENTSTORE_CLUSTER_GOSSIP_PORT=2112
- EVENTSTORE_INT_TCP_PORT=1112
- EVENTSTORE_EXT_TCP_PORT=1113
- EVENTSTORE_INT_HTTP_PORT=2112
- EVENTSTORE_EXT_HTTP_PORT=2113
- EVENTSTORE_DISCOVER_VIA_DNS=true
- EVENTSTORE_CLUSTER_DNS=eventstore.local
- EVENTSTORE_INT_IP=192.168.33.10
- EVENTSTORE_EXT_IP=192.168.33.10
ports:
- "1112:1112"
- "1113:1113"
- "2112:2112"
- "2113:2113"
networks:
app_net:
aliases:
- eventstore.local
ipv4_address: 192.168.33.10
volumes:
- type: volume
source: eventstore-volume-data
target: /var/lib/eventstore-data
- type: volume
source: eventstore-volume-index
target: /var/lib/eventstore-index
- type: volume
source: eventstore-volume-logs
target: /var/log/eventstore
eventstore.db2:
image: eventstore/eventstore:release-5.0.8
environment:
- EVENTSTORE_WORKER_THREADS=5
- EVENTSTORE_CLUSTER_SIZE=3
- EVENTSTORE_RUN_PROJECTIONS=All
- EVENTSTORE_DB=/var/lib/eventstore-data
- EVENTSTORE_INDEX=/var/lib/eventstore-index
- EVENTSTORE_LOG=/var/log/eventstore
- EVENTSTORE_CLUSTER_GOSSIP_PORT=2112
- EVENTSTORE_INT_TCP_PORT=1112
- EVENTSTORE_EXT_TCP_PORT=1113
- EVENTSTORE_INT_HTTP_PORT=2112
- EVENTSTORE_EXT_HTTP_PORT=2113
- EVENTSTORE_DISCOVER_VIA_DNS=true
- EVENTSTORE_CLUSTER_DNS=eventstore.local
- EVENTSTORE_INT_IP=192.168.33.11
- EVENTSTORE_EXT_IP=192.168.33.11
expose:
- "1113"
- "1112"
- "2112"
- "2113"
networks:
app_net:
aliases:
- eventstore.local
ipv4_address: 192.168.33.11
volumes:
- type: volume
source: eventstore-volume-data2
target: /var/lib/eventstore-data
- type: volume
source: eventstore-volume-index2
target: /var/lib/eventstore-index
- type: volume
source: eventstore-volume-logs2
target: /var/log/eventstore
eventstore.db3:
image: eventstore/eventstore:release-5.0.8
environment:
- EVENTSTORE_WORKER_THREADS=5
- EVENTSTORE_CLUSTER_SIZE=3
- EVENTSTORE_RUN_PROJECTIONS=All
- EVENTSTORE_DB=/var/lib/eventstore-data
- EVENTSTORE_INDEX=/var/lib/eventstore-index
- EVENTSTORE_LOG=/var/log/eventstore
- EVENTSTORE_CLUSTER_GOSSIP_PORT=2112
- EVENTSTORE_INT_TCP_PORT=1112
- EVENTSTORE_EXT_TCP_PORT=1113
- EVENTSTORE_INT_HTTP_PORT=2112
- EVENTSTORE_EXT_HTTP_PORT=2113
- EVENTSTORE_DISCOVER_VIA_DNS=true
- EVENTSTORE_CLUSTER_DNS=eventstore.local
- EVENTSTORE_INT_IP=192.168.33.12
- EVENTSTORE_EXT_IP=192.168.33.12
expose:
- "1113"
- "1112"
- "2112"
- "2113"
networks:
app_net:
aliases:
- eventstore.local
ipv4_address: 192.168.33.12
volumes:
- type: volume
source: eventstore-volume-data3
target: /var/lib/eventstore-data
- type: volume
source: eventstore-volume-index3
target: /var/lib/eventstore-index
- type: volume
source: eventstore-volume-logs3
target: /var/log/eventstore
nodejs:
image: node:14
working_dir: /var/code
volumes:
- .:/var/code
- /var/code/node_modules
command: bash -c "tail -f /dev/null"
networks:
- app_net
volumes:
eventstore-volume-data:
eventstore-volume-index:
eventstore-volume-logs:
eventstore-volume-data2:
eventstore-volume-index2:
eventstore-volume-logs2:
eventstore-volume-data3:
eventstore-volume-index3:
eventstore-volume-logs3:
networks:
app_net:
ipam:
driver: default
config:
- subnet: "192.168.33.0/24"

View File

@ -0,0 +1,54 @@
version: '3.4'
services:
eventstore.db:
image: eventstore/eventstore:release-5.0.8
environment:
- EVENTSTORE_CLUSTER_SIZE=1
- EVENTSTORE_RUN_PROJECTIONS=All
- EVENTSTORE_START_STANDARD_PROJECTIONS=True
- EVENTSTORE_DB=/var/lib/eventstore-data
- EVENTSTORE_INDEX=/var/lib/eventstore-index
- EVENTSTORE_LOG=/var/log/eventstore
- EVENTSTORE_EXT_TCP_PORT=1113
- EVENTSTORE_EXT_HTTP_PORT=2113
- EVENTSTORE_INT_IP=192.168.33.10
- EVENTSTORE_EXT_IP=192.168.33.10
ports:
- "1113:1113"
- "2113:2113"
volumes:
- type: volume
source: eventstore-volume-data
target: /var/lib/eventstore-data
- type: volume
source: eventstore-volume-index
target: /var/lib/eventstore-index
- type: volume
source: eventstore-volume-logs
target: /var/log/eventstore
networks:
app_net:
ipv4_address: 192.168.33.10
nodejs:
image: node:14
working_dir: /var/code
volumes:
- .:/var/code
- /var/code/node_modules
command: bash -c "tail -f /dev/null"
networks:
- app_net
volumes:
eventstore-volume-data:
eventstore-volume-index:
eventstore-volume-logs:
networks:
app_net:
ipam:
driver: default
config:
- subnet: "192.168.33.0/24"

4743
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,17 @@
"test": "nodeunit", "test": "nodeunit",
"test-debug": "TESTS_VERBOSE_LOGGING=1 nodeunit", "test-debug": "TESTS_VERBOSE_LOGGING=1 nodeunit",
"test:jest:watch": "jest --watch --coverage", "test:jest:watch": "jest --watch --coverage",
"test:single": "npm run compose:single:start && npm run compose:wait && npm run compose:single:test ; npm run compose:single:stop",
"test:gossip": "npm run compose:cluster:start && npm run compose:wait && npm run compose:gossip:test ; npm run compose:cluster:stop",
"test:cluster": "npm run compose:cluster:start && npm run compose:wait && npm run compose:cluster:test ; npm run compose:cluster:stop",
"compose:single:start": "docker-compose -f docker-compose-single.yaml up --build -d",
"compose:cluster:start": "docker-compose -f docker-compose-cluster.yaml up --build -d",
"compose:single:stop": "docker-compose -f docker-compose-single.yaml down -v --remove-orphans",
"compose:cluster:stop": "docker-compose -f docker-compose-cluster.yaml down -v --remove-orphans",
"compose:wait": "while [[ \"$(curl -s -o /dev/null -w ''%{http_code}'' localhost:2113/ping)\" != \"200\" ]]; do sleep 5; done",
"compose:single:test": "docker-compose -f docker-compose-single.yaml exec nodejs bash -c \"npm i && npm run build && EVENTSTORE_CONNECTION_TYPE=tcp EVENTSTORE_HOST=192.168.33.10 npm run test-debug\"",
"compose:cluster:test": "docker-compose -f docker-compose-cluster.yaml exec nodejs bash -c \"npm i && npm run build && EVENTSTORE_CONNECTION_TYPE=gossip EVENTSTORE_HOST=192.168.33.10 EVENTSTORE_HOST_1=192.168.33.10 EVENTSTORE_HOST_2=192.168.33.11 EVENTSTORE_HOST_3=192.168.33.12 npm run test-debug\"",
"compose:gossip:test": "docker-compose -f docker-compose-cluster.yaml exec nodejs bash -c \"npm i && npm run build && EVENTSTORE_CONNECTION_TYPE=dns EVENTSTORE_HOST=192.168.33.10 npm run test-debug\"",
"prepublishOnly": "npm run build && npm run gendocs", "prepublishOnly": "npm run build && npm run gendocs",
"gendocs": "rm -rf docs && jsdoc src -r -d docs" "gendocs": "rm -rf docs && jsdoc src -r -d docs"
}, },

View File

@ -11,38 +11,102 @@ protobufJS.util.Long = undefined;
protobufJS.configure(); protobufJS.configure();
var settings = { var settings = {
log: new NoopLogger() log: new NoopLogger(),
}; };
if (process.env.TESTS_VERBOSE_LOGGING === '1') { if (process.env.TESTS_VERBOSE_LOGGING === '1') {
settings.verboseLogging = true; settings.verboseLogging = true;
settings.log = new FileLogger('test-verbose.log'); settings.log = new FileLogger('test-verbose.log');
} }
var tcpEndPoint = {host: 'localhost', port: 1113}; function setUpWithGossipSeeds(cb) {
var gossipSeeds = [
function setUp(cb) { new client.GossipSeed({ host: process.env.EVENTSTORE_HOST_1 || '192.168.33.10', port: 2113 }),
new client.GossipSeed({ host: process.env.EVENTSTORE_HOST_2 || '192.168.33.11', port: 2113 }),
new client.GossipSeed({ host: process.env.EVENTSTORE_HOST_3 || '192.168.33.12', port: 2113 }),
];
this.log = settings.log; this.log = settings.log;
this.testStreamName = 'test-' + uuid.v4(); this.testStreamName = 'test-' + uuid.v4();
var connected = false; var connected = false;
this.conn = client.EventStoreConnection.create(settings, tcpEndPoint); this.conn = client.createConnection(settings, gossipSeeds);
this.conn.connect() this.conn
.connect()
.then(function () { .then(function () {
//Doesn't mean anything, connection is just initiated //Doesn't mean anything, connection is just initiated
settings.log.debug("Connection to %j initialized.", tcpEndPoint); settings.log.debug('Connection to %j initialized.', gossipSeeds);
}) })
.catch(function (err) { .catch(function (err) {
settings.log.error(err, "Initializing connection to %j failed.", tcpEndPoint); settings.log.error(err, 'Initializing connection to %j failed.', gossipSeeds);
cb(err); cb(err);
}); });
this.conn.on('closed', function (reason) { this.conn.on('closed', function (reason) {
if (connected) return; if (connected) return;
var error = new Error("Connection failed: " + reason); var error = new Error('Connection failed: ' + reason);
settings.log.error(error, "Connection to %j failed.", tcpEndPoint); settings.log.error(error, 'Connection to %j failed.', gossipSeeds);
cb(error); cb(error);
}); });
this.conn.on('connected', function (tcpEndPoint) { this.conn.on('connected', function (tcpEndPoint) {
if (connected) return; if (connected) return;
settings.log.debug("Connected to %j.", tcpEndPoint); settings.log.debug('Connected to %j.', tcpEndPoint);
connected = true;
cb();
});
}
function setUpWithDns(cb) {
var clusterDns = 'discover://eventstore.local:2113';
this.log = settings.log;
this.testStreamName = 'test-' + uuid.v4();
var connected = false;
this.conn = client.createConnection(settings, clusterDns);
this.conn
.connect()
.then(function () {
//Doesn't mean anything, connection is just initiated
settings.log.debug('Connection to %j initialized.', clusterDns);
})
.catch(function (err) {
settings.log.error(err, 'Initializing connection to %j failed.', clusterDns);
cb(err);
});
this.conn.on('closed', function (reason) {
if (connected) return;
var error = new Error('Connection failed: ' + reason);
settings.log.error(error, 'Connection to %j failed.', clusterDns);
cb(error);
});
this.conn.on('connected', function (tcpEndPoint) {
if (connected) return;
settings.log.debug('Connected to %j.', tcpEndPoint);
connected = true;
cb();
});
}
function setUpWithTcpEndpoint(cb) {
var tcpEndPoint = { host: process.env.EVENTSTORE_HOST || 'localhost', port: 1113 };
this.log = settings.log;
this.testStreamName = 'test-' + uuid.v4();
var connected = false;
this.conn = client.EventStoreConnection.create(settings, tcpEndPoint);
this.conn
.connect()
.then(function () {
//Doesn't mean anything, connection is just initiated
settings.log.debug('Connection to %j initialized.', tcpEndPoint);
})
.catch(function (err) {
settings.log.error(err, 'Initializing connection to %j failed.', tcpEndPoint);
cb(err);
});
this.conn.on('closed', function (reason) {
if (connected) return;
var error = new Error('Connection failed: ' + reason);
settings.log.error(error, 'Connection to %j failed.', tcpEndPoint);
cb(error);
});
this.conn.on('connected', function (tcpEndPoint) {
if (connected) return;
settings.log.debug('Connected to %j.', tcpEndPoint);
connected = true; connected = true;
cb(); cb();
}); });
@ -50,7 +114,7 @@ function setUp(cb) {
function tearDown(cb) { function tearDown(cb) {
this.conn.close(); this.conn.close();
this.conn.on('closed', function() { this.conn.on('closed', function () {
cb(); cb();
}); });
this.conn = null; this.conn = null;
@ -58,9 +122,8 @@ function tearDown(cb) {
function areEqual(name, actual, expected) { function areEqual(name, actual, expected) {
if (typeof expected !== 'object' || expected === null) if (typeof expected !== 'object' || expected === null)
this.strictEqual(actual, expected, util.format("Failed %s === %s, got %s.", name, expected, actual)); this.strictEqual(actual, expected, util.format('Failed %s === %s, got %s.', name, expected, actual));
else else this.deepEqual(actual, expected, util.format('Failed %s deepEqual %j, got %j.', name, expected, actual));
this.deepEqual(actual, expected, util.format("Failed %s deepEqual %j, got %j.", name, expected, actual));
} }
function fail(reason) { function fail(reason) {
@ -69,55 +132,75 @@ function fail(reason) {
function eventEqualEventData(name, resolvedEvent, eventData) { function eventEqualEventData(name, resolvedEvent, eventData) {
var ev = resolvedEvent.originalEvent; var ev = resolvedEvent.originalEvent;
this.ok(ev !== null, util.format("Failed %s !== null.", name + ".originalEvent")); this.ok(ev !== null, util.format('Failed %s !== null.', name + '.originalEvent'));
if (ev === null) return; if (ev === null) return;
this.areEqual(name + ".originalEvent.eventId", ev.eventId, eventData.eventId); this.areEqual(name + '.originalEvent.eventId', ev.eventId, eventData.eventId);
this.areEqual(name + ".originalEvent.eventType", ev.eventType, eventData.type); this.areEqual(name + '.originalEvent.eventType', ev.eventType, eventData.type);
this.ok(Buffer.compare(ev.data, eventData.data) === 0, name + ".originalEvent.data is not equal to original data."); this.ok(Buffer.compare(ev.data, eventData.data) === 0, name + '.originalEvent.data is not equal to original data.');
this.ok(Buffer.compare(ev.metadata, eventData.metadata) === 0, name + ".originalEvent.metadata is not equal to original metadata."); this.ok(
Buffer.compare(ev.metadata, eventData.metadata) === 0,
name + '.originalEvent.metadata is not equal to original metadata.'
);
} }
function testRecordedEvent(name, event) { function testRecordedEvent(name, event) {
this.ok(Long.isLong(event.eventNumber), name + ".eventNumber is not a Long"); this.ok(Long.isLong(event.eventNumber), name + '.eventNumber is not a Long');
this.ok(event.created instanceof Date, name + ".created is not a Date"); this.ok(event.created instanceof Date, name + '.created is not a Date');
this.ok(typeof event.createdEpoch === 'number', name + ".createdEpoch is not a number"); this.ok(typeof event.createdEpoch === 'number', name + '.createdEpoch is not a number');
} }
function testLiveEvent(name, event, evNumber) { function testLiveEvent(name, event, evNumber) {
this.ok(event.event, name + ".event not defined (or null)"); this.ok(event.event, name + '.event not defined (or null)');
this.ok(event.originalEvent, name + ".originalEvent not defined (or null)"); this.ok(event.originalEvent, name + '.originalEvent not defined (or null)');
this.ok(event.isResolved === false, name + ".isResolved should be true"); this.ok(event.isResolved === false, name + '.isResolved should be true');
this.ok(event.originalPosition instanceof client.Position, name + ".originalPosition is not an instance of Position"); this.ok(event.originalPosition instanceof client.Position, name + '.originalPosition is not an instance of Position');
this.ok(event.originalStreamId, name + ".originalStreamId not defined (or null)"); this.ok(event.originalStreamId, name + '.originalStreamId not defined (or null)');
this.ok(Long.isLong(event.originalEventNumber), name + ".originalEventNumber is not a Long"); this.ok(Long.isLong(event.originalEventNumber), name + '.originalEventNumber is not a Long');
if (typeof evNumber === 'number') { if (typeof evNumber === 'number') {
this.ok(event.originalEventNumber.toNumber() === evNumber, name + '.originalEventNumber expected ' + evNumber + ' got ' + event.originalEventNumber); this.ok(
event.originalEventNumber.toNumber() === evNumber,
name + '.originalEventNumber expected ' + evNumber + ' got ' + event.originalEventNumber
);
} }
testRecordedEvent.call(this, name + '.event', event.event); testRecordedEvent.call(this, name + '.event', event.event);
} }
function testReadEvent(name, event, evNumber) { function testReadEvent(name, event, evNumber) {
this.ok(event.event, name + ".event not defined (or null)"); this.ok(event.event, name + '.event not defined (or null)');
this.ok(event.originalEvent, name + ".originalEvent not defined (or null)"); this.ok(event.originalEvent, name + '.originalEvent not defined (or null)');
this.ok(event.isResolved === false, name + ".isResolved should be true"); this.ok(event.isResolved === false, name + '.isResolved should be true');
this.ok(event.originalPosition === null, name + ".originalPosition is not null"); this.ok(event.originalPosition === null, name + '.originalPosition is not null');
this.ok(event.originalStreamId, name + ".originalStreamId not defined (or null)"); this.ok(event.originalStreamId, name + '.originalStreamId not defined (or null)');
this.ok(Long.isLong(event.originalEventNumber), name + ".originalEventNumber is not a Long"); this.ok(Long.isLong(event.originalEventNumber), name + '.originalEventNumber is not a Long');
if (typeof evNumber === 'number') { if (typeof evNumber === 'number') {
this.ok(event.originalEventNumber.toNumber() === evNumber, name + '.originalEventNumber expected ' + evNumber + ' got ' + event.originalEventNumber); this.ok(
event.originalEventNumber.toNumber() === evNumber,
name + '.originalEventNumber expected ' + evNumber + ' got ' + event.originalEventNumber
);
} }
testRecordedEvent.call(this, name + '.event', event.event); testRecordedEvent.call(this, name + '.event', event.event);
} }
var _ = { var _ = {
'setUp': setUp, tearDown: tearDown,
'tearDown': tearDown
}; };
switch (process.env.EVENTSTORE_CONNECTION_TYPE) {
case 'gossip':
_.setUp = setUpWithGossipSeeds;
break;
case 'dns':
_.setUp = setUpWithDns;
break;
case 'tcp':
default:
_.setUp = setUpWithTcpEndpoint;
}
function wrap(name, testFunc) { function wrap(name, testFunc) {
var base = _[name]; var base = _[name];
if (base === undefined) { if (base === undefined) {
return function(test) { return function (test) {
settings.log.debug('--- %s ---', name); settings.log.debug('--- %s ---', name);
test.areEqual = areEqual.bind(test); test.areEqual = areEqual.bind(test);
test.fail = fail.bind(test); test.fail = fail.bind(test);
@ -125,36 +208,48 @@ function wrap(name, testFunc) {
test.testLiveEvent = testLiveEvent.bind(test); test.testLiveEvent = testLiveEvent.bind(test);
test.testReadEvent = testReadEvent.bind(test); test.testReadEvent = testReadEvent.bind(test);
return testFunc.call(this, test); return testFunc.call(this, test);
};
} }
} return function (cb) {
return function(cb) {
var self = this; var self = this;
base.call(this, function(err) { base.call(this, function (err) {
if (err) return cb(err); if (err) return cb(err);
return testFunc.call(self, cb); return testFunc.call(self, cb);
}); });
} };
} }
module.exports.init = function(testSuite, addSetUpTearDownIfNotPresent) { module.exports.init = function (testSuite, addSetUpTearDownIfNotPresent) {
var thisObj = {}; var thisObj = {};
if (addSetUpTearDownIfNotPresent === undefined) addSetUpTearDownIfNotPresent = true; if (addSetUpTearDownIfNotPresent === undefined) addSetUpTearDownIfNotPresent = true;
for(var k in testSuite) { for (var k in testSuite) {
if (testSuite.hasOwnProperty(k)) { if (testSuite.hasOwnProperty(k)) {
testSuite[k] = wrap(k, testSuite[k]).bind(thisObj); testSuite[k] = wrap(k, testSuite[k]).bind(thisObj);
} }
} }
if (!addSetUpTearDownIfNotPresent) return; if (!addSetUpTearDownIfNotPresent) return;
if (!testSuite.hasOwnProperty('setUp')) testSuite['setUp'] = setUp.bind(thisObj); if (!testSuite.hasOwnProperty('setUp')) {
switch (process.env.EVENTSTORE_CONNECTION_TYPE) {
case 'gossip':
testSuite['setUp'] = setUpWithGossipSeeds.bind(thisObj);
break;
case 'dns':
testSuite['setUp'] = setUpWithDns.bind(thisObj);
break;
case 'tcp':
default:
testSuite['setUp'] = setUpWithTcpEndpoint.bind(thisObj);
}
}
if (!testSuite.hasOwnProperty('tearDown')) testSuite['tearDown'] = tearDown.bind(thisObj); if (!testSuite.hasOwnProperty('tearDown')) testSuite['tearDown'] = tearDown.bind(thisObj);
}; };
module.exports.settings = function(settingsOverride) { module.exports.settings = function (settingsOverride) {
var obj = {}; var obj = {};
for(var prop in settings) { for (var prop in settings) {
obj[prop] = settings[prop]; obj[prop] = settings[prop];
} }
if (!settingsOverride) return obj; if (!settingsOverride) return obj;
for(var prop in settingsOverride) { for (var prop in settingsOverride) {
obj[prop] = settingsOverride[prop]; obj[prop] = settingsOverride[prop];
} }
return obj; return obj;

View File

@ -4,59 +4,18 @@ var GossipSeed = require('../src/gossipSeed');
var testBase = require('./common/base_test'); var testBase = require('./common/base_test');
var withSsl = !!process.env.NODE_ESC_WITH_SSL; var withSsl = !!process.env.NODE_ESC_WITH_SSL;
const evenstStoreType = process.env.EVENTSTORE_CONNECTION_TYPE;
module.exports = { module.exports = {}
'Connect To Endpoint Happy Path': function (test) {
test.expect(1);
var tcpEndpoint = {host: 'localhost', port: 1113};
var conn = client.EventStoreConnection.create(testBase.settings(), tcpEndpoint);
conn.connect()
.catch(function (err) {
test.done(err);
});
conn.on('connected', function (endPoint) {
test.areEqual("connected endPoint", endPoint, tcpEndpoint);
done();
});
conn.on('error', done);
function done(err) { switch(evenstStoreType){
conn.close(); case 'gossip':
if (err) return test.done(err); module.exports['Connect to Cluster using gossip seeds'] = function (test) {
test.done();
}
},
'Connect To Endpoint That Doesn\'t Exist': function (test) {
test.expect(1);
var tcpEndpoint = {host: 'localhost', port: 11112};
var conn = client.EventStoreConnection.create(testBase.settings({maxReconnections: 1}), tcpEndpoint);
conn.connect()
.catch(function (err) {
test.done(err);
});
conn.on('connected', function () {
test.ok(false, "Should not be able to connect.");
test.done();
});
conn.on('error', function (err) {
test.done(err);
});
conn.on('closed', function (reason) {
test.ok(reason.indexOf("Reconnection limit reached") === 0, "Wrong expected reason.");
test.done();
});
},
'Create a connection with tcp://host:port string': function (test) {
var conn = client.createConnection({}, 'tcp://localhost:1113');
conn.close();
test.done();
}/*,
'Connect to Cluster using gossip seeds': function (test) {
test.expect(1); test.expect(1);
var gossipSeeds = [ var gossipSeeds = [
new GossipSeed({host: 'localhost', port: 1113}), new GossipSeed({host: process.env.EVENTSTORE_HOST_1 || '192.168.33.10', port: 2113}),
new GossipSeed({host: 'localhost', port: 2113}), new GossipSeed({host: process.env.EVENTSTORE_HOST_2 || '192.168.33.11', port: 2113}),
new GossipSeed({host: 'localhost', port: 3113}) new GossipSeed({host: process.env.EVENTSTORE_HOST_3 || '192.168.33.12', port: 2113})
]; ];
var conn = client.EventStoreConnection.create(testBase.settings(), gossipSeeds); var conn = client.EventStoreConnection.create(testBase.settings(), gossipSeeds);
conn.connect() conn.connect()
@ -64,7 +23,7 @@ module.exports = {
test.done(err); test.done(err);
}); });
conn.on('connected', function(endPoint){ conn.on('connected', function(endPoint){
test.ok(endPoint, "no endpoint"); test.ok(endPoint, 'no endpoint');
done(); done();
}); });
conn.on('error', done); conn.on('error', done);
@ -74,16 +33,132 @@ module.exports = {
if (err) return test.done(err); if (err) return test.done(err);
test.done(); test.done();
} }
}*/ };
};
module.exports['Connect To Cluster with bad gossip seeds'] = function (test) {
test.expect(3);
var gossipSeeds = [
new GossipSeed({host: '1.2.3.4', port: 1113}),
new GossipSeed({host: '2.3.4.5', port: 2113}),
new GossipSeed({host: '3.4.5.6', port: 3113})
];
var conn = client.EventStoreConnection.create(testBase.settings({maxDiscoverAttempts: 1}), gossipSeeds);
conn.connect()
.catch(function (err) {
test.ok(err.message.indexOf('Couldn\'t resolve target end point') === 0, 'Wrong expected reason.');
});
conn.on('connected', function () {
test.ok(false, 'Should not be able to connect.');
});
conn.on('error', function (err) {
test.ok(err.message.indexOf('Failed to discover candidate in 1 attempts') === 0, 'Wrong expected reason.');
});
conn.on('closed', function (reason) {
test.ok(reason.indexOf('Failed to resolve TCP end point to which to connect') === 0, 'Wrong expected reason.');
test.done();
});
};
break;
case 'dns':
module.exports['Connect to Cluster using dns discover'] = function (test) {
test.expect(1);
var clusterDns = 'discover://eventstore.local:2113';
var conn = client.EventStoreConnection.create(testBase.settings(), clusterDns);
conn.connect()
.catch(function(err) {
test.done(err);
});
conn.on('connected', function(endPoint){
test.ok(endPoint, 'no endpoint');
done();
});
conn.on('error', done);
function done(err) {
conn.close();
if (err) return test.done(err);
test.done();
}
};
module.exports['Connect To Cluster with bad dns discover'] = function (test) {
test.expect(3);
var clusterDns = 'discover://eventstore-bad.local:2113';
var conn = client.EventStoreConnection.create(testBase.settings({maxDiscoverAttempts: 1}), clusterDns);
conn.connect()
.catch(function (err) {
test.ok(err.message.indexOf('Couldn\'t resolve target end point') === 0, 'Wrong expected reason.');
});
conn.on('connected', function () {
test.ok(false, 'Should not be able to connect.');
});
conn.on('error', function (err) {
test.ok(err.message.indexOf('Failed to discover candidate in 1 attempts') === 0, 'Wrong expected reason.');
});
conn.on('closed', function (reason) {
test.ok(reason.indexOf('Failed to resolve TCP end point to which to connect') === 0, 'Wrong expected reason.');
test.done();
});
};
break;
case 'tcp':
default:
module.exports['Connect To Endpoint Happy Path'] = function (test) {
test.expect(1);
var tcpEndpoint = {host: process.env.EVENTSTORE_HOST || 'localhost', port: 1113};
var conn = client.EventStoreConnection.create(testBase.settings(), tcpEndpoint);
conn.connect()
.catch(function (err) {
test.done(err);
});
conn.on('connected', function (endPoint) {
test.areEqual('connected endPoint', endPoint, tcpEndpoint);
done();
});
conn.on('error', done);
function done(err) {
conn.close();
if (err) return test.done(err);
test.done();
}
};
module.exports['Connect To Endpoint That Doesn\'t Exist'] = function (test) {
test.expect(1);
var tcpEndpoint = {host: process.env.EVENTSTORE_HOST || 'localhost', port: 11112};
var conn = client.EventStoreConnection.create(testBase.settings({maxReconnections: 1}), tcpEndpoint);
conn.connect()
.catch(function (err) {
test.done(err);
});
conn.on('connected', function () {
test.ok(false, 'Should not be able to connect.');
test.done();
});
conn.on('error', function (err) {
test.done(err);
});
conn.on('closed', function (reason) {
test.ok(reason.indexOf('Reconnection limit reached') === 0, 'Wrong expected reason.');
test.done();
});
};
module.exports['Create a connection with tcp://host:port string'] = function (test) {
var conn = client.createConnection({}, `tcp://${process.env.EVENTSTORE_HOST || 'localhost'}:1113`);
conn.close();
test.done();
};
}
if (withSsl) { if (withSsl) {
module.exports['Connect to secure tcp endpoint'] = function(test) { module.exports['Connect to secure tcp endpoint'] = function(test) {
var conn = client.createConnection({ var conn = client.createConnection({
useSslConnection: true, useSslConnection: true,
targetHost: 'localhost', targetHost: process.env.EVENTSTORE_HOST || 'localhost',
validateServer: false validateServer: false
}, 'tcp://localhost:1115'); }, `tcp://${process.env.EVENTSTORE_HOST || 'localhost'}:1115`);
conn.on('error', function (err) { conn.on('error', function (err) {
test.done(err); test.done(err);
}); });

View File

@ -9,20 +9,6 @@ function createRandomEvent() {
var testStreamName = 'test-' + uuid.v4(); var testStreamName = 'test-' + uuid.v4();
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms);
})
}
function delayOnlyFirst(count, action) {
if (count === 0) return action();
return delay(200)
.then(function () {
action();
})
}
module.exports = { module.exports = {
'Test Create Persistent Subscription': function(test) { 'Test Create Persistent Subscription': function(test) {
var settings = client.PersistentSubscriptionSettings.create(); var settings = client.PersistentSubscriptionSettings.create();
@ -46,10 +32,8 @@ module.exports = {
test.done(); test.done();
} }
function eventAppeared(s, e) { function eventAppeared(s, e) {
return delayOnlyFirst(receivedEvents.length, function () {
receivedEvents.push(e); receivedEvents.push(e);
if (receivedEvents.length === 2) s.stop(); if (receivedEvents.length === 2) s.stop();
});
} }
function subscriptionDropped(connection, reason, error) { function subscriptionDropped(connection, reason, error) {
if (error) return done(error); if (error) return done(error);

View File

@ -2,7 +2,7 @@ const client = require('../lib/dist');
const userCredentials = new client.UserCredentials('admin', 'changeit'); const userCredentials = new client.UserCredentials('admin', 'changeit');
const log = new client.NoopLogger(); const log = new client.NoopLogger();
const httpEndpoint = 'http://127.0.0.1:2113'; const httpEndpoint = `http://${process.env.EVENTSTORE_HOST || "localhost"}:2113`;
const operationTimeout = 5000; const operationTimeout = 5000;
const simpleProjection = "\ const simpleProjection = "\

View File

@ -7,20 +7,6 @@ function createRandomEvent() {
return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent'); return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent');
} }
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms);
})
}
function delayOnlyFirst(count, action) {
if (count === 0) return action();
return delay(200)
.then(function () {
action();
})
}
module.exports = { module.exports = {
'Test Subscribe to All From (Start)': function(test) { 'Test Subscribe to All From (Start)': function(test) {
test.expect(6); test.expect(6);
@ -48,14 +34,12 @@ module.exports = {
} }
function eventAppeared(s, e) { function eventAppeared(s, e) {
var isLive = liveProcessing; var isLive = liveProcessing;
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
if (isLive) { if (isLive) {
liveEvents.push(e); liveEvents.push(e);
} else { } else {
catchUpEvents.push(e); catchUpEvents.push(e);
} }
if (isLive && liveEvents.length === 2) s.stop(); if (isLive && liveEvents.length === 2) s.stop();
});
} }
function liveProcessingStarted() { function liveProcessingStarted() {
liveProcessing = true; liveProcessing = true;
@ -99,14 +83,12 @@ module.exports = {
} }
function eventAppeared(s, e) { function eventAppeared(s, e) {
var isLive = liveProcessing; var isLive = liveProcessing;
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
if (isLive) { if (isLive) {
liveEvents.push(e); liveEvents.push(e);
} else { } else {
catchUpEvents.push(e); catchUpEvents.push(e);
} }
if (isLive && liveEvents.length === 2) s.stop(); if (isLive && liveEvents.length === 2) s.stop();
});
} }
function liveProcessingStarted() { function liveProcessingStarted() {
liveProcessing = true; liveProcessing = true;
@ -149,14 +131,12 @@ module.exports = {
} }
function eventAppeared(s, e) { function eventAppeared(s, e) {
var isLive = liveProcessing; var isLive = liveProcessing;
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
if (isLive) { if (isLive) {
liveEvents.push(e); liveEvents.push(e);
} else { } else {
catchUpEvents.push(e); catchUpEvents.push(e);
} }
if (isLive && liveEvents.length === 2) s.stop(); if (isLive && liveEvents.length === 2) s.stop();
});
} }
function liveProcessingStarted() { function liveProcessingStarted() {
liveProcessing = true; liveProcessing = true;

View File

@ -2,20 +2,6 @@ const uuid = require('uuid');
const client = require('../lib/dist'); const client = require('../lib/dist');
const allCredentials = new client.UserCredentials("admin", "changeit"); const allCredentials = new client.UserCredentials("admin", "changeit");
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms);
})
}
function delayOnlyFirst(count, action) {
if (count === 0) return action();
return delay(200)
.then(function () {
action();
})
}
module.exports = { module.exports = {
'Test Subscribe To All Happy Path': function(test) { 'Test Subscribe To All Happy Path': function(test) {
const resolveLinkTos = false; const resolveLinkTos = false;
@ -44,10 +30,15 @@ module.exports = {
var receivedEvents = []; var receivedEvents = [];
function eventAppeared(subscription, event) { function eventAppeared(subscription, event) {
delayOnlyFirst(receivedEvents.length, function() { // Filter non-compliant events (only the one we've published)
let eventData;
try {
eventData = JSON.parse(event.event.data.toString());
} catch(e){}
if (eventData && eventData.a && eventData.b){
receivedEvents.push(event); receivedEvents.push(event);
}
if (receivedEvents.length === numberOfPublishedEvents) subscription.close(); if (receivedEvents.length === numberOfPublishedEvents) subscription.close();
});
} }
function subscriptionDropped(subscription, reason, error) { function subscriptionDropped(subscription, reason, error) {
if (error) return done(error); if (error) return done(error);

View File

@ -6,20 +6,6 @@ function createRandomEvent() {
return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent'); return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent');
} }
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms);
})
}
function delayOnlyFirst(count, action) {
if (count === 0) return action();
return delay(200)
.then(function () {
action();
})
}
module.exports = { module.exports = {
'Test Subscribe to Stream From Beginning (null)': function(test) { 'Test Subscribe to Stream From Beginning (null)': function(test) {
test.expect(48); test.expect(48);
@ -37,14 +23,12 @@ module.exports = {
function eventAppeared(s, e) { function eventAppeared(s, e) {
var isLive = liveProcessing; var isLive = liveProcessing;
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
if (isLive) { if (isLive) {
liveEvents.push(e); liveEvents.push(e);
} else { } else {
catchUpEvents.push(e); catchUpEvents.push(e);
} }
if (isLive && liveEvents.length === 2) s.stop(); if (isLive && liveEvents.length === 2) s.stop();
});
} }
function liveProcessingStarted() { function liveProcessingStarted() {
liveProcessing = true; liveProcessing = true;
@ -93,14 +77,12 @@ module.exports = {
function eventAppeared(s, e) { function eventAppeared(s, e) {
var isLive = liveProcessing; var isLive = liveProcessing;
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
if (isLive) { if (isLive) {
liveEvents.push(e); liveEvents.push(e);
} else { } else {
catchUpEvents.push(e); catchUpEvents.push(e);
} }
if (isLive && liveEvents.length === 2) s.stop(); if (isLive && liveEvents.length === 2) s.stop();
});
} }
function liveProcessingStarted() { function liveProcessingStarted() {
liveProcessing = true; liveProcessing = true;

View File

@ -2,20 +2,6 @@ const uuid = require('uuid');
const client = require('../lib/dist'); const client = require('../lib/dist');
const Long = require('long'); const Long = require('long');
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms);
})
}
function delayOnlyFirst(count, action) {
if (count === 0) return action();
return delay(200)
.then(function () {
action();
})
}
module.exports = { module.exports = {
'Test Subscribe To Stream Happy Path': function(test) { 'Test Subscribe To Stream Happy Path': function(test) {
const resolveLinkTos = false; const resolveLinkTos = false;
@ -43,10 +29,15 @@ module.exports = {
var receivedEvents = []; var receivedEvents = [];
function eventAppeared(subscription, event) { function eventAppeared(subscription, event) {
delayOnlyFirst(receivedEvents.length, function () { // Filter non-compliant events (only the one we've published)
let eventData;
try {
eventData = JSON.parse(event.event.data.toString());
} catch(e){}
if (eventData && eventData.a && eventData.b){
receivedEvents.push(event); receivedEvents.push(event);
}
if (receivedEvents.length === numberOfPublishedEvents) subscription.close(); if (receivedEvents.length === numberOfPublishedEvents) subscription.close();
});
} }
function subscriptionDropped(subscription, reason, error) { function subscriptionDropped(subscription, reason, error) {
if (error) return done(error); if (error) return done(error);