UNPKG

amqplib

Version:

An AMQP 0-9-1 (e.g., RabbitMQ) library and client.

181 lines (162 loc) 5.43 kB
const { describe, it } = require('node:test'); const assert = require('node:assert'); const connection = require('../lib/connection'); const Frames = connection.Connection; const HEARTBEAT = require('../lib/frame').HEARTBEAT; const Stream = require('node:stream'); const PassThrough = Stream.PassThrough; const { choice, forAll, repeat, label, sequence, transform, sized } = require('claire'); const amqp = require('./lib/data'); const assertEqualModuloDefaults = require('./lib/util').assertEqualModuloDefaults; const defs = require('../lib/defs'); // We'll need to supply a stream which we manipulate ourselves function inputs() { // don't coalesce buffers, since that could mess up properties (e.g., encoded frame size) return new PassThrough({ objectMode: true }); } const HB = Buffer.from([ defs.constants.FRAME_HEARTBEAT, 0, 0, // channel 0 0, 0, 0, 0, // zero size defs.constants.FRAME_END, ]); describe('Frame', () => { describe('Explicit parsing', () => { it('Parse heartbeat', () => { const input = inputs(); const frames = new Frames(input); input.write(HB); assert.ok(frames.recvFrame() === HEARTBEAT); assert.ok(!frames.recvFrame()); }); it('Parse partitioned', () => { const input = inputs(); const frames = new Frames(input); input.write(HB.subarray(0, 3)); assert.ok(!frames.recvFrame()); input.write(HB.subarray(3)); assert.ok(frames.recvFrame() === HEARTBEAT); assert.ok(!frames.recvFrame()); }); function testBogusFrame(name, bytes) { it(name, (_t, done) => { const input = inputs(); const frames = new Frames(input); frames.frameMax = 5; //for the max frame test input.write(Buffer.from(bytes)); frames.step((err, _frame) => { if (err != null) done(); else assert.fail('Was a bogus frame!'); }); }); } testBogusFrame('Wrong sized frame', [ defs.constants.FRAME_BODY, 0, 0, 0, 0, 0, 0, // zero length 65, // but a byte! defs.constants.FRAME_END, ]); testBogusFrame('Unknown method frame', [ defs.constants.FRAME_METHOD, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, // garbage ID defs.constants.FRAME_END, ]); }); const Trace = label('frame trace', repeat(choice.apply(choice, amqp.methods))); describe('Parsing', () => { function testPartitioning(partition) { return forAll(Trace) .satisfy((t) => { const bufs = []; const input = inputs(); const frames = new Frames(input); let i = 0; let ex; frames.accept = (f) => { // A minor hack to make sure we get the assertion exception; // otherwise, it's just a test that we reached the line // incrementing `i` for each frame. try { assertEqualModuloDefaults(t[i], f.fields); } catch (e) { ex = e; } i++; }; t.forEach((f) => { f.channel = 0; bufs.push(defs.encodeMethod(f.id, 0, f.fields)); }); partition(bufs).forEach((chunk) => input.write(chunk)); frames.acceptLoop(); if (ex) throw ex; return i === t.length; }) .asTest({ times: 20 }); } it('Parse trace of methods', testPartitioning((bufs) => bufs)); it("Parse concat'd methods", testPartitioning((bufs) => [Buffer.concat(bufs)])); it('Parse partitioned methods', testPartitioning((bufs) => { const full = Buffer.concat(bufs); const onethird = Math.floor(full.length / 3); const twothirds = 2 * onethird; return [full.subarray(0, onethird), full.subarray(onethird, twothirds), full.subarray(twothirds)]; })); }); const FRAME_MAX_MAX = 4096 * 4; const FRAME_MAX_MIN = 4096; const FrameMax = amqp.rangeInt('frame max', FRAME_MAX_MIN, FRAME_MAX_MAX); const Body = sized((_n) => Math.floor(Math.random() * FRAME_MAX_MAX), repeat(amqp.Octet)); const Content = transform( (args) => ({ method: args[0].fields, header: args[1].fields, body: Buffer.from(args[2]), }), sequence(amqp.methods['BasicDeliver'], amqp.properties['BasicProperties'], Body), ); describe('Content framing', () => { it('Adhere to frame max', forAll(Content, FrameMax) .satisfy((content, max) => { const input = inputs(); const frames = new Frames(input); frames.frameMax = max; frames.sendMessage(0, defs.BasicDeliver, content.method, defs.BasicProperties, content.header, content.body); let _i = 0; let largest = 0; let frame = input.read(); while (frame) { _i++; if (frame.length > largest) largest = frame.length; if (frame.length > max) { return false; } frame = input.read(); } // The ratio of frames to 'contents' should always be >= 2 // (one properties frame and at least one content frame); > 2 // indicates fragmentation. The largest is always, of course <= frame max //console.log('Frames: %d; frames per message: %d; largest frame %d', _i, _i / t.length, largest); return true; }) .asTest()); }); });