Files
protobuf.js/tests/api_converters.js

419 lines
20 KiB
JavaScript

var tape = require("tape");
var protobuf = require("..");
tape.test("converters - special floating-point defaults", function(test) {
var Type = protobuf.parse("syntax = \"proto2\";\
message NonFiniteDefaults {\
optional double positive = 1 [default = inf];\
optional double negative = 2 [default = -inf];\
optional float not_a_number = 3 [default = nan];\
optional double negative_zero = 4 [default = -0];\
optional double finite = 5 [default = 1.5];\
}").root.lookupType("NonFiniteDefaults"),
message = Type.create();
var object = Type.toObject(message, { defaults: true });
test.equal(object.positive, Infinity, "should preserve a positive infinity default");
test.equal(object.negative, -Infinity, "should preserve a negative infinity default");
test.ok(Number.isNaN(object.notANumber), "should preserve a NaN default");
test.ok(Object.is(object.negativeZero, -0), "should preserve a negative zero default");
test.equal(object.finite, 1.5, "should preserve a finite default");
var jsonObject = Type.toObject(message, { defaults: true, json: true });
test.equal(jsonObject.positive, "Infinity", "should JSON-convert a positive infinity default");
test.equal(jsonObject.negative, "-Infinity", "should JSON-convert a negative infinity default");
test.equal(jsonObject.notANumber, "NaN", "should JSON-convert a NaN default");
test.ok(Object.is(jsonObject.negativeZero, -0), "should preserve a negative zero default in JSON mode");
test.end();
});
tape.test("converters", function(test) {
protobuf.load("tests/data/convert.proto", function(err, root) {
if (err)
return test.fail(err.message);
var Message = root.lookup("Message");
test.test(test.name + " - Message#toObject", function(test) {
test.plan(8);
test.test(test.name + " - called with defaults = true", function(test) {
var obj = Message.toObject(Message.create(), { defaults: true });
test.equal(obj.stringVal, "", "should set stringVal");
test.same(obj.stringRepeated, [], "should set stringRepeated");
test.same(obj.uint64Val, { low: 0, high: 0, unsigned: true }, "should set uint64Val");
test.same(obj.uint64Repeated, [], "should set uint64Repeated");
test.same(obj.bytesVal, protobuf.util.newBuffer(0), "should set bytesVal");
test.same(obj.bytesRepeated, [], "should set bytesRepeated");
test.equal(obj.enumVal, 1, "should set enumVal to the first defined value");
test.same(obj.enumRepeated, [], "should set enumRepeated");
test.same(obj.int64Map, {}, "should set int64Map");
test.end();
});
test.test(test.name + " - called with defaults = true and longs = BigInt", function(test) {
var obj = Message.toObject(Message.create(), { defaults: true, longs: BigInt });
test.equal(obj.uint64Val, 0n, "should set uint64Val");
test.end();
});
test.test(test.name + " - called with defaults = undefined", function(test) {
var obj = Message.toObject(Message.create());
test.equal(obj.stringVal, undefined, "should not set stringVal");
test.equal(obj.stringRepeated, undefined, "should not set stringRepeated");
test.equal(obj.uint64Val, undefined, "should not set uint64Val");
test.equal(obj.uint64Repeated, undefined, "should not set uint64Repeated");
test.equal(obj.bytesVal, undefined, "should not set bytesVal");
test.equal(obj.bytesRepeated, undefined, "should not set bytesRepeated");
test.equal(obj.enumVal, undefined, "should not set enumVal");
test.equal(obj.enumRepeated, undefined, "should not set enumRepeated");
test.equal(obj.int64Map, undefined, "should not set int64 map");
test.end();
});
test.test(test.name + " - called with arrays = true", function(test) {
var obj = Message.toObject(Message.create(), { arrays: true });
test.equal(obj.stringVal, undefined, "should not set stringVal");
test.same(obj.stringRepeated, [], "should set stringRepeated");
test.equal(obj.uint64Val, undefined, "should not set uint64Val");
test.same(obj.uint64Repeated, [], "should set uint64Repeated");
test.equal(obj.bytesVal, undefined, "should not set bytesVal");
test.same(obj.bytesRepeated, [], "should set bytesRepeated");
test.equal(obj.enumVal, undefined, "should not set enumVal");
test.same(obj.enumRepeated, [], "should set enumRepeated");
test.equal(obj.int64Map, undefined, "should not set int64Map");
test.end();
});
test.test(test.name + " - called with objects = true", function(test) {
var obj = Message.toObject(Message.create(), { objects: true });
test.equal(obj.stringVal, undefined, "should not set stringVal");
test.equal(obj.stringRepeated, undefined, "should not set stringRepeated");
test.equal(obj.uint64Val, undefined, "should not set uint64Val");
test.same(obj.uint64Repeated, undefined, "should not set uint64Repeated");
test.equal(obj.bytesVal, undefined, "should not set bytesVal");
test.same(obj.bytesRepeated, undefined, "should not set bytesRepeated");
test.equal(obj.enumVal, undefined, "should not set enumVal");
test.same(obj.enumRepeated, undefined, "should not set enumRepeated");
test.same(obj.int64Map, {}, "should set int64Map");
test.end();
});
test.test(test.name + " - should convert", function(test) {
var buf = protobuf.util.newBuffer(3);
buf[0] = buf[1] = buf[2] = 49; // "111"
var msg = Message.create({
// This number was chosen to be > 2^63 and < 2^64.
uint64Val: protobuf.util.Long.fromString("11000000000000000001", true),
uint64Repeated: [2, 3],
bytesVal: buf,
bytesRepeated: [buf, buf],
enumVal: 2,
enumRepeated: [1, 100, 2],
int64Map: {
// These numbers were chosen to be < Number.MIN_SAFE_INTEGER.
a: protobuf.util.Long.fromString("-200000000000000001"),
b: protobuf.util.Long.fromString("-300000000000000001")
}
});
var msgLongsToNumber = Message.toObject(msg, { longs: Number }),
msgLongsToString = Message.toObject(msg, { longs: String }),
msgLongsToBigInt = Message.toObject(msg, { longs: BigInt });
test.same(Message.ctor.toObject(msg, { longs: Number}), msgLongsToNumber, "should convert the same using the static and the instance method");
test.same(Message.ctor.toObject(msg, { longs: String}), msgLongsToString, "should convert the same using the static and the instance method");
test.same(Message.ctor.toObject(msg, { longs: BigInt}), msgLongsToBigInt, "should convert the same using the static and the instance method");
test.equal(msgLongsToNumber.uint64Val, 11000000000000000000, "longs to numbers");
test.equal(msgLongsToString.uint64Val, "11000000000000000001", "longs to strings");
test.equal(msgLongsToBigInt.uint64Val, 11000000000000000001n, "longs to bigints");
test.same(msgLongsToBigInt.uint64Repeated, [2n, 3n], "long arrays to bigints");
test.same(msgLongsToNumber.int64Map, { a: -200000000000000000, b: -300000000000000000}, "long map values to numbers");
test.same(msgLongsToString.int64Map, { a: "-200000000000000001", b: "-300000000000000001"}, "long map values to strings");
test.same(msgLongsToBigInt.int64Map, { a: -200000000000000001n, b: -300000000000000001n}, "long map values to bigints");
test.equal(Object.prototype.toString.call(Message.toObject(msg, { bytes: Array }).bytesVal), "[object Array]", "bytes to arrays");
test.equal(Message.toObject(msg, { bytes: String }).bytesVal, "MTEx", "bytes to base64 strings");
if (protobuf.util.isNode)
test.ok(Buffer.isBuffer(Message.toObject(msg, { bytes: Buffer }).bytesVal), "bytes to buffers");
test.equal(Message.toObject(msg, { enums: String }).enumVal, "TWO", "enums to strings");
test.equal(Message.toObject(msg, { enums: String }).enumRepeated[1], 100, "enums to strings does not change unknown values");
test.end();
});
test.test(test.name + " - Message.toObject with empty buffers", function(test) {
var msg = Message.create({
bytesVal: protobuf.util.newBuffer(0),
});
test.equal(Message.toObject(msg, { bytes: String }).bytesVal, "", "bytes to base64 strings");
if (protobuf.util.isNode) {
const bytesVal = Message.toObject(msg, { bytes: Buffer }).bytesVal;
test.ok(Buffer.isBuffer(bytesVal), "bytes to buffers");
test.equal(bytesVal.length, 0, "empty buffer");
}
test.end();
});
test.test(test.name + " - Message.toObject with bytes array defaults", function(test) {
var root = protobuf.Root.fromJSON({
nested: {
Defaults: {
fields: {
bytes: {
type: "bytes",
id: 1,
options: {
"default": [ "not_a_number" ]
}
}
}
}
}
});
var Defaults = root.lookupType("Defaults");
var obj = Defaults.toObject({}, { defaults: true, bytes: Array });
test.same(obj.bytes, [ "not_a_number" ], "should preserve bytes array defaults");
test.end();
});
});
test.test(test.name + " - null-prototype objects", function(test) {
var userRoot = protobuf.parse("syntax = \"proto2\"; message User { optional string name = 1; optional int32 age = 2; }").root,
User = userRoot.lookupType("User"),
user = Object.create(null),
oneofRoot = protobuf.parse("syntax = \"proto3\"; message M { oneof kind { string a = 1; int32 b = 2; } }").root,
M = oneofRoot.lookupType("M"),
oneof = Object.create(null);
user.name = "attacker";
user.age = 99;
test.equal(User.verify(user), null, "verify accepts valid null-prototype objects");
user.age = "bad";
test.equal(User.verify(user), "age: integer expected", "verify returns errors for invalid null-prototype objects");
oneof.a = "x";
test.same(M.toObject(oneof, { oneofs: true }), { a: "x", kind: "a" }, "toObject converts null-prototype oneof objects");
test.end();
});
test.test(test.name + " - runtime-significant field names", function(test) {
var root = protobuf.parse("syntax = \"proto2\"; message M { required string hasOwnProperty = 1; }").root;
root.resolveAll();
var M = root.lookupType("M"),
message = M.decode(M.encode({ hasOwnProperty: "x" }).finish());
test.equal(message.hasOwnProperty, "x", "decode should not call a shadowed hasOwnProperty field");
test.equal(M.verify(message), null, "verify should not call a shadowed hasOwnProperty field");
test.same(M.toObject(message), { hasOwnProperty: "x" }, "toObject should not call a shadowed hasOwnProperty field");
test.end();
});
test.test(test.name + " - Message.fromObject", function(test) {
var obj = {
uint64Val: 1,
uint64Repeated: [1, "2"],
bytesVal: "MTEx",
bytesRepeated: ["MTEx", [49, 49, 49]],
enumVal: "ONE",
enumRepeated: [2, "TWO", 100],
int64Map: {
a: 2,
b: "3"
}
};
var msg = Message.fromObject(obj);
test.same(Message.ctor.fromObject(obj), msg, "should convert the same using the static and the instance method");
test.equal(Message.fromObject(msg), msg, "should just return the object if already a runtime message");
var buf = protobuf.util.newBuffer(3);
buf[0] = buf[1] = buf[2] = 49; // "111"
test.same(msg.uint64Val, { low: 1, high: 0, unsigned: true }, "should set uint64Val from a number");
test.same(msg.uint64Repeated, [ { low: 1, high: 0, unsigned: true}, { low: 2, high: 0, unsigned: true } ], "should set uint64Repeated from a number and a string");
test.same(msg.bytesVal, buf, "should set bytesVal from a base64 string");
test.same(msg.bytesRepeated, [ buf, buf ], "should set bytesRepeated from a base64 string and a plain array");
test.equal(msg.enumVal, 1, "should set enumVal from a string");
test.same(msg.enumRepeated, [ 2, 2, 100 ], "should set enumRepeated from a number and a string and preserve unknown value");
test.same(msg.int64Map, { a: { low: 2, high: 0, unsigned: false }, b: { low: 3, high: 0, unsigned: false } }, "should set int64Map from a number and a string");
var bigMsg = Message.fromObject({
uint64Val: 11000000000000000001n,
uint64Repeated: [2n, 3n],
int64Map: {
a: -200000000000000001n,
b: -300000000000000001n
}
});
test.same(bigMsg.uint64Val, protobuf.util.Long.fromString("11000000000000000001", true), "should set uint64Val from a bigint");
test.same(bigMsg.uint64Repeated, [
protobuf.util.Long.fromString("2", true),
protobuf.util.Long.fromString("3", true)
], "should set uint64Repeated from bigints");
test.same(bigMsg.int64Map, {
a: protobuf.util.Long.fromString("-200000000000000001"),
b: protobuf.util.Long.fromString("-300000000000000001")
}, "should set int64Map from bigints");
test.end();
});
test.test(test.name + " - fixed64 signedness", function(test) {
var root = protobuf.Root.fromJSON({
nested: {
Fixed: {
fields: {
fixed64Val: {
type: "fixed64",
id: 1
},
sfixed64Val: {
type: "sfixed64",
id: 2
}
}
}
}
});
var Fixed = root.lookupType("Fixed");
var msg = Fixed.fromObject({
fixed64Val: "11000000000000000001",
sfixed64Val: "-9000000000000000001"
});
var msgFromNumber = Fixed.fromObject({
fixed64Val: 11000000000000000000,
sfixed64Val: -9000000000000000000
});
var msgFromLongZero = Fixed.fromObject({
fixed64Val: protobuf.util.Long.ZERO,
sfixed64Val: protobuf.util.Long.UZERO
});
var obj = Fixed.toObject(msg, { longs: BigInt });
var defaults = Fixed.toObject(Fixed.create(), { defaults: true });
test.equal(msg.fixed64Val.unsigned, true, "should set fixed64 values as unsigned");
test.equal(msg.sfixed64Val.unsigned, false, "should set sfixed64 values as signed");
test.same(msgFromNumber.fixed64Val, protobuf.util.Long.fromString("11000000000000000000", true), "should set fixed64 values from numbers as unsigned");
test.same(msgFromNumber.sfixed64Val, protobuf.util.Long.fromString("-9000000000000000000"), "should set sfixed64 values from numbers as signed");
test.same(msgFromLongZero.fixed64Val, protobuf.util.Long.UZERO, "should set fixed64 values from Longs as unsigned");
test.same(msgFromLongZero.sfixed64Val, protobuf.util.Long.ZERO, "should set sfixed64 values from Longs as signed");
test.equal(protobuf.util.Long.ZERO.unsigned, false, "should not mutate Long.ZERO");
test.equal(protobuf.util.Long.UZERO.unsigned, true, "should not mutate Long.UZERO");
test.equal(obj.fixed64Val, 11000000000000000001n, "should output fixed64 as unsigned bigint");
test.equal(obj.sfixed64Val, -9000000000000000001n, "should output sfixed64 as signed bigint");
test.same(defaults.fixed64Val, { low: 0, high: 0, unsigned: true }, "should default fixed64 as unsigned");
test.same(defaults.sfixed64Val, { low: 0, high: 0, unsigned: false }, "should default sfixed64 as signed");
test.end();
});
test.test(test.name + " - Type.toObject recursion limit", function(test) {
var recursionLimit = protobuf.util.recursionLimit;
protobuf.util.recursionLimit = 3;
try {
var nestedRoot = protobuf.Root.fromJSON({
nested: {
Recursive: {
fields: {
next: {
type: "Recursive",
id: 1
}
}
}
}
});
var Recursive = nestedRoot.lookupType("Recursive");
var msg = Recursive.create();
var cursor = msg;
for (var i = 0; i < 5; ++i)
cursor = cursor.next = Recursive.create();
test.throws(function() {
Recursive.toObject(msg);
}, /max depth exceeded/, "should reject excessive object conversion depth");
} finally {
protobuf.util.recursionLimit = recursionLimit;
}
test.end();
});
test.test(test.name + " - Message#toJSON", function(test) {
var msg = Message.create();
msg.$type = {
toObject: function(obj, opt) {
test.same(opt, protobuf.util.toJSONOptions, "should use toJSONOptions with toJSON");
test.end();
}
};
msg.toJSON();
});
test.end();
});
});
tape.test("converters - bytes field default with bytes = String", function(test) {
var proto = "syntax = \"proto2\";\
message DefaultBytes {\
optional bytes bytes_field = 1 [default = \"moo\"];\
}";
var Message = protobuf.parse(proto).root.lookupType("DefaultBytes");
var withDefault = Message.toObject(Message.create({}), { defaults: true, bytes: String });
var withValue = Message.toObject(Message.create({ bytesField: protobuf.util.newBuffer([109, 111, 111]) }), { defaults: true, bytes: String });
test.equal(withDefault.bytesField, "bW9v", "should base64 encode the default value");
test.equal(withDefault.bytesField, withValue.bytesField, "should match an explicitly set value");
var back = Message.fromObject(withDefault);
test.same(Array.prototype.slice.call(back.bytesField), [109, 111, 111], "should round-trip through fromObject");
test.end();
});