UNPKG

nsqjs

Version:
411 lines (327 loc) 10.7 kB
const _ = require('lodash') const child_process = require('child_process') // eslint-disable-line camelcase const fetch = require('node-fetch') const pEvent = require('p-event') const retry = require('async-retry') const should = require('should') const temp = require('temp').track() const url = require('url') const util = require('util') const nsq = require('../lib/nsq') const EventEmitter = require('events') let TCP_PORT = 4150 let HTTP_PORT = 4151 const startNSQD = async (dataPath, additionalOptions = {}) => { let options = { 'http-address': `127.0.0.1:${HTTP_PORT}`, 'tcp-address': `127.0.0.1:${TCP_PORT}`, 'broadcast-address': '127.0.0.1', 'data-path': dataPath, 'tls-cert': './test/cert.pem', 'tls-key': './test/key.pem', } _.extend(options, additionalOptions) // Convert to array for child_process. options = Object.keys(options).map((option) => [ `-${option}`, options[option], ]) const process = child_process.spawn('nsqd', _.flatten(options), { stdio: ['ignore', 'ignore', 'ignore'], }) process.on('error', (err) => { throw err }) await retry( async () => { const response = await fetch(`http://localhost:${HTTP_PORT}/ping`) if (!response.ok) { throw new Error('not ready') } }, {retries: 10, minTimeout: 50} ) return process } const topicOp = async (op, topic) => { const u = new url.URL(`http://127.0.0.1:${HTTP_PORT}/${op}`) u.searchParams.set('topic', topic) await fetch(u.toString(), {method: 'POST'}) } const createTopic = async (topic) => topicOp('topic/create', topic) const deleteTopic = async (topic) => topicOp('topic/delete', topic) // Publish a single message via HTTP const publish = async (topic, message) => { const u = new url.URL(`http://127.0.0.1:${HTTP_PORT}/pub`) u.searchParams.set('topic', topic) await fetch(u.toString(), { method: 'POST', body: message, }) } describe('integration', () => { let nsqdProcess = null let reader = null beforeEach(async () => { nsqdProcess = await startNSQD(await temp.mkdir('/nsq')) await createTopic('test') }) afterEach(async () => { const closeEvent = pEvent(reader, 'nsqd_closed') const exitEvent = pEvent(nsqdProcess, 'exit') reader.close() await closeEvent await deleteTopic('test') nsqdProcess.kill('SIGKILL') await exitEvent // After each start, increment the ports to prevent possible conflict the // next time an NSQD instance is started. Sometimes NSQD instances do not // exit cleanly causing odd behavior for tests and the test suite. TCP_PORT = TCP_PORT + 50 HTTP_PORT = HTTP_PORT + 50 reader = null }) describe('stream compression and encryption', () => { const optionPermutations = [ {deflate: true}, {snappy: true}, {tls: true, tlsVerification: false}, {tls: true, tlsVerification: false, snappy: true}, {tls: true, tlsVerification: false, deflate: true}, ] optionPermutations.forEach((options) => { const compression = ['deflate', 'snappy'] .filter((key) => key in options) .map((key) => key) compression.push('none') // Figure out what compression is enabled const description = `reader with compression (${ compression[0] }) and tls (${options.tls != null})` describe(description, () => { it('should send and receive a message', async () => { const topic = 'test' const channel = 'default' const message = 'a message for our reader' await publish(topic, message) reader = new nsq.Reader( topic, channel, Object.assign( {nsqdTCPAddresses: [`127.0.0.1:${TCP_PORT}`]}, options ) ) reader.on('error', () => {}) const messageEvent = pEvent(reader, 'message') reader.connect() const msg = await messageEvent should.equal(msg.body.toString(), message) msg.finish() }) it('should send and receive a large message', async () => { const topic = 'test' const channel = 'default' const message = _.range(0, 100000) .map(() => 'a') .join('') await publish(topic, message) reader = new nsq.Reader( topic, channel, Object.assign( {nsqdTCPAddresses: [`127.0.0.1:${TCP_PORT}`]}, options ) ) reader.on('error', () => {}) const messageEvent = pEvent(reader, 'message') reader.connect() const msg = await messageEvent should.equal(msg.body.toString(), message) msg.finish() }) }) }) }) describe('end to end', () => { const topic = 'test' const channel = 'default' let writer = null reader = null beforeEach((done) => { writer = new nsq.Writer('127.0.0.1', TCP_PORT) writer.on('ready', () => { reader = new nsq.Reader(topic, channel, { nsqdTCPAddresses: [`127.0.0.1:${TCP_PORT}`], }) reader.on('nsqd_connected', () => done()) reader.connect() }) writer.on('error', () => {}) writer.connect() }) afterEach(() => { writer.close() }) it('should send and receive a string', (done) => { const message = 'hello world' writer.publish(topic, message, (err) => { if (err) done(err) }) reader.on('error', (err) => { console.log(err) }) reader.on('message', (msg) => { msg.body.toString().should.eql(message) msg.finish() done() }) }) it('should send and receive a String object', (done) => { const message = new String('hello world') writer.publish(topic, message, (err) => { if (err) done(err) }) reader.on('error', (err) => { console.log(err) }) reader.on('message', (msg) => { msg.body.toString().should.eql(message.toString()) msg.finish() done() }) }) it('should send and receive a Buffer', (done) => { const message = Buffer.from([0x11, 0x22, 0x33]) writer.publish(topic, message) reader.on('error', () => {}) reader.on('message', (readMsg) => { for (let i = 0; i < readMsg.body.length; i++) { should.equal(readMsg.body[i], message[i]) } readMsg.finish() done() }) }) it('should not receive messages when immediately paused', (done) => { setTimeout(done, 50) // Note: because NSQDConnection.connect() does most of it's work in // process.nextTick(), we're really pausing before the reader is // connected. // reader.pause() reader.on('message', (msg) => { msg.finish() done(new Error('Should not have received a message while paused')) }) writer.publish(topic, 'pause test') }) it('should not receive any new messages when paused', (done) => { writer.publish(topic, {messageShouldArrive: true}) reader.on('error', (err) => { console.log(err) }) reader.on('message', (msg) => { // check the message msg.json().messageShouldArrive.should.be.true() msg.finish() if (reader.isPaused()) return done() reader.pause() process.nextTick(() => { // send it again, shouldn't get this one writer.publish(topic, {messageShouldArrive: false}) setTimeout(done, 50) }) }) }) it('should not receive any requeued messages when paused', (done) => { writer.publish(topic, 'requeue me') let id = '' reader.on('message', (msg) => { // this will fail if the msg comes through again id.should.equal('') id = msg.id reader.pause() // send it again, shouldn't get this one msg.requeue(0, false) setTimeout(done, 50) }) reader.on('error', () => {}) }) it('should start receiving messages again after unpause async', async () => { const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) const publish = util.promisify((topic, msg, cb) => { writer.publish(topic, msg, cb) }) let paused = false const messageEvents = new EventEmitter() const firstEvent = pEvent(messageEvents, 'first') const secondEvent = pEvent(messageEvents, 'second') reader.on('message', (msg) => { should.equal(paused, false) messageEvents.emit(msg.body.toString(), msg) }) // Pubish message publish(topic, 'first') // Handle first message let msg = await firstEvent paused.should.be.false() msg.finish() // Pause reader reader.pause() paused = true // Publish second message await publish(topic, 'second') // Unpause after delay await wait(50) reader.unpause() paused = false // Handle second message msg = await secondEvent msg.finish() }) it('should successfully publish a message before fully connected', (done) => { writer = new nsq.Writer('127.0.0.1', TCP_PORT) writer.connect() // The writer is connecting, but it shouldn't be ready to publish. should.equal(writer.ready, false) writer.on('error', () => {}) // Publish the message. It should succeed since the writer will queue up // the message while connecting. writer.publish('a_topic', 'a message', (err) => { should.not.exist(err) done() }) }) }) }) describe('failures', () => { let nsqdProcess = null before(async () => { nsqdProcess = await startNSQD(await temp.mkdir('/nsq')) }) describe('Writer', () => { describe('nsqd disconnect before publish', () => { it('should fail to publish a message', async () => { const writer = new nsq.Writer('127.0.0.1', TCP_PORT) writer.on('error', () => {}) const readyEvent = pEvent(writer, 'ready') const exitEvent = pEvent(nsqdProcess, 'exit') writer.connect() await readyEvent nsqdProcess.kill('SIGKILL') await exitEvent const publish = util.promisify((topic, msg, cb) => writer.publish(topic, msg, cb) ) try { await publish('test_topic', 'a failing message') should.fail() } catch (e) { should.exist(e) } }) }) }) })