You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

591 lines
21 KiB

'use strict';
var libPath = process.env['KAFKA_COV'] ? '../lib-cov/' : '../lib/';
var Consumer = require(libPath + 'consumer');
var Producer = require(libPath + 'producer');
var Offset = require(libPath + 'offset');
var Client = require(libPath + 'client');
var should = require('should');
var uuid = require('node-uuid');
var TopicsNotExistError = require(libPath + 'errors').TopicsNotExistError;
var FakeClient = require('./mocks/mockClient');
var InvalidConfigError = require('../lib/errors/InvalidConfigError');
var client, producer, offset;
var TOPIC_POSTFIX = '_test_' + Date.now();
var EXISTS_TOPIC_1 = '_exists_1' + TOPIC_POSTFIX;
var EXISTS_TOPIC_2 = '_exists_2' + TOPIC_POSTFIX;
var EXISTS_GZIP = '_exists_gzip'; // + TOPIC_POSTFIX
var EXISTS_SNAPPY = '_exists_snappy'; // + TOPIC_POSTFIX
// Compression-friendly to be interesting
var SNAPPY_MESSAGE = new Array(20).join('snappy');
var host = process.env['KAFKA_TEST_HOST'] || '';
function noop () { console.log(arguments); }
describe('Consumer', function () {
describe('#setOffset', function () {
var client, consumer;
beforeEach(function () {
client = new FakeClient();
consumer = new Consumer(
client,
[ {topic: 'fake-topic'} ]
);
clearTimeout(consumer.checkPartitionOwnershipInterval);
consumer.payloads = [
{topic: 'fake-topic', partition: '0', offset: 0, maxBytes: 1048576, metadata: 'm'},
{topic: 'fake-topic', partition: '1', offset: 0, maxBytes: 1048576, metadata: 'm'}
];
});
it('should setOffset correctly given partition is a string', function () {
consumer.setOffset('fake-topic', '0', 86);
consumer.payloads[0].offset.should.be.eql(86);
consumer.setOffset('fake-topic', '1', 23);
consumer.payloads[1].offset.should.be.eql(23);
});
it('should setOffset correctly if given partition is a number', function () {
consumer.setOffset('fake-topic', 0, 67);
consumer.payloads[0].offset.should.be.eql(67);
consumer.setOffset('fake-topic', 1, 98);
consumer.payloads[1].offset.should.be.eql(98);
});
});
describe('validate topics', function () {
function validateThrowsInvalidConfigError (topics) {
should.throws(function () {
var client = new FakeClient(host);
// eslint-disable-next-line no-new
new Consumer(client, topics);
}, InvalidConfigError);
}
function validateDoesNotThrowInvalidConfigError (topics) {
should.doesNotThrow(function () {
var client = new FakeClient(host);
// eslint-disable-next-line no-new
new Consumer(client, topics);
}, InvalidConfigError);
}
it('should throw an error on invalid topic partitions', function () {
validateThrowsInvalidConfigError([
{
topic: 'another-topic'
},
{
topic: 'my-fake-topic',
partition: '7'
}
]);
validateThrowsInvalidConfigError([
{
topic: 'another-topic',
partition: 3
},
{
topic: 'my-fake-topic',
partition: {}
}
]);
validateThrowsInvalidConfigError([
{
topic: 'my-fake-topic',
partition: false
}
]);
});
it('should not throw an error for valid topic partition', function () {
validateDoesNotThrowInvalidConfigError([
{
topic: 'fake-topic',
partition: 7
},
{
topic: 'another-topic'
}
]);
validateDoesNotThrowInvalidConfigError([
{
topic: 'another-topic'
},
{
topic: 'fake-topic',
partition: 0
}
]);
});
});
describe('validate groupId', function () {
function validateThrowsInvalidConfigError (groupId) {
should.throws(function () {
var client = new FakeClient(host);
// eslint-disable-next-line no-new
new Consumer(client, [ { topic: EXISTS_TOPIC_2 } ], {groupId: groupId});
}, InvalidConfigError);
}
function validateDoesNotThrowInvalidConfigError (groupId) {
should.doesNotThrow(function () {
var client = new FakeClient(host);
// eslint-disable-next-line no-new
new Consumer(client, [ { topic: EXISTS_TOPIC_2 } ], {groupId: groupId});
});
}
it('should throws an error on invalid group IDs', function () {
validateThrowsInvalidConfigError('myGroupId:12345');
validateThrowsInvalidConfigError('myGroupId,12345');
validateThrowsInvalidConfigError('myGroupId"12345"');
validateThrowsInvalidConfigError('myGroupId?12345');
});
it('should not throw on valid group IDs', function () {
validateDoesNotThrowInvalidConfigError('myGroupId.12345');
validateDoesNotThrowInvalidConfigError('something_12345');
validateDoesNotThrowInvalidConfigError('myGroupId-12345');
});
});
function offsetOutOfRange (topic, consumer) {
topic.maxNum = 2;
offset.fetch([topic], function (err, offsets) {
if (err) consumer.emit('error', err);
var min = Math.min.apply(null, offsets[topic.topic][topic.partition]);
consumer.setOffset(topic.topic, topic.partition, min);
});
}
[
{
name: 'PLAINTEXT Consumer'
},
{
name: 'SSL Consumer',
sslOptions: {
rejectUnauthorized: false
},
suiteTimeout: 30000
}
].forEach(function (testParameters) {
var sslOptions = testParameters.sslOptions || undefined;
var suiteTimeout = testParameters.suiteTimeout || null;
var suiteName = testParameters.name;
function createClient () {
var clientId = 'kafka-node-client-' + uuid.v4();
return new Client(host, clientId, undefined, undefined, sslOptions);
}
describe(suiteName, function () {
before(function (done) {
if (suiteTimeout) { this.timeout(suiteTimeout); }
client = createClient();
producer = new Producer(client);
offset = new Offset(client);
producer.on('ready', function () {
producer.createTopics([
EXISTS_TOPIC_1,
EXISTS_TOPIC_2,
EXISTS_GZIP,
EXISTS_SNAPPY
], false, function (err, created) {
if (err) return done(err);
function useNewTopics () {
producer.send([
{ topic: EXISTS_TOPIC_2, messages: 'hello kafka' },
{ topic: EXISTS_GZIP, messages: 'hello gzip', attributes: 1 },
{ topic: EXISTS_SNAPPY, messages: SNAPPY_MESSAGE, attributes: 2 }
], done);
}
// Ensure leader selection happened
setTimeout(useNewTopics, 1000);
});
});
});
after(function (done) {
client.close(done);
});
describe('events', function () {
it('should emit message when get new message', function (done) {
if (suiteTimeout) { this.timeout(suiteTimeout); }
var topics = [ { topic: EXISTS_TOPIC_2 } ];
var options = { autoCommit: false, groupId: '_groupId_1_test' };
var consumer = new Consumer(client, topics, options);
var count = 0;
consumer.on('error', noop);
consumer.on('offsetOutOfRange', function (topic) {
offsetOutOfRange(topic, this);
});
consumer.on('message', function (message) {
message.topic.should.equal(EXISTS_TOPIC_2);
message.value.should.equal('hello kafka');
message.partition.should.equal(0);
offset.commit('_groupId_1_test', [message], function (err) {
if (count++ === 0) done(err);
});
});
});
it('should decode gzip messages', function (done) {
if (suiteTimeout) { this.timeout(suiteTimeout); }
var topics = [ { topic: EXISTS_GZIP } ];
var options = { autoCommit: false, groupId: '_groupId_gzip_test' };
var client = createClient();
var consumer = new Consumer(client, topics, options);
var count = 0;
consumer.on('error', noop);
consumer.on('offsetOutOfRange', function (topic) {
offsetOutOfRange(topic, this);
});
consumer.on('message', function (message) {
message.topic.should.equal(EXISTS_GZIP);
message.value.should.equal('hello gzip');
offset.commit('_groupId_gzip_test', [message], function (err) {
if (count++ === 0) {
consumer.close(function () {
done(err);
});
}
});
});
});
it('should decode snappy messages', function (done) {
if (suiteTimeout) { this.timeout(suiteTimeout); }
var topics = [ { topic: EXISTS_SNAPPY } ];
var options = { autoCommit: false, groupId: '_groupId_snappy_test' };
var client = createClient();
var consumer = new Consumer(client, topics, options);
var count = 0;
consumer.on('error', noop);
consumer.on('offsetOutOfRange', function (topic) {
offsetOutOfRange(topic, this);
});
consumer.once('message', function (message) {
message.topic.should.equal(EXISTS_SNAPPY);
message.value.should.equal(SNAPPY_MESSAGE);
offset.commit('_groupId_snappy_test', [message], function (err) {
if (count++ === 0) {
consumer.close(function () {
done(err);
});
}
});
});
});
it('should emit error when topic not exists', function (done) {
var topics = [ { topic: '_not_exist_topic_1_test' } ];
var consumer = new Consumer(client, topics);
consumer.on('error', function (error) {
error.should.be.an.instanceOf(TopicsNotExistError)
.and.have.property('message', 'The topic(s) _not_exist_topic_1_test do not exist');
done();
});
});
it('should emit offsetOutOfRange when offset out of range', function (done) {
var topics = [ { topic: EXISTS_TOPIC_1, offset: 100 } ];
var options = { fromOffset: true, autoCommit: false };
var count = 0;
var client = createClient();
var consumer = new Consumer(client, topics, options);
consumer.on('offsetOutOfRange', function (topic) {
topic.topic.should.equal(EXISTS_TOPIC_1);
topic.partition.should.equal(0);
topic.message.should.equal('OffsetOutOfRange');
if (count++ === 0) {
consumer.close(function () {
done();
});
}
});
consumer.on('error', noop);
});
});
describe('#addTopics', function () {
describe('when topic need to added not exist', function () {
it('should return error', function (done) {
var options = { autoCommit: false, groupId: '_groupId_1_test' };
var topics = ['_not_exist_topic_1_test'];
var consumer = new Consumer(client, [], options);
consumer.on('error', noop);
consumer.addTopics(topics, function (err) {
err.topics.length.should.equal(1);
err.topics.should.eql(topics);
done();
});
});
it('should return error when using payload as well', function (done) {
var options = { autoCommit: false, groupId: '_groupId_1_test' };
var topics = [{topic: '_not_exist_topic_1_test', offset: 42}];
var consumer = new Consumer(client, [], options);
consumer.on('error', noop);
consumer.addTopics(topics, function (err) {
err.topics.length.should.equal(1);
err.topics.should.eql(topics.map(function (p) { return p.topic; }));
done();
}, true);
});
});
describe('when topic need to added exist', function () {
it('should added successfully', function (done) {
var options = { autoCommit: false, groupId: '_groupId_addTopics_test' };
var topics = [EXISTS_TOPIC_2];
var consumer = new Consumer(client, [], options);
consumer.on('error', noop);
consumer.addTopics(topics, function (err, data) {
data.should.eql(topics);
consumer.payloads.some(function (p) { return p.topic === topics[0]; }).should.be.ok;
done(err);
});
});
it('should add with given offset', function (done) {
var options = { autoCommit: false, groupId: '_groupId_addTopics_test' };
var topics = [{topic: EXISTS_TOPIC_2, offset: 42}];
var consumer = new Consumer(client, [], options);
consumer.on('error', noop);
consumer.addTopics(topics, function (err, data) {
data.should.eql(topics.map(function (p) { return p.topic; }));
consumer.payloads.some(function (p) { return p.topic === topics[0].topic && p.offset === topics[0].offset; }).should.be.ok;
done(err);
}, true);
});
});
describe('#removeTopics', function () {
it('should remove topics successfully', function (done) {
var options = { autoCommit: false, groupId: '_groupId_addTopics_test' };
var topics = [{ topic: EXISTS_TOPIC_2 }, { topic: EXISTS_TOPIC_1 }];
var consumer = new Consumer(client, topics, options);
consumer.on('error', noop);
consumer.payloads.length.should.equal(2);
consumer.removeTopics(EXISTS_TOPIC_2, function (err, removed) {
removed.should.equal(1);
consumer.payloads.length.should.equal(1);
done(err);
});
});
});
describe('#setOffset', function () {
it('should update the offset in consumer', function () {
var options = { autoCommit: false, groupId: '_groupId_addTopics_test' };
var topics = [{ topic: EXISTS_TOPIC_2 }];
var consumer = new Consumer(client, topics, options);
consumer.on('error', noop);
consumer.setOffset(EXISTS_TOPIC_2, 0, 100);
consumer.payloads.filter(function (p) {
return p.topic === EXISTS_TOPIC_2;
})[0].offset.should.equal(100);
});
});
describe('#commit', function () {
it('should commit offset of current topics', function (done) {
var topics = [ { topic: EXISTS_TOPIC_2 } ];
var options = { autoCommit: false, groupId: '_groupId_commit_test' };
var client = new Client(host);
var consumer = new Consumer(client, topics, options);
var count = 0;
consumer.on('error', noop);
consumer.on('offsetOutOfRange', function (topic) {
offsetOutOfRange(topic, this);
});
consumer.on('message', function (message) {
consumer.commit(true, function (err) {
if (!err && count++ === 0) done(err);
});
});
});
});
describe('#buildPayloads', function () {
it('should set default config on the topic', function () {
var defaults = {
groupId: 'kafka-node-group',
autoCommit: true,
autoCommitIntervalMs: 5000,
encoding: 'utf8',
fetchMaxWaitMs: 100,
fetchMinBytes: 1,
fetchMaxBytes: 1024 * 1024,
fromOffset: false
};
var consumer = new Consumer(client, []);
consumer.options.should.eql(defaults);
var topic = consumer.buildPayloads([{ topic: 'topic' }])[0];
topic.partition.should.equal(0);
topic.offset.should.equal(0);
topic.metadata.should.equal('m');
topic.maxBytes.should.equal(1024 * 1024);
});
it('should support custom options', function () {
var options = {
groupId: 'custom-group',
autoCommit: false,
autoCommitIntervalMs: 1000,
fetchMaxWaitMs: 200,
fetchMinBytes: 1,
fetchMaxBytes: 1024,
fromOffset: false
};
var consumer = new Consumer(client, [], options);
consumer.options.should.equal(options);
var topic = consumer.buildPayloads([{ topic: 'topic' }])[0];
topic.partition.should.equal(0);
topic.offset.should.equal(0);
topic.metadata.should.equal('m');
topic.maxBytes.should.equal(1024);
});
it('should return right payloads when arguments is string', function () {
var consumer = new Consumer(client, []);
var topics = ['topic'];
var topic = consumer.buildPayloads(topics)[0];
topic.should.be.an.instanceof(Object);
topic.partition.should.equal(0);
topic.offset.should.equal(0);
topic.metadata.should.equal('m');
topic.maxBytes.should.equal(1024 * 1024);
});
});
describe('#close', function () {
it('should close the consumer', function (done) {
var client = new Client(host);
var topics = [ { topic: EXISTS_TOPIC_2 } ];
var options = { autoCommit: false, groupId: '_groupId_close_test' };
var consumer = new Consumer(client, topics, options);
consumer.once('message', function (message) {
consumer.close(function (err) {
done(err);
});
});
});
it('should commit the offset if force', function (done) {
var client = new Client(host);
var topics = [ { topic: EXISTS_TOPIC_2 } ];
var force = true;
var options = { autoCommit: false, groupId: '_groupId_close_test' };
var consumer = new Consumer(client, topics, options);
consumer.once('message', function (message) {
consumer.close(force, function (err) {
done(err);
});
});
});
});
xdescribe('#pauseTopics|resumeTopics', function () {
it('should pause or resume the topics', function (done) {
var client = new Client(host);
var topics = [
{topic: EXISTS_TOPIC_1, partition: 0},
{topic: EXISTS_TOPIC_1, partition: 1},
{topic: EXISTS_TOPIC_2, partition: 0},
{topic: EXISTS_TOPIC_2, partition: 1}
];
var consumer = new Consumer(client, topics, {});
consumer.on('error', function () {});
function normalize (p) {
return { topic: p.topic, partition: p.partition };
}
function compare (p1, p2) {
return p1.topic === p2.topic
? p1.partition - p2.partition
: p1.topic > p2.topic;
}
consumer.payloads.should.eql(topics);
consumer.pauseTopics([EXISTS_TOPIC_1, { topic: EXISTS_TOPIC_2, partition: 0 }]);
consumer.payloads.map(normalize).should.eql([{ topic: EXISTS_TOPIC_2, partition: 1 }]);
consumer.resumeTopics([{topic: EXISTS_TOPIC_1, partition: 0}]);
consumer.payloads.map(normalize).sort(compare).should.eql([
{topic: EXISTS_TOPIC_1, partition: 0},
{topic: EXISTS_TOPIC_2, partition: 1}
]);
consumer.pausedPayloads.map(normalize).sort(compare).should.eql([
{topic: EXISTS_TOPIC_1, partition: 1},
{topic: EXISTS_TOPIC_2, partition: 0}
]);
consumer.resumeTopics([EXISTS_TOPIC_1, EXISTS_TOPIC_2]);
consumer.payloads.sort(compare).should.eql(topics);
consumer.pausedPayloads.should.eql([]);
consumer.once('message', function () {
consumer.close(true, done);
});
});
});
describe('Consumer', function () {
before(function (done) {
if (suiteTimeout) { this.timeout(suiteTimeout); }
client = createClient();
producer = new Producer(client);
offset = new Offset(client);
producer.on('ready', function () {
producer.createTopics([
EXISTS_TOPIC_1,
EXISTS_TOPIC_2,
EXISTS_GZIP,
EXISTS_SNAPPY
], false, function (err, created) {
if (err) return done(err);
function useNewTopics () {
producer.send([
{ topic: EXISTS_TOPIC_2, messages: 'hello kafka' },
{ topic: EXISTS_GZIP, messages: 'hello gzip', attributes: 1 },
{ topic: EXISTS_SNAPPY, messages: SNAPPY_MESSAGE, attributes: 2 }
], done);
}
// Ensure leader selection happened
setTimeout(useNewTopics, 1000);
});
});
});
});
after(function (done) {
client.close(done);
});
});
});
});
});