amqp-node
Version:
An AMQP 0-9-1 (e.g., RabbitMQ) library and client.
315 lines (290 loc) • 7.67 kB
JavaScript
'use strict';
var codec = require('../lib/codec');
var defs = require('../lib/defs');
var assert = require('assert');
var ints = require('buffer-more-ints');
var C = require('claire');
var forAll = C.forAll;
// These just test known encodings; to generate the answers I used
// RabbitMQ's binary generator module.
var testCases = [
// integers
['byte', {
byte: 127
},
[]
],
['byte max value', {
byte: 255
},
[]
],
['byte min value', {
byte: 0
},
[]
],
['< 0 promoted to signed short', {
short: -1
},
[]
],
['> 255 promoted to short', {
short: 256
},
[]
],
['< 2^15 still a short', {
short: 0x7fff
},
[]
],
['-2^15 still a short', {
short: -0x8000
},
[]
],
['>= 2^15 promoted to int', {
int: 0x8000
},
[]
],
['< -2^15 promoted to int', {
int: -0x8001
},
[]
],
['< 2^31 still an int', {
int: 0x7fffffff
},
[]
],
['>= -2^31 still an int', {
int: -0x80000000
},
[]
],
['>= 2^31 promoted to long', {
long: 0x80000000
},
[]
],
['< -2^31 promoted to long', {
long: -0x80000001
},
[]
],
// floating point
['float value', {
double: 0.5
},
[]
],
['negative float value', {
double: -0.5
},
[]
],
// %% test some boundaries of precision?
// string
['string', {
string: 'boop'
},
[]
],
// buffer -> byte array
['byte array from buffer', {
bytes: new Buffer([1, 2, 3, 4])
},
[]
],
// boolean, void
['true', {
bool: true
},
[]
],
['false', {
bool: false
},
[]
],
['null', {
'void': null
},
[]
],
// array, object
['array', {
array: [6, true, 'foo']
},
[]
],
['object', {
object: {
foo: 'bar',
baz: 12
}
},
[]
],
// exotic types
['timestamp', {
timestamp: {
'!': 'timestamp',
value: 1357212277527
}
},
[]
],
['decimal', {
decimal: {
'!': 'decimal',
value: {
digits: 2345,
places: 2
}
}
},
[]
],
['float', {
float: {
'!': 'float',
value: 0.1
}
},
[]
],
];
function bufferToArray(b) {
return Array.prototype.slice.call(b);
}
describe('Explicit encodings', function () {
testCases.forEach(function (tc) {
var name = tc[0],
val = tc[1],
expect = tc[2];
it(name, function () {
var buffer = new Buffer(1000);
var size = codec.encodeTable(buffer, val, 0);
var result = buffer.slice(4, size);
assert.deepEqual(expect, bufferToArray(result));
});
});
});
// Whole frames
var amqp = require('./data');
function roundtrip_table(t) {
var buf = new Buffer(4096);
var size = codec.encodeTable(buf, t, 0);
var decoded = codec.decodeFields(buf.slice(4, size)); // ignore the length-prefix
try {
assert.deepEqual(t, decoded);
} catch (e) {
return false;
}
return true;
}
function roundtrips(T) {
return forAll(T).satisfy(function (v) {
return roundtrip_table({
value: v
});
});
}
describe('Roundtrip values', function () {
[
amqp.Octet,
amqp.ShortStr,
amqp.LongStr,
amqp.UShort,
amqp.ULong,
amqp.ULongLong,
amqp.UShort,
amqp.Short,
amqp.Long,
amqp.Bit,
amqp.Decimal,
amqp.Timestamp,
amqp.Double,
amqp.Float,
amqp.FieldArray,
amqp.FieldTable
].forEach(function (T) {
it(T.toString() + ' roundtrip', roundtrips(T).asTest());
});
});
// Asserts that the decoded fields are equal to the original fields,
// or equal to a default where absent in the original. The defaults
// depend on the type of method or properties.
//
// This works slightly different for methods and properties: for
// methods, each field must have a value, so the default is
// substituted for undefined values when encoding; for properties,
// fields may be absent in the encoded value, so a default is
// substituted for missing fields when decoding. The effect is the
// same so far as these tests are concerned.
function assertEqualModuloDefaults(original, decodedFields) {
var args = defs.info(original.id).args;
for (var i = 0; i < args.length; i++) {
var arg = args[i];
var originalValue = original.fields[arg.name];
var decodedValue = decodedFields[arg.name];
try {
if (originalValue === undefined) {
// longstr gets special treatment here, since the defaults are
// given as strings rather than buffers, but the decoded values
// will be buffers.
assert.deepEqual((arg.type === 'longstr') ?
new Buffer(arg.default) : arg.default,
decodedValue);
} else {
assert.deepEqual(originalValue, decodedValue);
}
} catch (assertionErr) {
var methodOrProps = defs.info(original.id).name;
assertionErr.message += ' (frame ' + methodOrProps +
' field ' + arg.name + ')';
throw assertionErr;
}
}
// %%% TODO make sure there's no surplus fields
return true;
}
// This is handy for elsewhere
module.exports.assertEqualModuloDefaults = assertEqualModuloDefaults;
function roundtripMethod(Method) {
return forAll(Method).satisfy(function (method) {
var buf = defs.encodeMethod(method.id, 0, method.fields);
// FIXME depends on framing, ugh
var fs1 = defs.decode(method.id, buf.slice(11, buf.length));
assertEqualModuloDefaults(method, fs1);
return true;
});
}
function roundtripProperties(Properties) {
return forAll(Properties).satisfy(function (properties) {
var buf = defs.encodeProperties(properties.id, 0, properties.size,
properties.fields);
// FIXME depends on framing, ugh
var fs1 = defs.decode(properties.id, buf.slice(19, buf.length));
assert.equal(properties.size, ints.readUInt64BE(buf, 11));
assertEqualModuloDefaults(properties, fs1);
return true;
});
}
describe('Roundtrip methods', function () {
amqp.methods.forEach(function (Method) {
it(Method.toString() + ' roundtrip',
roundtripMethod(Method).asTest());
});
});
describe('Roundtrip properties', function () {
amqp.properties.forEach(function (Properties) {
it(Properties.toString() + ' roundtrip',
roundtripProperties(Properties).asTest());
});
});