mirror of
https://github.com/hanzoai/protobuf.js.git
synced 2026-09-24 17:02:08 +00:00
375 lines
12 KiB
JavaScript
375 lines
12 KiB
JavaScript
var tape = require("tape");
|
|
|
|
var protobuf = require("..");
|
|
|
|
var root = protobuf.Root.fromJSON({
|
|
nested: {
|
|
Inner: {
|
|
fields: {
|
|
key: {
|
|
type: "string",
|
|
id: 1
|
|
},
|
|
values: {
|
|
rule: "repeated",
|
|
type: "string",
|
|
id: 2
|
|
}
|
|
}
|
|
},
|
|
Outer: {
|
|
fields: {
|
|
value: {
|
|
keyType: "string",
|
|
type: "Inner",
|
|
id: 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
|
|
function randomString(len) {
|
|
var str = "";
|
|
for (var i = 0; i < len; ++i)
|
|
str += chars.charAt((Math.random() * chars.length)|0);
|
|
return str;
|
|
}
|
|
|
|
function randomMap() {
|
|
var map = {};
|
|
for (var i = 0; i < 10; ++i) {
|
|
var values = [];
|
|
for (var j = 0; j < 10; ++j)
|
|
values.push(randomString(10));
|
|
var key; do { key = randomString(10); } while(map[key]);
|
|
map[key] = {
|
|
key: randomString(10),
|
|
values: values
|
|
};
|
|
}
|
|
return map;
|
|
}
|
|
|
|
tape.test("maps", function(test) {
|
|
var Inner = root.lookup("Inner"),
|
|
Outer = root.lookup("Outer");
|
|
|
|
test.test(test.name + " - randomly generated", function(test) {
|
|
|
|
var outer = { value: randomMap() };
|
|
var buf = Outer.encode(outer).finish();
|
|
var dec = Outer.decode(buf);
|
|
|
|
test.deepEqual(Outer.toObject(dec), outer, "should decode back the original random map");
|
|
|
|
test.end();
|
|
});
|
|
|
|
test.test(test.name + " - specifically crafted", function(test) {
|
|
|
|
var outer = {
|
|
value: {
|
|
b: {
|
|
key: "1",
|
|
values: ["c", "d"]
|
|
},
|
|
a: {
|
|
key: "2",
|
|
values: ["a", "b"]
|
|
}
|
|
}
|
|
};
|
|
|
|
var buf = Outer.encode(outer).finish();
|
|
verifyEncode(test, buf);
|
|
|
|
var dec = Outer.decode(buf);
|
|
test.deepEqual(Outer.toObject(dec), outer, "should decode back the original map");
|
|
|
|
test.end();
|
|
});
|
|
|
|
test.test(test.name + " - omitted message value", function(test) {
|
|
|
|
var dec = Outer.decode(Uint8Array.of(0x0a, 0x03, 0x0a, 0x01, 0x61));
|
|
test.ok(dec.value.a instanceof Inner.ctor, "should decode an omitted message value as a default message");
|
|
test.deepEqual(
|
|
Array.prototype.slice.call(Outer.encode(dec).finish()),
|
|
[ 0x0a, 0x05, 0x0a, 0x01, 0x61, 0x12, 0x00 ],
|
|
"should re-encode the default message value"
|
|
);
|
|
|
|
test.end();
|
|
});
|
|
|
|
test.test(test.name + " - repeated message values merge", function(test) {
|
|
|
|
var dec = Outer.decode(Uint8Array.of(
|
|
0x0a, 0x12,
|
|
0x0a, 0x01, 0x61,
|
|
0x12, 0x08, 0x0a, 0x06, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64,
|
|
0x12, 0x03, 0x12, 0x01, 0x78
|
|
));
|
|
|
|
test.deepEqual(Outer.toObject(dec), {
|
|
value: {
|
|
a: {
|
|
key: "merged",
|
|
values: [ "x" ]
|
|
}
|
|
}
|
|
}, "should merge repeated message-valued map entry fields");
|
|
|
|
test.end();
|
|
});
|
|
|
|
test.test(test.name + " - special string key", function(test) {
|
|
|
|
var map = {};
|
|
Object.defineProperty(map, "__proto__", {
|
|
value: {
|
|
key: "3",
|
|
values: ["x"]
|
|
},
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
|
|
var outer = {
|
|
value: map
|
|
};
|
|
|
|
var dec = Outer.decode(Outer.encode(outer).finish());
|
|
test.equal(Object.getPrototypeOf(dec.value), Object.prototype, "should keep the decoded map prototype");
|
|
test.ok(Object.prototype.hasOwnProperty.call(dec.value, "__proto__"), "should decode the key as an own property");
|
|
test.equal(dec.value.__proto__.key, "3", "should decode the key's value");
|
|
test.deepEqual(dec.value.__proto__.values, ["x"], "should decode the key's repeated value");
|
|
|
|
var msg = Outer.fromObject(outer);
|
|
test.equal(Object.getPrototypeOf(msg.value), Object.prototype, "should keep the converted map prototype");
|
|
test.ok(Object.prototype.hasOwnProperty.call(msg.value, "__proto__"), "should convert the key as an own property");
|
|
|
|
var obj = Outer.toObject(msg);
|
|
test.equal(Object.getPrototypeOf(obj.value), Object.prototype, "should keep the output map prototype");
|
|
test.ok(Object.prototype.hasOwnProperty.call(obj.value, "__proto__"), "should output the key as an own property");
|
|
test.equal(obj.value.__proto__.key, "3", "should output the key's value");
|
|
test.deepEqual(obj.value.__proto__.values, ["x"], "should output the key's repeated value");
|
|
|
|
test.end();
|
|
});
|
|
|
|
test.test(test.name + " - omitted fields", function(test) {
|
|
|
|
var mapRoot = protobuf.Root.fromJSON({
|
|
nested: {
|
|
MapMessage: {
|
|
fields: {
|
|
value: {
|
|
keyType: "int32",
|
|
type: "string",
|
|
id: 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var MapMessage = mapRoot.lookup("MapMessage");
|
|
|
|
var value = {
|
|
value: {
|
|
0: ''
|
|
}
|
|
};
|
|
var dec;
|
|
|
|
// 1 <chunk> = message(1 <varint> = 0, 2 <chunk> = empty chunk)
|
|
dec = MapMessage.decode(Uint8Array.of(0x0a, 0x04, 0x08, 0x00, 0x12, 0x00));
|
|
test.deepEqual(MapMessage.toObject(dec), value, "should correctly decode the buffer without omitted fields");
|
|
|
|
// 1 <chunk> = message(1 <varint> = 0)
|
|
dec = MapMessage.decode(Uint8Array.of(0x0a, 0x02, 0x08, 0x00));
|
|
test.deepEqual(MapMessage.toObject(dec), value, "should correctly decode the buffer with omitted value");
|
|
|
|
// 1 <chunk> = message(2 <chunk> = empty chunk)
|
|
dec = MapMessage.decode(Uint8Array.of(0x0a, 0x02, 0x12, 0x00));
|
|
test.deepEqual(MapMessage.toObject(dec), value, "should correctly decode the buffer with omitted key");
|
|
|
|
// 1 <chunk> = empty chunk
|
|
dec = MapMessage.decode(Uint8Array.of(0x0a, 0x00));
|
|
test.deepEqual(MapMessage.toObject(dec), value, "should correctly decode the buffer with both key and value omitted");
|
|
|
|
test.end();
|
|
});
|
|
|
|
test.test(test.name + " - omitted long values", function(test) {
|
|
|
|
var fields = {},
|
|
bytes = [],
|
|
cases = [
|
|
[ "ints", "int64", protobuf.util.Long.ZERO ],
|
|
[ "uints", "uint64", protobuf.util.Long.UZERO ],
|
|
[ "sints", "sint64", protobuf.util.Long.ZERO ],
|
|
[ "fixeds", "fixed64", protobuf.util.Long.UZERO ],
|
|
[ "sfixeds", "sfixed64", protobuf.util.Long.ZERO ]
|
|
];
|
|
|
|
cases.forEach(function(testCase, index) {
|
|
var id = index + 1;
|
|
fields[testCase[0]] = {
|
|
keyType: "int32",
|
|
type: testCase[1],
|
|
id: id
|
|
};
|
|
bytes.push((id << 3) | 2, 2, 8, id);
|
|
});
|
|
|
|
var MapMessage = protobuf.Root.fromJSON({
|
|
nested: {
|
|
MapMessage: { fields: fields }
|
|
}
|
|
}).lookup("MapMessage");
|
|
var dec = MapMessage.decode(Uint8Array.from(bytes));
|
|
|
|
cases.forEach(function(testCase, index) {
|
|
test.ok(testCase[2].equals(dec[testCase[0]][index + 1]), "should decode omitted " + testCase[1] + " values as zero");
|
|
});
|
|
|
|
test.end();
|
|
});
|
|
|
|
test.test(test.name + " - scalar key roundtrip", function(test) {
|
|
var mapRoot = protobuf.Root.fromJSON({
|
|
nested: {
|
|
MapMessage: {
|
|
fields: {
|
|
bools: {
|
|
keyType: "bool",
|
|
type: "bool",
|
|
id: 1
|
|
},
|
|
ints: {
|
|
keyType: "int64",
|
|
type: "int64",
|
|
id: 2
|
|
},
|
|
uints: {
|
|
keyType: "uint64",
|
|
type: "uint64",
|
|
id: 3
|
|
},
|
|
sints: {
|
|
keyType: "sint64",
|
|
type: "string",
|
|
id: 4
|
|
},
|
|
fixeds: {
|
|
keyType: "fixed64",
|
|
type: "string",
|
|
id: 5
|
|
},
|
|
sfixeds: {
|
|
keyType: "sfixed64",
|
|
type: "string",
|
|
id: 6
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var MapMessage = mapRoot.lookup("MapMessage");
|
|
var uint64Max = "18446744073709551615";
|
|
var uintMap = {};
|
|
var fixedMap = {};
|
|
uintMap[uint64Max] = "1";
|
|
fixedMap[uint64Max] = "a";
|
|
var boolBuf = Uint8Array.of(0x0a, 0x04, 0x08, 0x00, 0x10, 0x00);
|
|
var intBuf = MapMessage.encode({ ints: { "-1": "-1" } }).finish();
|
|
var uintBuf = MapMessage.encode({ uints: uintMap }).finish();
|
|
var sintBuf = MapMessage.encode({ sints: { "-1": "a" } }).finish();
|
|
var fixedBuf = MapMessage.encode({ fixeds: fixedMap }).finish();
|
|
var sfixedBuf = MapMessage.encode({ sfixeds: { "-1": "a" } }).finish();
|
|
var longCases = [
|
|
[ "int64", intBuf, { ints: { "-1": "-1" } } ],
|
|
[ "uint64", uintBuf, { uints: uintMap } ],
|
|
[ "sint64", sintBuf, { sints: { "-1": "a" } } ],
|
|
[ "fixed64", fixedBuf, { fixeds: fixedMap } ],
|
|
[ "sfixed64", sfixedBuf, { sfixeds: { "-1": "a" } } ]
|
|
];
|
|
|
|
function toArray(buf) {
|
|
return Array.prototype.slice.call(buf);
|
|
}
|
|
|
|
function assertReencode(buf, message) {
|
|
test.deepEqual(toArray(MapMessage.encode(MapMessage.decode(buf)).finish()), toArray(buf), message);
|
|
}
|
|
|
|
test.deepEqual(
|
|
toArray(MapMessage.encode({ bools: { "false": false } }).finish()),
|
|
toArray(boolBuf),
|
|
"should encode false boolean keys"
|
|
);
|
|
assertReencode(boolBuf, "should re-encode false boolean keys");
|
|
|
|
longCases.forEach(function(testCase) {
|
|
assertReencode(testCase[1], "should re-encode " + testCase[0] + " keys");
|
|
test.equal(
|
|
MapMessage.verify(MapMessage.decode(testCase[1])),
|
|
null,
|
|
"should verify decoded " + testCase[0] + " keys"
|
|
);
|
|
test.deepEqual(
|
|
MapMessage.toObject(MapMessage.decode(testCase[1]), { longs: String }),
|
|
testCase[2],
|
|
"should output " + testCase[0] + " keys as decimal strings"
|
|
);
|
|
});
|
|
|
|
test.end();
|
|
});
|
|
|
|
test.end();
|
|
});
|
|
|
|
function verifyEncode(test, buf) {
|
|
test.test(test.name + " - should encode", function(test) {
|
|
test.equal(buf.length, 32, "a total of 32 bytes");
|
|
|
|
// first kv:
|
|
/*
|
|
b: {
|
|
key: "1",
|
|
values: ["c", "d"]
|
|
},
|
|
*/
|
|
test.equal(buf[ 0], 10, "id 1, wireType 2"); // Outer.value
|
|
test.equal(buf[ 1], 14, "a length of 14");
|
|
test.equal(buf[ 2], 10, "id 1, wireType 2"); // Outer.value $key
|
|
test.equal(buf[ 3], 1, "a length of 1");
|
|
test.equal(buf[ 4], 98, "'b'");
|
|
test.equal(buf[ 5], 18, "id 2, wireType 2"); // Outer.value $value
|
|
test.equal(buf[ 6], 9, "a length of 9");
|
|
test.equal(buf[ 7], 10, "id 1, wireType 2"); // Inner.key
|
|
test.equal(buf[ 8], 1 , "a length of 1");
|
|
test.equal(buf[ 9], 49, "'1'");
|
|
test.equal(buf[10], 18, "id 2, wireType 2"); // Inner.values (1)
|
|
test.equal(buf[11], 1, "a length of 1");
|
|
test.equal(buf[12], 99, "'c'");
|
|
test.equal(buf[13], 18, "id 2, wireType 2"); // Inner.values (2)
|
|
test.equal(buf[14], 1, "a length of 1");
|
|
test.equal(buf[15],100, "'d'");
|
|
|
|
// second
|
|
test.equal(buf[16], 10, "id 1, wireType 2"); // Outer.value
|
|
// ...
|
|
|
|
test.end();
|
|
});
|
|
}
|