stompit
Version:
STOMP client library for node.js
559 lines (388 loc) • 18.2 kB
JavaScript
/*jslint node: true, indent: 2, camelcase: true, esversion: 9 */
const IncomingFrameStream = require('../lib/IncomingFrameStream');
const BufferWritable = require('../lib/util/buffer/BufferWritable');
const NullWritable = require('../lib/util/NullWritable');
const net = require('net');
const crypto = require('crypto');
const assert = require('assert');
describe('IncomingFrameStream', function() {
var stream;
beforeEach(function() {
stream = new IncomingFrameStream();
});
var readFrame = function(stream, callback) {
var read = false;
var onreadable = function() {
if (!read) {
var frame = stream.read();
if (frame !== null) {
read = true;
stream.removeListener('readable', onreadable);
callback(null, frame);
}
}
};
stream.on('readable', onreadable);
onreadable();
};
var readFrameBody = function(stream, callback) {
readFrame(stream, function(error, frame) {
if (error) {
callback(error);
return;
}
var writable = new BufferWritable(Buffer.alloc(20));
writable.on('finish', function() {
callback(null, frame, writable.getWrittenSlice());
});
frame.pipe(writable);
});
};
var writeBinaryFrame = function(writable, maxChunkSize, length, callback) {
writable.write('MESSAGE\ncontent-length:' + length + '\n\n');
var lengthRemaining = length;
var md5sum = crypto.createHash('md5');
var completed = false;
var write = function() {
var drained = true;
while(lengthRemaining > 0 && drained) {
var size = Math.min(lengthRemaining, maxChunkSize);
var chunk = Buffer.alloc(size);
md5sum.update(chunk);
drained = writable.write(chunk);
lengthRemaining -= size;
}
if (lengthRemaining === 0 && !completed) {
writable.removeListener('drain', write);
completed = true;
writable.write('\x00');
callback(length, md5sum.digest('hex'));
}
};
writable.on('drain', write);
write();
};
var createString = function(length) {
var charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var result = '';
for (var i = 0; i < length; i++) {
result += charset[Math.floor(Math.random() * charset.length)];
}
return result;
};
var writeTextFrame = function(writable, maxChunkSize, length, callback) {
writable.write('MESSAGE\n\n');
var lengthRemaining = length;
var md5sum = crypto.createHash('md5');
var completed = false;
var write = function() {
var drained = true;
while(lengthRemaining > 0 && drained) {
var size = Math.min(lengthRemaining, maxChunkSize);
var chunk = createString(size);
md5sum.update(chunk);
drained = writable.write(chunk);
lengthRemaining -= size;
}
if (lengthRemaining === 0 && !completed) {
completed = false;
writable.removeListener('drain', write);
writable.write('\x00');
callback(length, md5sum.digest('hex'));
}
};
writable.on('drain', write);
write();
};
describe('IncomingFrame', function() {
it('should read variable-length bodies', function(done) {
stream.write('CONNECT\n\nONE\x00\n\nCONNECT\n\nTWO\x00');
readFrameBody(stream, function(error, frame, body) {
assert(!error);
assert(body.length === 3);
assert(body.toString() === 'ONE');
readFrameBody(stream, function(error, frame, body) {
assert(!error);
assert(body.length === 3);
assert(body.toString() === 'TWO');
done();
});
});
});
it('should read fixed-length bodies', function(done) {
stream.write('CONNECT\ncontent-length:4\n\n\x00\x00\x00\x00\x00CONNECT\ncontent-length:3\n\n\x00\n\n\x00');
readFrameBody(stream, function(error, frame, body) {
assert(!error);
assert(frame.headers['content-length'] === 4);
assert(body[0] === 0 && body[1] === 0 && body[2] === 0 && body[3] === 0);
readFrameBody(stream, function(error, frame, body) {
assert(!error);
assert(frame.headers['content-length'] === 3);
assert(body[0] === 0 && body[1] === 10 && body[2] === 10);
done();
});
});
});
it('should read empty frame', function(done) {
stream.write('CONNECT\n\n\x00');
readFrameBody(stream, function(error, frame, body) {
assert(!error);
assert(body.length === 0);
done();
});
});
it('should decode v1.1 escaped characters', function(done) {
stream.setVersion('1.1');
stream.write('CONNECT\nheader1:\\ctest\nheader2:test\\n\nheader3:\\c\\n\\\\\n\n\x00');
readFrameBody(stream, function(error, frame, body) {
assert(frame.headers.header1 === ':test');
assert(frame.headers.header2 === 'test\n');
assert(frame.headers.header3 === ':\n\\');
assert(body.length === 0);
done();
});
});
it('should decode v1.2 escaped characters', function(done) {
stream.setVersion('1.2');
stream.write('CONNECT\nheader1:\\r\\n\n\n\x00');
readFrameBody(stream, function(error, frame, body) {
assert(frame.headers.header1 === '\r\n');
done();
});
});
it('should not decode any escape characters in version 1.0', function(done) {
stream.setVersion('1.0');
stream.write('CONNECT\nheader1:\\ctest\\n:\n\n\x00');
readFrameBody(stream, function(error, frame, body) {
assert(frame.headers.header1 === '\\ctest\\n:');
done();
});
});
it('should parse header line with multiple colons', function(done) {
stream.write('CONNECT\nheader1::value:::value:\n\n\x00');
readFrameBody(stream, function(error, frame, body) {
assert(frame.headers.header1 === ':value:::value:');
done();
});
});
it('should emit an error for an undefined escape sequence', function(done) {
stream.setVersion('1.1');
var processedStreamError = false;
stream.on('error', function(error) {
assert(error);
processedStreamError = true;
done();
});
stream.write('CONNECT\nheader:\\rtest\n\n\x00');
readFrameBody(stream, function(error, frame, body) {
assert(error);
});
});
it('should use the first occurring entry of a repeated header', function(done) {
stream.write('CONNECT\ntest:1\ntest:2\n\n\x00');
readFrameBody(stream, function(error, frame, body) {
assert(frame.headers.test === '1');
done();
});
});
it('should parse CRLF as EOL', function(done) {
stream.write('CONNECT\r\nheader1:value1\r\n\r\n\x00\r\n\r\nTEST\n\n\x00');
readFrame(stream, function(error, frame) {
assert(frame.command === 'CONNECT');
assert(frame.headers.header1 === 'value1');
frame.readEmptyBody(function(isEmpty) {
assert(isEmpty);
readFrame(stream, function(error, frame) {
frame.readEmptyBody(function(isEmpty) {
assert(isEmpty);
done();
});
});
});
});
});
describe('#readEmptyBody', function() {
it('should read an empty body', function(done) {
stream.write('CONNECT\n\n\x00');
readFrame(stream, function(error, frame) {
frame.readEmptyBody(function(isEmpty) {
assert(isEmpty);
done();
});
});
});
it('should not read a non-empty body', function(done) {
stream.write('CONNECT\n\nBODY\x00');
readFrame(stream, function(error, frame) {
frame.readEmptyBody(function(isEmpty) {
assert(!isEmpty);
done();
});
});
});
});
describe('#read', function() {
it('should not emit error event for a valid frame', function(done) {
stream.on('error', function() {
assert(false);
});
stream.write('CONNECT\r\n\r\nBODY\x00');
readFrameBody(stream, function(error, frame, body) {
done();
});
});
});
describe('#readString', function() {
it('should read all data into a string', function(done) {
stream.write('CONNECT\n\nBODY\x00');
readFrame(stream, function(error, frame) {
frame.readString('utf-8', function(error, body) {
assert(!error);
assert(body === 'BODY');
done();
});
});
});
it('should decode a multibyte character spread across multiple stream chunks', function(done) {
stream.write('CONNECT\n\n');
stream.write(Buffer.from([0xE2]));
stream.write(Buffer.from([0x82]));
stream.write(Buffer.from([0xAC, 0x00]));
readFrame(stream, function(error, frame) {
frame.readString('utf-8', function(error, body) {
assert(!error);
assert(body.length === 1);
assert(body == '€');
done();
});
});
});
});
});
it('should respond to back-pressure in the frame body stream', function(done) {
stream = new IncomingFrameStream();
assert(stream.write('MESSAGE\n\n') === false);
// should choke the transport stream because IncomingFrame object has been
// pushed and is waiting to be read
stream.once('drain', function() {
assert(stream.write('1') === false);
// should choke the stream because the sub-stream being read has
// reached its high watermark
stream.once('drain', function() {
stream.write('two\x00');
});
});
readFrame(stream, function(error, frame) {
var writable = new NullWritable();
writable.on('finish', function() {
assert(writable.bytesWritten === 4);
done();
});
frame.pipe(writable);
});
});
it('should emit end event when the writable side of the stream ends', function(done) {
stream.on('end', function() {
done();
});
stream.end();
stream.read();
});
it('should emit end event after a frame has been written and then the stream ended', function(done) {
stream.on('end', function() {
done();
});
stream.end('MESSAGE\n\nTESTING\x00');
readFrameBody(stream, function(error, frame, body) {
assert(!error);
assert(body.toString() === 'TESTING');
// Trigger the next read that causes EOF
readFrame(stream, function(error, frame) {
assert(error);
});
});
});
it('should emit error event when the stream ends in the middle of parsing a frame', function(done) {
stream.on('error', function(error) {
assert(error.message === 'unexpected end of stream');
done();
});
stream.on('end', function() {
assert(false);
});
stream.write('MESSAGE\n\nTESTIN');
stream.end();
readFrameBody(stream, function() {});
});
it('should read multiple large binary and text frames', function(done) {
var port = 12345;
var sentFrames = [];
var receivedFrames = [];
var server = net.createServer(function(connection) {
writeBinaryFrame(connection, 4096, 327675, function(length, hash) {
sentFrames.push([length, hash]);
connection.write('\r\n\n\n');
writeTextFrame(connection, 256, 1024, function(length, hash) {
sentFrames.push([length, hash]);
writeBinaryFrame(connection, 16635, 347798, function(length, hash) {
sentFrames.push([length, hash]);
connection.end();
});
});
});
});
server.listen(port);
var read = function(callback) {
readFrame(stream, function(error, frame) {
if (error) {
callback(error);
return;
}
var writable = new NullWritable('md5');
frame.on('end', function() {
receivedFrames.push([writable.getBytesWritten(), writable.getHashDigest('hex')]);
callback(null);
});
frame.pipe(writable);
});
};
var readLoop;
readLoop = function() {
read(function(error) {
if (!error) {
readLoop();
}
});
};
var client = net.connect(port, function() {
client.pipe(stream);
stream.on('end', function() {
assert(sentFrames.length === receivedFrames.length);
for (var i = 0; i < sentFrames.length; i++) {
assert.equal(receivedFrames[i][0], sentFrames[i][0]);
assert.equal(receivedFrames[i][1], sentFrames[i][1]);
}
done();
});
readLoop();
});
});
it('shoud emit an error event when the maximum length length is exceeded', function(done) {
stream = new IncomingFrameStream({
maxLineLength:2
});
stream.on('error', function() {
done();
});
stream.write('MESSAGE\n\n');
});
it('should emit an error event when the maximum number of unique headers is exceeded', function(done) {
stream = new IncomingFrameStream({
maxHeaders: 2
});
stream.on('error', function() {
done();
});
stream.write('MESSAGE\nheader1:abc\nheader2:abc\nheader3:abc\n\n');
});
});