| /* jshint node: true, mocha: true */ |
| |
| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| 'use strict'; |
| |
| var utils = require('../lib/utils'), |
| schemas = require('../lib/schemas'), |
| assert = require('assert'), |
| util = require('util'); |
| |
| var Tap = utils.Tap; |
| var createType = schemas.createType; |
| var types = schemas.types; |
| |
| |
| suite('types', function () { |
| |
| suite('BooleanType', function () { |
| |
| var data = [ |
| { |
| valid: [true, false], |
| invalid: [null, 'hi', undefined, 1.5, 1e28, 123124123123213] |
| } |
| ]; |
| |
| testType(types.BooleanType, data); |
| |
| test('to JSON', function () { |
| var t = new types.BooleanType(); |
| assert.equal(t.toJSON(), 'boolean'); |
| }); |
| |
| test('compare buffers', function () { |
| var t = new types.BooleanType(); |
| var bt = t.toBuffer(true); |
| var bf = t.toBuffer(false); |
| assert.equal(t.compareBuffers(bt, bf), 1); |
| assert.equal(t.compareBuffers(bf, bt), -1); |
| assert.equal(t.compareBuffers(bt, bt), 0); |
| }); |
| |
| test('get name', function () { |
| var t = new types.BooleanType(); |
| assert.strictEqual(t.getName(), undefined); |
| assert.equal(t.getName(true), 'boolean'); |
| }); |
| |
| }); |
| |
| suite('IntType', function () { |
| |
| var data = [ |
| { |
| valid: [1, -3, 12314, 0, 1e9], |
| invalid: [null, 'hi', undefined, 1.5, 1e28, 123124123123213] |
| } |
| ]; |
| |
| testType(types.IntType, data); |
| |
| test('toBuffer int', function () { |
| |
| var type = createType('int'); |
| assert.equal(type.fromBuffer(new Buffer([0x80, 0x01])), 64); |
| assert(new Buffer([0]).equals(type.toBuffer(0))); |
| |
| }); |
| |
| test('resolve int > long', function () { |
| var intType = createType('int'); |
| var longType = createType('long'); |
| var buf = intType.toBuffer(123); |
| assert.equal( |
| longType.fromBuffer(buf, longType.createResolver(intType)), |
| 123 |
| ); |
| }); |
| |
| test('resolve int > [null, int]', function () { |
| var wt = createType('int'); |
| var rt = createType(['null', 'int']); |
| var buf = wt.toBuffer(123); |
| assert.deepEqual( |
| rt.fromBuffer(buf, rt.createResolver(wt)), |
| {'int': 123} |
| ); |
| }); |
| |
| test('resolve int > float', function () { |
| var wt = createType('int'); |
| var rt = createType('float'); |
| var buf = wt.toBuffer(123); |
| assert.deepEqual(rt.fromBuffer(buf, rt.createResolver(wt)), 123); |
| }); |
| |
| test('resolve int > double', function () { |
| var wt = createType('int'); |
| var rt = createType('double'); |
| var n = Math.pow(2, 30) + 1; |
| var buf = wt.toBuffer(n); |
| assert.deepEqual(rt.fromBuffer(buf, rt.createResolver(wt)), n); |
| }); |
| |
| test('toString', function () { |
| assert.equal(createType('int').toString(), '"int"'); |
| }); |
| |
| test('clone', function () { |
| var t = createType('int'); |
| assert.equal(t.clone(123), 123); |
| assert.throws(function () { t.clone(''); }); |
| }); |
| |
| test('resolve invalid', function () { |
| assert.throws(function () { getResolver('int', 'long'); }); |
| }); |
| |
| }); |
| |
| suite('LongType', function () { |
| |
| var data = [ |
| { |
| valid: [1, -3, 12314, 9007199254740990, 900719925474090], |
| invalid: [null, 'hi', undefined, 9007199254740991, 1.3, 1e67] |
| } |
| ]; |
| |
| testType(types.LongType, data); |
| |
| test('resolve invalid', function () { |
| assert.throws(function () { getResolver('long', 'double'); }); |
| }); |
| |
| test('resolve long > float', function () { |
| var t1 = createType('long'); |
| var t2 = createType('float'); |
| var n = 9007199254740990; // Number.MAX_SAFE_INTEGER - 1 |
| var buf = t1.toBuffer(n); |
| var f = t2.fromBuffer(buf, t2.createResolver(t1)); |
| assert(Math.abs(f - n) / n < 1e-7); |
| assert(t2.isValid(f)); |
| }); |
| |
| test('precision loss', function () { |
| var type = createType('long'); |
| var buf = new Buffer([0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20]); |
| assert.throws(function () { type.fromBuffer(buf); }); |
| }); |
| |
| test('using missing methods', function () { |
| assert.throws(function () { types.LongType.using(); }); |
| }); |
| |
| }); |
| |
| suite('StringType', function () { |
| |
| var data = [ |
| { |
| valid: ['', 'hi'], |
| invalid: [null, undefined, 1, 0] |
| } |
| ]; |
| |
| testType(types.StringType, data); |
| |
| test('fromBuffer string', function () { |
| var type = createType('string'); |
| var buf = new Buffer([0x06, 0x68, 0x69, 0x21]); |
| var s = 'hi!'; |
| assert.equal(type.fromBuffer(buf), s); |
| assert(buf.equals(type.toBuffer(s))); |
| }); |
| |
| test('toBuffer string', function () { |
| var type = createType('string'); |
| var buf = new Buffer([0x06, 0x68, 0x69, 0x21]); |
| assert(buf.equals(type.toBuffer('hi!', 1))); |
| }); |
| |
| test('resolve string > bytes', function () { |
| var stringT = createType('string'); |
| var bytesT = createType('bytes'); |
| var buf = stringT.toBuffer('\x00\x01'); |
| assert.deepEqual( |
| bytesT.fromBuffer(buf, bytesT.createResolver(stringT)), |
| new Buffer([0, 1]) |
| ); |
| }); |
| |
| test('encode resize', function () { |
| var t = createType('string'); |
| var s = 'hello'; |
| var b, pos; |
| b = new Buffer(2); |
| pos = t.encode(s, b); |
| assert(pos < 0); |
| b = new Buffer(2 - pos); |
| pos = t.encode(s, b); |
| assert(pos >= 0); |
| assert.equal(s, t.fromBuffer(b)); // Also checks exact length match. |
| }); |
| |
| }); |
| |
| suite('NullType', function () { |
| |
| var data = [ |
| { |
| schema: 'null', |
| valid: [null], |
| invalid: [0, 1, 'hi', undefined] |
| } |
| ]; |
| |
| testType(types.NullType, data); |
| |
| }); |
| |
| suite('FloatType', function () { |
| |
| var data = [ |
| { |
| valid: [1, -3, 123e7], |
| invalid: [null, 'hi', undefined], |
| check: function (a, b) { assert(floatEquals(a, b)); } |
| } |
| ]; |
| |
| testType(types.FloatType, data); |
| |
| test('compare buffer', function () { |
| var t = createType('float'); |
| var b1 = t.toBuffer(0.5); |
| assert.equal(t.compareBuffers(b1, b1), 0); |
| var b2 = t.toBuffer(-0.75); |
| assert.equal(t.compareBuffers(b1, b2), 1); |
| var b3 = t.toBuffer(175); |
| assert.equal(t.compareBuffers(b1, b3), -1); |
| }); |
| |
| test('resolver double > float', function () { |
| assert.throws(function () { getResolver('float', 'double'); }); |
| }); |
| |
| test('fromString', function () { |
| var t = createType('float'); |
| var f = t.fromString('3.1'); |
| assert(t.isValid(f)); |
| }); |
| |
| test('clone from double', function () { |
| var t = createType('float'); |
| var d = 3.1; |
| var f; |
| f = t.clone(d); |
| assert(t.isValid(f)); |
| }); |
| |
| }); |
| |
| suite('DoubleType', function () { |
| |
| var data = [ |
| { |
| valid: [1, -3.4, 12314e31, 5e37], |
| invalid: [null, 'hi', undefined], |
| check: function (a, b) { assert(floatEquals(a, b), '' + [a, b]); } |
| } |
| ]; |
| |
| testType(types.DoubleType, data); |
| |
| test('resolver string > double', function () { |
| assert.throws(function () { getResolver('double', 'string'); }); |
| }); |
| |
| test('compare buffer', function () { |
| var t = createType('double'); |
| var b1 = t.toBuffer(0.5); |
| assert.equal(t.compareBuffers(b1, b1), 0); |
| var b2 = t.toBuffer(-0.75); |
| assert.equal(t.compareBuffers(b1, b2), 1); |
| var b3 = t.toBuffer(175); |
| assert.equal(t.compareBuffers(b1, b3), -1); |
| }); |
| |
| }); |
| |
| suite('BytesType', function () { |
| |
| var data = [ |
| { |
| valid: [new Buffer(1), new Buffer('abc')], |
| invalid: [null, 'hi', undefined, 1, 0, -3.5] |
| } |
| ]; |
| |
| testType(types.BytesType, data); |
| |
| test('clone', function () { |
| var t = createType('bytes'); |
| var s = '\x01\x02'; |
| var buf = new Buffer(s); |
| var clone; |
| clone = t.clone(buf); |
| assert.deepEqual(clone, buf); |
| clone[0] = 0; |
| assert.equal(buf[0], 1); |
| assert.throws(function () { t.clone(s); }); |
| clone = t.clone(buf.toJSON(), {coerceBuffers: true}); |
| assert.deepEqual(clone, buf); |
| assert.throws(function () { t.clone(1, {coerceBuffers: true}); }); |
| }); |
| |
| test('fromString', function () { |
| var t = createType('bytes'); |
| var s = '\x01\x02'; |
| var buf = new Buffer(s); |
| var clone = t.fromString(JSON.stringify(s)); |
| assert.deepEqual(clone, buf); |
| }); |
| |
| test('compare', function () { |
| var t = createType('bytes'); |
| var b1 = t.toBuffer(new Buffer([0, 2])); |
| assert.equal(t.compareBuffers(b1, b1), 0); |
| var b2 = t.toBuffer(new Buffer([0, 2, 3])); |
| assert.equal(t.compareBuffers(b1, b2), -1); |
| var b3 = t.toBuffer(new Buffer([1])); |
| assert.equal(t.compareBuffers(b3, b1), 1); |
| }); |
| |
| }); |
| |
| suite('UnionType', function () { |
| |
| var data = [ |
| { |
| name: 'null & string', |
| schema: ['null', 'string'], |
| valid: [null, {string: 'hi'}], |
| invalid: ['null', undefined, {string: 1}], |
| check: assert.deepEqual |
| }, |
| { |
| name: 'qualified name', |
| schema: ['null', {type: 'fixed', name: 'a.B', size: 2}], |
| valid: [null, {'a.B': new Buffer(2)}], |
| invalid: [new Buffer(2)], |
| check: assert.deepEqual |
| }, |
| { |
| name: 'array int', |
| schema: ['int', {type: 'array', items: 'int'}], |
| valid: [{'int': 1}, {array: [1,3]}], |
| invalid: [null, 2, {array: ['a']}, [4], 2], |
| check: assert.deepEqual |
| }, |
| { |
| name: 'null', |
| schema: ['null'], |
| valid: [null], |
| invalid: [{array: ['a']}, [4], 'null'], |
| check: assert.deepEqual |
| } |
| ]; |
| |
| var schemas = [ |
| {}, |
| [], |
| ['null', 'null'], |
| ['null', {type: 'map', values: 'int'}, {type: 'map', values: 'long'}], |
| ['null', ['int', 'string']] |
| ]; |
| |
| testType(types.UnionType, data, schemas); |
| |
| test('getTypes', function () { |
| var t = createType(['null', 'int']); |
| assert.deepEqual(t.getTypes(), [createType('null'), createType('int')]); |
| }); |
| |
| test('instanceof Union', function () { |
| var type = new types.UnionType(['null', 'int']); |
| assert(type instanceof types.UnionType); |
| }); |
| |
| test('missing name write', function () { |
| var type = new types.UnionType(['null', 'int']); |
| assert.throws(function () { |
| type.toBuffer({b: 'a'}); |
| }); |
| }); |
| |
| test('read invalid index', function () { |
| var type = new types.UnionType(['null', 'int']); |
| var buf = new Buffer([1, 0]); |
| assert.throws(function () { type.fromBuffer(buf); }); |
| }); |
| |
| test('non wrapped write', function () { |
| var type = new types.UnionType(['null', 'int']); |
| assert.throws(function () { |
| type.toBuffer(1, true); |
| }, Error); |
| }); |
| |
| test('to JSON', function () { |
| var type = new types.UnionType(['null', 'int']); |
| assert.equal(JSON.stringify(type), '["null","int"]'); |
| }); |
| |
| test('resolve int to [long, int]', function () { |
| var t1 = createType('int'); |
| var t2 = createType(['long', 'int']); |
| var a = t2.createResolver(t1); |
| var buf = t1.toBuffer(23); |
| assert.deepEqual(t2.fromBuffer(buf, a), {'long': 23}); |
| }); |
| |
| test('resolve null to [null, int]', function () { |
| var t1 = createType('null'); |
| var t2 = createType(['null', 'int']); |
| var a = t2.createResolver(t1); |
| assert.deepEqual(t2.fromBuffer(new Buffer(0), a), null); |
| }); |
| |
| test('resolve [string, int] to [long, string]', function () { |
| var t1 = createType(['string', 'int']); |
| var t2 = createType(['int', 'bytes']); |
| var a = t2.createResolver(t1); |
| var buf; |
| buf = t1.toBuffer({string: 'hi'}); |
| assert.deepEqual(t2.fromBuffer(buf, a), {'bytes': new Buffer('hi')}); |
| buf = t1.toBuffer({'int': 1}); |
| assert.deepEqual(t2.fromBuffer(buf, a), {'int': 1}); |
| }); |
| |
| test('clone', function () { |
| var t = new types.UnionType(['null', 'int']); |
| var o = {'int': 1}; |
| assert.strictEqual(t.clone(null), null); |
| var c = t.clone(o); |
| assert.deepEqual(c, o); |
| c.int = 2; |
| assert.equal(o.int, 1); |
| assert.throws(function () { t.clone([]); }); |
| assert.throws(function () { t.clone(undefined); }); |
| }); |
| |
| test('clone and wrap', function () { |
| var t = createType(['string', 'int']); |
| var o; |
| o = t.clone('hi', {wrapUnions: true}); |
| assert.deepEqual(o, {'string': 'hi'}); |
| o = t.clone(3, {wrapUnions: true}); |
| assert.deepEqual(o, {'int': 3}); |
| assert.throws(function () { t.clone(null, {wrapUnions: 2}); }); |
| }); |
| |
| test('invalid multiple keys', function () { |
| var t = createType(['null', 'int']); |
| var o = {'int': 2}; |
| assert(t.isValid(o)); |
| o.foo = 3; |
| assert(!t.isValid(o)); |
| }); |
| |
| test('clone multiple keys', function () { |
| var t = createType(['null', 'int']); |
| var o = {'int': 2, foo: 3}; |
| assert.throws(function () { t.clone(o); }); |
| }); |
| |
| test('clone unqualified names', function () { |
| var t = createType({ |
| name: 'Person', |
| type: 'record', |
| fields: [ |
| {name: 'id1', type: {name: 'an.Id', type: 'fixed', size: 1}}, |
| {name: 'id2', type: ['null', 'an.Id']} |
| ] |
| }); |
| var b = new Buffer([0]); |
| var o = {id1: b, id2: {Id: b}}; |
| assert.deepEqual(t.clone(o), {id1: b, id2: {'an.Id': b}}); |
| }); |
| |
| test('clone unqualified names', function () { |
| var t = createType({ |
| name: 'Person', |
| type: 'record', |
| fields: [ |
| {name: 'id1', type: {name: 'Id', type: 'fixed', size: 1}}, |
| {name: 'id2', type: ['null', 'Id']} |
| ] |
| }); |
| var b = new Buffer([0]); |
| var o = {id1: b, id2: {'an.Id': b}}; |
| assert.throws(function () { t.clone(o); }); |
| }); |
| |
| test('compare buffers', function () { |
| var t = createType(['null', 'double']); |
| var b1 = t.toBuffer(null); |
| assert.equal(t.compareBuffers(b1, b1), 0); |
| var b2 = t.toBuffer({'double': 4}); |
| assert.equal(t.compareBuffers(b2, b1), 1); |
| assert.equal(t.compareBuffers(b1, b2), -1); |
| var b3 = t.toBuffer({'double': 6}); |
| assert.equal(t.compareBuffers(b3, b2), 1); |
| }); |
| |
| test('compare', function () { |
| var t; |
| t = createType(['null', 'int']); |
| assert.equal(t.compare(null, {'int': 3}), -1); |
| assert.equal(t.compare(null, null), 0); |
| t = createType(['int', 'float']); |
| assert.equal(t.compare({'int': 2}, {'float': 0.5}), -1); |
| assert.equal(t.compare({'int': 20}, {'int': 5}), 1); |
| }); |
| |
| }); |
| |
| suite('EnumType', function () { |
| |
| var data = [ |
| { |
| name: 'single symbol', |
| schema: {name: 'Foo', symbols: ['HI']}, |
| valid: ['HI'], |
| invalid: ['HEY', null, undefined, 0] |
| }, |
| { |
| name: 'number-ish as symbol', |
| schema: {name: 'Foo', symbols: ['HI', 'A0']}, |
| valid: ['HI', 'A0'], |
| invalid: ['HEY', null, undefined, 0, 'a0'] |
| } |
| ]; |
| |
| var schemas = [ |
| {name: 'Foo', symbols: []}, |
| {name: 'Foo'}, |
| {symbols: ['hi']}, |
| {name: 'G', symbols: ['0']} |
| ]; |
| |
| testType(types.EnumType, data, schemas); |
| |
| test('get full name', function () { |
| var t = createType({ |
| type: 'enum', |
| symbols: ['A', 'B'], |
| name: 'Letter', |
| namespace: 'latin' |
| }); |
| assert.equal(t.getName(), 'latin.Letter'); |
| }); |
| |
| test('get aliases', function () { |
| var t = createType({ |
| type: 'enum', |
| symbols: ['A', 'B'], |
| name: 'Letter', |
| namespace: 'latin', |
| aliases: ['Character', 'alphabet.Letter'] |
| }); |
| var aliases = t.getAliases(); |
| assert.deepEqual(aliases, ['latin.Character', 'alphabet.Letter']); |
| aliases.push('Char'); |
| assert.equal(t.getAliases().length, 3); |
| }); |
| |
| test('get symbols', function () { |
| var t = createType({type: 'enum', symbols: ['A', 'B'], name: 'Letter'}); |
| var symbols = t.getSymbols(); |
| assert.deepEqual(symbols, ['A', 'B']); |
| symbols.push('Char'); |
| assert.equal(t.getSymbols().length, 2); |
| }); |
| |
| test('duplicate symbol', function () { |
| assert.throws(function () { |
| createType({type: 'enum', symbols: ['A', 'B', 'A'], name: 'B'}); |
| }); |
| }); |
| |
| test('write invalid', function () { |
| var type = createType({type: 'enum', symbols: ['A'], name: 'a'}); |
| assert.throws(function () { |
| type.toBuffer('B'); |
| }); |
| }); |
| |
| test('read invalid index', function () { |
| var type = new types.EnumType({type: 'enum', symbols: ['A'], name: 'a'}); |
| var buf = new Buffer([2]); |
| assert.throws(function () { type.fromBuffer(buf); }); |
| }); |
| |
| test('resolve', function () { |
| var t1, t2, buf, resolver; |
| t1 = newEnum('Foo', ['bar', 'baz']); |
| t2 = newEnum('Foo', ['bar', 'baz']); |
| resolver = t1.createResolver(t2); |
| buf = t2.toBuffer('bar'); |
| assert.equal(t1.fromBuffer(buf, resolver), 'bar'); |
| t2 = newEnum('Foo', ['baz', 'bar']); |
| buf = t2.toBuffer('bar'); |
| resolver = t1.createResolver(t2); |
| assert.notEqual(t1.fromBuffer(buf), 'bar'); |
| assert.equal(t1.fromBuffer(buf, resolver), 'bar'); |
| t1 = newEnum('Foo2', ['foo', 'baz', 'bar'], ['Foo']); |
| resolver = t1.createResolver(t2); |
| assert.equal(t1.fromBuffer(buf, resolver), 'bar'); |
| t2 = newEnum('Foo', ['bar', 'bax']); |
| assert.throws(function () { t1.createResolver(t2); }); |
| assert.throws(function () { |
| t1.createResolver(createType('int')); |
| }); |
| function newEnum(name, symbols, aliases, namespace) { |
| var obj = {type: 'enum', name: name, symbols: symbols}; |
| if (aliases !== undefined) { |
| obj.aliases = aliases; |
| } |
| if (namespace !== undefined) { |
| obj.namespace = namespace; |
| } |
| return new types.EnumType(obj); |
| } |
| }); |
| |
| test('clone', function () { |
| var t = createType({type: 'enum', name: 'Foo', symbols: ['bar', 'baz']}); |
| assert.equal(t.clone('bar'), 'bar'); |
| assert.throws(function () { t.clone('BAR'); }); |
| assert.throws(function () { t.clone(null); }); |
| }); |
| |
| test('compare buffers', function () { |
| var t = createType({type: 'enum', name: 'Foo', symbols: ['bar', 'baz']}); |
| var b1 = t.toBuffer('bar'); |
| var b2 = t.toBuffer('baz'); |
| assert.equal(t.compareBuffers(b1, b1), 0); |
| assert.equal(t.compareBuffers(b2, b1), 1); |
| }); |
| |
| test('compare', function () { |
| var t = createType({type: 'enum', name: 'Foo', symbols: ['b', 'a']}); |
| assert.equal(t.compare('b', 'a'), -1); |
| assert.equal(t.compare('a', 'a'), 0); |
| }); |
| |
| }); |
| |
| suite('FixedType', function () { |
| |
| var data = [ |
| { |
| name: 'size 1', |
| schema: {name: 'Foo', size: 2}, |
| valid: [new Buffer([1, 2]), new Buffer([2, 3])], |
| invalid: ['HEY', null, undefined, 0, new Buffer(1), new Buffer(3)], |
| check: function (a, b) { assert(a.equals(b)); } |
| } |
| ]; |
| |
| var schemas = [ |
| {name: 'Foo', size: 0}, |
| {name: 'Foo', size: -2}, |
| {size: 2}, |
| {name: 'Foo'}, |
| {} |
| ]; |
| |
| testType(types.FixedType, data, schemas); |
| |
| test('get full name', function () { |
| var t = createType({ |
| type: 'fixed', |
| size: 2, |
| name: 'Id', |
| namespace: 'id' |
| }); |
| assert.equal(t.getName(), 'id.Id'); |
| }); |
| |
| test('get aliases', function () { |
| var t = createType({ |
| type: 'fixed', |
| size: 3, |
| name: 'Id' |
| }); |
| var aliases = t.getAliases(); |
| assert.deepEqual(aliases, []); |
| aliases.push('ID'); |
| assert.equal(t.getAliases().length, 1); |
| }); |
| |
| test('get size', function () { |
| var t = createType({type: 'fixed', size: 5, name: 'Id'}); |
| assert.equal(t.getSize(), 5); |
| }); |
| |
| test('resolve', function () { |
| var t1 = new types.FixedType({name: 'Id', size: 4}); |
| var t2 = new types.FixedType({name: 'Id', size: 4}); |
| assert.doesNotThrow(function () { t2.createResolver(t1); }); |
| t2 = new types.FixedType({name: 'Id2', size: 4}); |
| assert.throws(function () { t2.createResolver(t1); }); |
| t2 = new types.FixedType({name: 'Id2', size: 4, aliases: ['Id']}); |
| assert.doesNotThrow(function () { t2.createResolver(t1); }); |
| t2 = new types.FixedType({name: 'Id2', size: 5, aliases: ['Id']}); |
| assert.throws(function () { t2.createResolver(t1); }); |
| }); |
| |
| test('clone', function () { |
| var t = new types.FixedType({name: 'Id', size: 2}); |
| var s = '\x01\x02'; |
| var buf = new Buffer(s); |
| var clone; |
| clone = t.clone(buf); |
| assert.deepEqual(clone, buf); |
| clone[0] = 0; |
| assert.equal(buf[0], 1); |
| assert.throws(function () { t.clone(s); }); |
| clone = t.clone(buf.toJSON(), {coerceBuffers: true}); |
| assert.deepEqual(clone, buf); |
| assert.throws(function () { t.clone(1, {coerceBuffers: true}); }); |
| assert.throws(function () { t.clone(new Buffer([2])); }); |
| }); |
| |
| test('getSchema with extra fields', function () { |
| var t = createType({type: 'fixed', name: 'Id', size: 2, three: 3}); |
| t.one = 1; |
| assert.equal(t.getSchema(), '{"name":"Id","type":"fixed","size":2}'); |
| assert.equal(t.getSchema(true), '"Id"'); |
| }); |
| |
| test('fromString', function () { |
| var t = new types.FixedType({name: 'Id', size: 2}); |
| var s = '\x01\x02'; |
| var buf = new Buffer(s); |
| var clone = t.fromString(JSON.stringify(s)); |
| assert.deepEqual(clone, buf); |
| }); |
| |
| test('compare buffers', function () { |
| var t = createType({type: 'fixed', name: 'Id', size: 2}); |
| var b1 = new Buffer([1, 2]); |
| assert.equal(t.compareBuffers(b1, b1), 0); |
| var b2 = new Buffer([2, 2]); |
| assert.equal(t.compareBuffers(b1, b2), -1); |
| }); |
| |
| }); |
| |
| suite('MapType', function () { |
| |
| var data = [ |
| { |
| name: 'int', |
| schema: {values: 'int'}, |
| valid: [{one: 1}, {two: 2, o: 0}], |
| invalid: [1, {o: null}, [], undefined, {o: 'hi'}, {1: '', 2: 3}, ''], |
| check: assert.deepEqual |
| }, |
| { |
| name: 'enum', |
| schema: {values: {type: 'enum', name: 'a', symbols: ['A', 'B']}}, |
| valid: [{a: 'A'}, {a: 'A', b: 'B'}, {}], |
| invalid: [{o: 'a'}, {1: 'A', 2: 'b'}, {a: 3}], |
| check: assert.deepEqual |
| }, |
| { |
| name: 'array of string', |
| schema: {values: {type: 'array', items: 'string'}}, |
| valid: [{a: []}, {a: ['A'], b: ['B', '']}, {}], |
| invalid: [{o: 'a', b: []}, {a: [1, 2]}, {a: {b: ''}}], |
| check: assert.deepEqual |
| } |
| ]; |
| |
| var schemas = [ |
| {}, |
| {values: ''}, |
| {values: {type: 'array'}} |
| ]; |
| |
| testType(types.MapType, data, schemas); |
| |
| test('get values type', function () { |
| var t = new types.MapType({type: 'map', values: 'int'}); |
| assert.deepEqual(t.getValuesType(), createType('int')); |
| }); |
| |
| test('write int', function () { |
| var t = new types.MapType({type: 'map', values: 'int'}); |
| var buf = t.toBuffer({'\x01': 3, '\x02': 4}); |
| assert.deepEqual(buf, new Buffer([4, 2, 1, 6, 2, 2, 8, 0])); |
| }); |
| |
| test('read long', function () { |
| var t = new types.MapType({type: 'map', values: 'long'}); |
| var buf = new Buffer([4, 2, 1, 6, 2, 2, 8, 0]); |
| assert.deepEqual(t.fromBuffer(buf), {'\x01': 3, '\x02': 4}); |
| }); |
| |
| test('read with sizes', function () { |
| var t = new types.MapType({type: 'map', values: 'int'}); |
| var buf = new Buffer([1,6,2,97,2,0]); |
| assert.deepEqual(t.fromBuffer(buf), {a: 1}); |
| }); |
| |
| test('skip', function () { |
| var v1 = createType({ |
| name: 'Foo', |
| type: 'record', |
| fields: [ |
| {name: 'map', type: {type: 'map', values: 'int'}}, |
| {name: 'val', type: 'int'} |
| ] |
| }); |
| var v2 = createType({ |
| name: 'Foo', |
| type: 'record', |
| fields: [{name: 'val', type: 'int'}] |
| }); |
| var b1 = new Buffer([2,2,97,2,0,6]); // Without sizes. |
| var b2 = new Buffer([1,6,2,97,2,0,6]); // With sizes. |
| var resolver = v2.createResolver(v1); |
| assert.deepEqual(v2.fromBuffer(b1, resolver), {val: 3}); |
| assert.deepEqual(v2.fromBuffer(b2, resolver), {val: 3}); |
| }); |
| |
| test('resolve int > long', function () { |
| var t1 = new types.MapType({type: 'map', values: 'int'}); |
| var t2 = new types.MapType({type: 'map', values: 'long'}); |
| var resolver = t2.createResolver(t1); |
| var obj = {one: 1, two: 2}; |
| var buf = t1.toBuffer(obj); |
| assert.deepEqual(t2.fromBuffer(buf, resolver), obj); |
| }); |
| |
| test('resolve double > double', function () { |
| var t = new types.MapType({type: 'map', values: 'double'}); |
| var resolver = t.createResolver(t); |
| var obj = {one: 1, two: 2}; |
| var buf = t.toBuffer(obj); |
| assert.deepEqual(t.fromBuffer(buf, resolver), obj); |
| }); |
| |
| test('resolve invalid', function () { |
| var t1 = new types.MapType({type: 'map', values: 'int'}); |
| var t2 = new types.MapType({type: 'map', values: 'string'}); |
| assert.throws(function () { t2.createResolver(t1); }); |
| t2 = new types.ArrayType({type: 'array', items: 'string'}); |
| assert.throws(function () { t2.createResolver(t1); }); |
| }); |
| |
| test('resolve fixed', function () { |
| var t1 = createType({ |
| type: 'map', values: {name: 'Id', type: 'fixed', size: 2} |
| }); |
| var t2 = createType({ |
| type: 'map', values: { |
| name: 'Id2', aliases: ['Id'], type: 'fixed', size: 2 |
| } |
| }); |
| var resolver = t2.createResolver(t1); |
| var obj = {one: new Buffer([1, 2])}; |
| var buf = t1.toBuffer(obj); |
| assert.deepEqual(t2.fromBuffer(buf, resolver), obj); |
| }); |
| |
| test('clone', function () { |
| var t = new types.MapType({type: 'map', values: 'int'}); |
| var o = {one: 1, two: 2}; |
| var c = t.clone(o); |
| assert.deepEqual(c, o); |
| c.one = 3; |
| assert.equal(o.one, 1); |
| assert.throws(function () { t.clone(undefined); }); |
| }); |
| |
| test('clone coerce buffers', function () { |
| var t = new types.MapType({type: 'map', values: 'bytes'}); |
| var o = {one: {type: 'Buffer', data: [1]}}; |
| assert.throws(function () { t.clone(o); }); |
| var c = t.clone(o, {coerceBuffers: true}); |
| assert.deepEqual(c, {one: new Buffer([1])}); |
| }); |
| |
| test('compare buffers', function () { |
| var t = new types.MapType({type: 'map', values: 'bytes'}); |
| var b1 = t.toBuffer({}); |
| assert.throws(function () { t.compareBuffers(b1, b1); }); |
| }); |
| |
| test('isValid hook', function () { |
| var t = new types.MapType({type: 'map', values: 'int'}); |
| var o = {one: 1, two: 'deux', three: null, four: 4}; |
| var errs = {}; |
| assert(!t.isValid(o, {errorHook: hook})); |
| assert.deepEqual(errs, {two: 'deux', three: null}); |
| |
| function hook(path, obj, type) { |
| assert.strictEqual(type, t.getValuesType()); |
| assert.equal(path.length, 1); |
| errs[path[0]] = obj; |
| } |
| }); |
| |
| test('getName', function () { |
| var t = new types.MapType({type: 'map', values: 'int'}); |
| assert.strictEqual(t.getName(), undefined); |
| }); |
| |
| }); |
| |
| suite('ArrayType', function () { |
| |
| var data = [ |
| { |
| name: 'int', |
| schema: {items: 'int'}, |
| valid: [[1,3,4], []], |
| invalid: [1, {o: null}, undefined, ['a'], [true]], |
| check: assert.deepEqual |
| } |
| ]; |
| |
| var schemas = [ |
| {}, |
| {items: ''}, |
| ]; |
| |
| testType(types.ArrayType, data, schemas); |
| |
| test('get items type', function () { |
| var t = new types.ArrayType({type: 'array', items: 'int'}); |
| assert.deepEqual(t.getItemsType(), createType('int')); |
| }); |
| |
| test('read with sizes', function () { |
| var t = new types.ArrayType({type: 'array', items: 'int'}); |
| var buf = new Buffer([1,2,2,0]); |
| assert.deepEqual(t.fromBuffer(buf), [1]); |
| }); |
| |
| test('skip', function () { |
| var v1 = createType({ |
| name: 'Foo', |
| type: 'record', |
| fields: [ |
| {name: 'array', type: {type: 'array', items: 'int'}}, |
| {name: 'val', type: 'int'} |
| ] |
| }); |
| var v2 = createType({ |
| name: 'Foo', |
| type: 'record', |
| fields: [{name: 'val', type: 'int'}] |
| }); |
| var b1 = new Buffer([2,2,0,6]); // Without sizes. |
| var b2 = new Buffer([1,2,2,0,6]); // With sizes. |
| var resolver = v2.createResolver(v1); |
| assert.deepEqual(v2.fromBuffer(b1, resolver), {val: 3}); |
| assert.deepEqual(v2.fromBuffer(b2, resolver), {val: 3}); |
| }); |
| |
| test('resolve string items to bytes items', function () { |
| var t1 = new types.ArrayType({type: 'array', items: 'string'}); |
| var t2 = new types.ArrayType({type: 'array', items: 'bytes'}); |
| var resolver = t2.createResolver(t1); |
| var obj = ['\x01\x02']; |
| var buf = t1.toBuffer(obj); |
| assert.deepEqual(t2.fromBuffer(buf, resolver), [new Buffer([1, 2])]); |
| }); |
| |
| test('resolve invalid', function () { |
| var t1 = new types.ArrayType({type: 'array', items: 'string'}); |
| var t2 = new types.ArrayType({type: 'array', items: 'long'}); |
| assert.throws(function () { t2.createResolver(t1); }); |
| t2 = new types.MapType({type: 'map', values: 'string'}); |
| assert.throws(function () { t2.createResolver(t1); }); |
| }); |
| |
| test('clone', function () { |
| var t = new types.ArrayType({type: 'array', items: 'int'}); |
| var o = [1, 2]; |
| var c = t.clone(o); |
| assert.deepEqual(c, o); |
| c.one = 3; |
| assert.equal(o[0], 1); |
| assert.throws(function () { t.clone({}); }); |
| }); |
| |
| test('clone coerce buffers', function () { |
| var t = createType({ |
| type: 'array', |
| items: {type: 'fixed', name: 'Id', size: 2} |
| }); |
| var o = [{type: 'Buffer', data: [1, 2]}]; |
| assert.throws(function () { t.clone(o); }); |
| var c = t.clone(o, {coerceBuffers: true}); |
| assert.deepEqual(c, [new Buffer([1, 2])]); |
| }); |
| |
| test('compare buffers', function () { |
| var t = createType({type: 'array', items: 'int'}); |
| assert.equal(t.compareBuffers(t.toBuffer([]), t.toBuffer([])), 0); |
| assert.equal(t.compareBuffers(t.toBuffer([1, 2]), t.toBuffer([])), 1); |
| assert.equal(t.compareBuffers(t.toBuffer([1]), t.toBuffer([1, -1])), -1); |
| assert.equal(t.compareBuffers(t.toBuffer([1]), t.toBuffer([2])), -1); |
| assert.equal(t.compareBuffers(t.toBuffer([1, 2]), t.toBuffer([1])), 1); |
| }); |
| |
| test('compare', function () { |
| var t = createType({type: 'array', items: 'int'}); |
| assert.equal(t.compare([], []), 0); |
| assert.equal(t.compare([], [-1]), -1); |
| assert.equal(t.compare([1], [1]), 0); |
| assert.equal(t.compare([2], [1, 2]), 1); |
| }); |
| |
| test('isValid hook invalid array', function () { |
| var t = createType({type: 'array', items: 'int'}); |
| var hookCalled = false; |
| assert(!t.isValid({}, {errorHook: hook})); |
| assert(hookCalled); |
| |
| function hook(path, obj, type) { |
| assert.strictEqual(type, t); |
| assert.deepEqual(path, []); |
| hookCalled = true; |
| } |
| }); |
| |
| test('isValid hook invalid elems', function () { |
| var t = createType({type: 'array', items: 'int'}); |
| var paths = []; |
| assert(!t.isValid([0, 3, 'hi', 5, 'hey'], {errorHook: hook})); |
| assert.deepEqual(paths, [['2'], ['4']]); |
| |
| function hook(path, obj, type) { |
| assert.strictEqual(type, t.getItemsType()); |
| assert.equal(typeof obj, 'string'); |
| paths.push(path); |
| } |
| }); |
| |
| }); |
| |
| suite('RecordType', function () { |
| |
| var data = [ |
| { |
| name: 'union field null and string with default', |
| schema: { |
| type: 'record', |
| name: 'a', |
| fields: [{name: 'b', type: ['null', 'string'], 'default': null}] |
| }, |
| valid: [], |
| invalid: [], |
| check: assert.deepEqual |
| } |
| ]; |
| |
| var schemas = [ |
| {type: 'record', name: 'a', fields: ['null', 'string']}, |
| {type: 'record', name: 'a', fields: [{type: ['null', 'string']}]}, |
| { |
| type: 'record', |
| name: 'a', |
| fields: [{name: 'b', type: ['null', 'string'], 'default': 'a'}] |
| }, |
| {type: 'record', name: 'a', fields: {type: 'int', name: 'age'}} |
| ]; |
| |
| testType(types.RecordType, data, schemas); |
| |
| test('duplicate field names', function () { |
| assert.throws(function () { |
| createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int'}, {name: 'age', type: 'float'}] |
| }); |
| }); |
| }); |
| |
| test('default constructor', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int', 'default': 25}] |
| }); |
| var Person = type.getRecordConstructor(); |
| var p = new Person(); |
| assert.equal(p.age, 25); |
| assert.strictEqual(p.constructor, Person); |
| }); |
| |
| test('default check & write', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| {name: 'age', type: 'int', 'default': 25}, |
| {name: 'name', type: 'string', 'default': '\x01'} |
| ] |
| }); |
| assert.deepEqual(type.toBuffer({}), new Buffer([50, 2, 1])); |
| }); |
| |
| test('fixed string default', function () { |
| var s = '\x01\x04'; |
| var b = new Buffer(s); |
| var type = createType({ |
| type: 'record', |
| name: 'Object', |
| fields: [ |
| { |
| name: 'id', |
| type: {type: 'fixed', size: 2, name: 'Id'}, |
| 'default': s |
| } |
| ] |
| }); |
| var obj = new (type.getRecordConstructor())(); |
| assert.deepEqual(obj.id, new Buffer([1, 4])); |
| assert.deepEqual(type.toBuffer({}), b); |
| }); |
| |
| test('fixed buffer invalid default', function () { |
| assert.throws(function () { |
| createType({ |
| type: 'record', |
| name: 'Object', |
| fields: [ |
| { |
| name: 'id', |
| type: {type: 'fixed', size: 2, name: 'Id'}, |
| 'default': new Buffer([0]) |
| } |
| ] |
| }); |
| }); |
| }); |
| |
| test('union invalid default', function () { |
| assert.throws(function () { |
| createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'name', type: ['null', 'string'], 'default': ''}] |
| }); |
| }); |
| }); |
| |
| test('record default', function () { |
| var d = {street: null, zip: 123}; |
| var Person = createType({ |
| name: 'Person', |
| type: 'record', |
| fields: [ |
| { |
| name: 'address', |
| type: { |
| name: 'Address', |
| type: 'record', |
| fields: [ |
| {name: 'street', type: ['null', 'string']}, |
| {name: 'zip', type: ['int', 'string']} |
| ] |
| }, |
| 'default': d |
| } |
| ] |
| }).getRecordConstructor(); |
| var p = new Person(); |
| assert.deepEqual(p.address, {street: null, zip: {'int': 123}}); |
| }); |
| |
| test('record keyword field name', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'null', type: 'int'}] |
| }); |
| var Person = type.getRecordConstructor(); |
| assert.deepEqual(new Person(2), {'null': 2}); |
| }); |
| |
| test('record isValid', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int'}] |
| }); |
| var Person = type.getRecordConstructor(); |
| assert((new Person(20)).$isValid()); |
| assert(!(new Person()).$isValid()); |
| assert(!(new Person('a')).$isValid()); |
| }); |
| |
| test('record toBuffer', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int'}] |
| }); |
| var Person = type.getRecordConstructor(); |
| assert.deepEqual((new Person(48)).$toBuffer(), new Buffer([96])); |
| assert.throws(function () { (new Person()).$toBuffer(); }); |
| }); |
| |
| test('record compare', function () { |
| var P = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| {name: 'data', type: {type: 'map', values: 'int'}, order:'ignore'}, |
| {name: 'age', type: 'int'} |
| ] |
| }).getRecordConstructor(); |
| var p1 = new P({}, 1); |
| var p2 = new P({}, 2); |
| assert.equal(p1.$compare(p2), -1); |
| assert.equal(p2.$compare(p2), 0); |
| assert.equal(p2.$compare(p1), 1); |
| }); |
| |
| test('Record type', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int'}] |
| }); |
| var Person = type.getRecordConstructor(); |
| assert.strictEqual(Person.getType(), type); |
| }); |
| |
| test('mutable defaults', function () { |
| var Person = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| { |
| name: 'friends', |
| type: {type: 'array', items: 'string'}, |
| 'default': [] |
| } |
| ] |
| }).getRecordConstructor(); |
| var p1 = new Person(undefined); |
| assert.deepEqual(p1.friends, []); |
| p1.friends.push('ann'); |
| var p2 = new Person(undefined); |
| assert.deepEqual(p2.friends, []); |
| }); |
| |
| test('resolve alias', function () { |
| var v1 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'name', type: 'string'}] |
| }); |
| var p = v1.random(); |
| var buf = v1.toBuffer(p); |
| var v2 = createType({ |
| type: 'record', |
| name: 'Human', |
| aliases: ['Person'], |
| fields: [{name: 'name', type: 'string'}] |
| }); |
| var resolver = v2.createResolver(v1); |
| assert.deepEqual(v2.fromBuffer(buf, resolver), p); |
| var v3 = createType({ |
| type: 'record', |
| name: 'Human', |
| fields: [{name: 'name', type: 'string'}] |
| }); |
| assert.throws(function () { v3.createResolver(v1); }); |
| }); |
| |
| test('resolve alias with namespace', function () { |
| var v1 = createType({ |
| type: 'record', |
| name: 'Person', |
| namespace: 'earth', |
| fields: [{name: 'name', type: 'string'}] |
| }); |
| var v2 = createType({ |
| type: 'record', |
| name: 'Human', |
| aliases: ['Person'], |
| fields: [{name: 'name', type: 'string'}] |
| }); |
| assert.throws(function () { v2.createResolver(v1); }); |
| var v3 = createType({ |
| type: 'record', |
| name: 'Human', |
| aliases: ['earth.Person'], |
| fields: [{name: 'name', type: 'string'}] |
| }); |
| assert.doesNotThrow(function () { v3.createResolver(v1); }); |
| }); |
| |
| test('resolve skip field', function () { |
| var v1 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| {name: 'age', type: 'int'}, |
| {name: 'name', type: 'string'} |
| ] |
| }); |
| var p = {age: 25, name: 'Ann'}; |
| var buf = v1.toBuffer(p); |
| var v2 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'name', type: 'string'}] |
| }); |
| var resolver = v2.createResolver(v1); |
| assert.deepEqual(v2.fromBuffer(buf, resolver), {name: 'Ann'}); |
| }); |
| |
| test('resolve new field', function () { |
| var v1 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'name', type: 'string'}] |
| }); |
| var p = {name: 'Ann'}; |
| var buf = v1.toBuffer(p); |
| var v2 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| {name: 'age', type: 'int', 'default': 25}, |
| {name: 'name', type: 'string'} |
| ] |
| }); |
| var resolver = v2.createResolver(v1); |
| assert.deepEqual(v2.fromBuffer(buf, resolver), {name: 'Ann', age: 25}); |
| }); |
| |
| test('resolve new field no default', function () { |
| var v1 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'name', type: 'string'}] |
| }); |
| var v2 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| {name: 'age', type: 'int'}, |
| {name: 'name', type: 'string'} |
| ] |
| }); |
| assert.throws(function () { v2.createResolver(v1); }); |
| }); |
| |
| test('resolve from recursive schema', function () { |
| var v1 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'friends', type: {type: 'array', items: 'Person'}}] |
| }); |
| var v2 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int', 'default': -1}] |
| }); |
| var resolver = v2.createResolver(v1); |
| var p1 = {friends: [{friends: []}]}; |
| var p2 = v2.fromBuffer(v1.toBuffer(p1), resolver); |
| assert.deepEqual(p2, {age: -1}); |
| }); |
| |
| test('resolve to recursive schema', function () { |
| var v1 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int', 'default': -1}] |
| }); |
| var v2 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| { |
| name: 'friends', |
| type: {type: 'array', items: 'Person'}, |
| 'default': [] |
| } |
| ] |
| }); |
| var resolver = v2.createResolver(v1); |
| var p1 = {age: 25}; |
| var p2 = v2.fromBuffer(v1.toBuffer(p1), resolver); |
| assert.deepEqual(p2, {friends: []}); |
| }); |
| |
| test('resolve from both recursive schema', function () { |
| var v1 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| {name: 'friends', type: {type: 'array', items: 'Person'}}, |
| {name: 'age', type: 'int'} |
| ] |
| }); |
| var v2 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'friends', type: {type: 'array', items: 'Person'}}] |
| }); |
| var resolver = v2.createResolver(v1); |
| var p1 = {friends: [{age: 1, friends: []}], age: 10}; |
| var p2 = v2.fromBuffer(v1.toBuffer(p1), resolver); |
| assert.deepEqual(p2, {friends: [{friends: []}]}); |
| }); |
| |
| test('resolve multiple matching aliases', function () { |
| var v1 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| {name: 'phone', type: 'string'}, |
| {name: 'number', type: 'string'} |
| ] |
| }); |
| var v2 = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'number', type: 'string', aliases: ['phone']}] |
| }); |
| assert.throws(function () { v2.createResolver(v1); }); |
| }); |
| |
| test('getName', function () { |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| doc: 'Hi!', |
| namespace: 'earth', |
| aliases: ['Human'], |
| fields: [ |
| {name: 'friends', type: {type: 'array', items: 'string'}}, |
| {name: 'age', aliases: ['years'], type: {type: 'int'}} |
| ] |
| }); |
| assert.strictEqual(t.getName(), 'earth.Person'); |
| assert.equal(t.getName(true), 'record'); |
| }); |
| |
| test('getSchema', function () { |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| doc: 'Hi!', |
| namespace: 'earth', |
| aliases: ['Human'], |
| fields: [ |
| {name: 'friends', type: {type: 'array', items: 'string'}}, |
| {name: 'age', aliases: ['years'], type: {type: 'int'}} |
| ] |
| }); |
| assert.equal( |
| t.getSchema(), |
| '{"name":"earth.Person","type":"record","fields":[{"name":"friends","type":{"type":"array","items":"string"}},{"name":"age","type":"int"}]}' |
| ); |
| assert.equal(t.getSchema(true), '"earth.Person"'); |
| }); |
| |
| test('getSchema recursive schema', function () { |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| namespace: 'earth', |
| fields: [ |
| {name: 'friends', type: {type: 'array', items: 'Person'}}, |
| ] |
| }); |
| assert.equal( |
| t.getSchema(), |
| '{"name":"earth.Person","type":"record","fields":[{"name":"friends","type":{"type":"array","items":"earth.Person"}}]}' |
| ); |
| assert.equal(t.getSchema(true), '"earth.Person"'); |
| }); |
| |
| test('toString record', function () { |
| var T = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'pwd', type: 'bytes'}] |
| }).getRecordConstructor(); |
| var r = new T(new Buffer([1, 2])); |
| assert.equal(r.$toString(), T.getType().toString(r)); |
| }); |
| |
| test('clone', function () { |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int'}, {name: 'name', type: 'string'}] |
| }); |
| var Person = t.getRecordConstructor(); |
| var o = {age: 25, name: 'Ann'}; |
| var c = t.clone(o); |
| assert.deepEqual(c, o); |
| assert(c instanceof Person); |
| c.age = 26; |
| assert.equal(o.age, 25); |
| assert.strictEqual(c.$getType(), t); |
| assert.deepEqual(c.$clone(), c); |
| }); |
| |
| test('clone field hook', function () { |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int'}, {name: 'name', type: 'string'}] |
| }); |
| var o = {name: 'Ann', age: 25}; |
| var c = t.clone(o, {fieldHook: function (f, o, r) { |
| assert.strictEqual(r, t); |
| return f._type instanceof types.StringType ? o.toUpperCase() : o; |
| }}); |
| assert.deepEqual(c, {name: 'ANN', age: 25}); |
| }); |
| |
| test('get full name & aliases', function () { |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| namespace: 'a', |
| fields: [{name: 'age', type: 'int'}, {name: 'name', type: 'string'}] |
| }); |
| assert.equal(t.getName(), 'a.Person'); |
| assert.deepEqual(t.getAliases(), []); |
| }); |
| |
| test('field getters', function () { |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| namespace: 'a', |
| fields: [ |
| {name: 'age', type: 'int'}, |
| {name: 'name', type: 'string', aliases: ['word'], namespace: 'b'} |
| ] |
| }); |
| var fields = t.getFields(); |
| assert.deepEqual(fields[0].getAliases(), []); |
| assert.deepEqual(fields[1].getAliases(), ['word']); |
| assert.equal(fields[1].getName(), 'name'); // Namespaces are ignored. |
| assert.deepEqual(fields[1].getType(), createType('string')); |
| fields.push('null'); |
| assert.equal(t.getFields().length, 2); // No change. |
| }); |
| |
| test('field order', function () { |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int'}] |
| }); |
| var field = t.getFields()[0]; |
| assert.equal(field.getOrder(), 'ascending'); // Default. |
| }); |
| |
| test('compare buffers default order', function () { |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| {name: 'age', type: 'long'}, |
| {name: 'name', type: 'string'}, |
| {name: 'weight', type: 'float'}, |
| ] |
| }); |
| var b1 = t.toBuffer({age: 20, name: 'Ann', weight: 0.5}); |
| assert.equal(t.compareBuffers(b1, b1), 0); |
| var b2 = t.toBuffer({age: 20, name: 'Bob', weight: 0}); |
| assert.equal(t.compareBuffers(b1, b2), -1); |
| var b3 = t.toBuffer({age: 19, name: 'Carrie', weight: 0}); |
| assert.equal(t.compareBuffers(b1, b3), 1); |
| }); |
| |
| test('compare buffers custom order', function () { |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| {name: 'meta', type: {type: 'map', values: 'int'}, order: 'ignore'}, |
| {name: 'name', type: 'string', order: 'descending'} |
| ] |
| }); |
| var b1 = t.toBuffer({meta: {}, name: 'Ann'}); |
| assert.equal(t.compareBuffers(b1, b1), 0); |
| var b2 = t.toBuffer({meta: {foo: 1}, name: 'Bob'}); |
| assert.equal(t.compareBuffers(b1, b2), 1); |
| var b3 = t.toBuffer({meta: {foo: 0}, name: 'Alex'}); |
| assert.equal(t.compareBuffers(b1, b3), -1); |
| }); |
| |
| test('compare buffers invalid order', function () { |
| assert.throws(function () { createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int', order: 'up'}] |
| }); }); |
| }); |
| |
| test('error type', function () { |
| var t = createType({ |
| type: 'error', |
| name: 'Ouch', |
| fields: [{name: 'name', type: 'string'}] |
| }); |
| var E = t.getRecordConstructor(); |
| var err = new E('MyError'); |
| assert(err instanceof Error); |
| }); |
| |
| test('isValid hook', function () { |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| {name: 'age', type: 'int'}, |
| {name: 'names', type: {type: 'array', items: 'string'}} |
| ] |
| }); |
| var hasErr = false; |
| try { |
| assert(!t.isValid({age: 23, names: ['ann', null]}, {errorHook: hook})); |
| } catch (err) { |
| hasErr = true; |
| } |
| assert(hasErr); |
| hasErr = false; |
| try { |
| // Again to make sure `PATH` was correctly reset. |
| assert(!t.isValid({age: 23, names: ['ann', null]}, {errorHook: hook})); |
| } catch (err) { |
| hasErr = true; |
| } |
| assert(hasErr); |
| |
| function hook(path, obj, type) { |
| assert.strictEqual(type, t.getFields()[1].getType().getItemsType()); |
| assert.deepEqual(path, ['names', '1']); |
| throw new Error(); |
| } |
| }); |
| |
| test('isValid empty record', function () { |
| var t = createType({type: 'record', name: 'Person', fields: []}); |
| assert(t.isValid({})); |
| }); |
| |
| }); |
| |
| suite('AbstractLongType', function () { |
| |
| var fastLongType = new types.LongType(); |
| |
| suite('unpacked', function () { |
| |
| var slowLongType = types.LongType.using({ |
| fromBuffer: function (buf) { |
| var neg = buf[7] >> 7; |
| if (neg) { // Negative number. |
| invert(buf); |
| } |
| var n = buf.readInt32LE() + Math.pow(2, 32) * buf.readInt32LE(4); |
| if (neg) { |
| invert(buf); |
| n = -n - 1; |
| } |
| return n; |
| }, |
| toBuffer: function (n) { |
| var buf = new Buffer(8); |
| var neg = n < 0; |
| if (neg) { |
| invert(buf); |
| n = -n - 1; |
| } |
| buf.writeInt32LE(n | 0); |
| var h = n / Math.pow(2, 32) | 0; |
| buf.writeInt32LE(h ? h : (n >= 0 ? 0 : -1), 4); |
| if (neg) { |
| invert(buf); |
| } |
| return buf; |
| }, |
| isValid: function (n) { |
| return typeof n == 'number' && n % 1 === 0; |
| }, |
| fromJSON: function (n) { return n; }, |
| toJSON: function (n) { return n; }, |
| compare: function (n1, n2) { |
| return n1 === n2 ? 0 : (n1 < n2 ? -1 : 1); |
| } |
| }); |
| |
| test('encode', function () { |
| [123, -1, 321414, 900719925474090].forEach(function (n) { |
| assert.deepEqual(slowLongType.toBuffer(n), fastLongType.toBuffer(n)); |
| }); |
| }); |
| |
| test('decode', function () { |
| [123, -1, 321414, 900719925474090].forEach(function (n) { |
| var buf = fastLongType.toBuffer(n); |
| assert.deepEqual(slowLongType.fromBuffer(buf), n); |
| }); |
| }); |
| |
| test('clone', function () { |
| assert.equal(slowLongType.clone(123), 123); |
| assert.equal(slowLongType.fromString('-1'), -1); |
| assert.equal(slowLongType.toString(-1), '-1'); |
| }); |
| |
| test('random', function () { |
| assert(slowLongType.isValid(slowLongType.random())); |
| }); |
| |
| test('isValid hook', function () { |
| var s = 'hi'; |
| var errs = []; |
| assert(!slowLongType.isValid(s, {errorHook: hook})); |
| assert.deepEqual(errs, [s]); |
| assert.throws(function () { slowLongType.toBuffer(s); }); |
| |
| function hook(path, obj, type) { |
| assert.strictEqual(type, slowLongType); |
| assert.equal(path.length, 0); |
| errs.push(obj); |
| } |
| }); |
| |
| }); |
| |
| suite('packed', function () { |
| |
| var slowLongType = types.LongType.using({ |
| fromBuffer: function (buf) { |
| var tap = new Tap(buf); |
| return tap.readLong(); |
| }, |
| toBuffer: function (n) { |
| var buf = new Buffer(10); |
| var tap = new Tap(buf); |
| tap.writeLong(n); |
| return buf.slice(0, tap.pos); |
| }, |
| fromJSON: function (n) { return n; }, |
| toJSON: function (n) { return n; }, |
| isValid: function (n) { return typeof n == 'number' && n % 1 === 0; }, |
| compare: function (n1, n2) { |
| return n1 === n2 ? 0 : (n1 < n2 ? -1 : 1); |
| } |
| }, true); |
| |
| test('encode', function () { |
| [123, -1, 321414, 900719925474090].forEach(function (n) { |
| assert.deepEqual(slowLongType.toBuffer(n), fastLongType.toBuffer(n)); |
| }); |
| }); |
| |
| test('decode', function () { |
| [123, -1, 321414, 900719925474090].forEach(function (n) { |
| var buf = fastLongType.toBuffer(n); |
| assert.deepEqual(slowLongType.fromBuffer(buf), n); |
| }); |
| }); |
| |
| test('clone', function () { |
| assert.equal(slowLongType.clone(123), 123); |
| assert.equal(slowLongType.fromString('-1'), -1); |
| assert.equal(slowLongType.toString(-1), '-1'); |
| }); |
| |
| test('random', function () { |
| assert(slowLongType.isValid(slowLongType.random())); |
| }); |
| |
| }); |
| |
| test('incomplete buffer', function () { |
| // Check that `fromBuffer` doesn't get called. |
| var slowLongType = new types.LongType.using({ |
| fromBuffer: function () { throw new Error('no'); }, |
| toBuffer: null, |
| fromJSON: null, |
| toJSON: null, |
| isValid: null, |
| compare: null |
| }); |
| var buf = fastLongType.toBuffer(12314); |
| assert.deepEqual( |
| slowLongType.decode(buf.slice(0, 1)), |
| {value: undefined, offset: -1} |
| ); |
| }); |
| |
| }); |
| |
| suite('LogicalType', function () { |
| |
| function DateType(attrs, opts) { |
| types.LogicalType.call(this, attrs, opts, [types.LongType]); |
| } |
| util.inherits(DateType, types.LogicalType); |
| |
| DateType.prototype._fromValue = function (val) { return new Date(val); }; |
| |
| DateType.prototype._toValue = function (date) { return +date; }; |
| |
| DateType.prototype._resolve = function (type) { |
| if (type instanceof types.StringType) { |
| return function (str) { return new Date(Date.parse(str)); }; |
| } else if (type instanceof types.LongType) { |
| return this.fromValue; |
| } |
| }; |
| |
| function AgeType(attrs, opts) { |
| types.LogicalType.call(this, attrs, opts, [types.IntType]); |
| } |
| util.inherits(AgeType, types.LogicalType); |
| |
| AgeType.prototype._fromValue = function (val) { |
| if (val < 0) { throw new Error('invalid age'); } |
| return val; |
| }; |
| |
| AgeType.prototype._toValue = AgeType.prototype._fromValue; |
| |
| var logicalTypes = {age: AgeType, date: DateType}; |
| |
| test('valid type', function () { |
| var t = createType({ |
| type: 'long', |
| logicalType: 'date' |
| }, {logicalTypes: logicalTypes}); |
| assert(t instanceof DateType); |
| assert(t.getUnderlyingType() instanceof types.LongType); |
| assert(t.isValid(t.random())); |
| var d = new Date(123); |
| assert.equal(t.toString(d), '123'); |
| assert.deepEqual(t.fromString('123'), d); |
| assert.deepEqual(t.clone(d), d); |
| assert.equal(t.compare(d, d), 0); |
| assert.equal(t.getSchema(), '"long"'); |
| }); |
| |
| test('invalid type', function () { |
| var attrs = { |
| type: 'int', |
| logicalType: 'date' |
| }; |
| var t; |
| t = createType(attrs); // Missing. |
| assert(t instanceof types.IntType); |
| t = createType(attrs, {logicalTypes: logicalTypes}); // Invalid. |
| assert(t instanceof types.IntType); |
| assert.throws(function () { |
| createType(attrs, { |
| logicalTypes: logicalTypes, |
| assertLogicalTypes: true |
| }); |
| }); |
| }); |
| |
| test('nested types', function () { |
| var attrs = { |
| name: 'Person', |
| type: 'record', |
| fields: [ |
| {name: 'age', type: {type: 'int', logicalType: 'age'}}, |
| {name: 'time', type: {type: 'long', logicalType: 'date'}} |
| ] |
| }; |
| var base = createType(attrs); |
| var derived = createType(attrs, {logicalTypes: logicalTypes}); |
| var fields = derived.getFields(); |
| assert(fields[0].getType() instanceof AgeType); |
| assert(fields[1].getType() instanceof DateType); |
| var date = new Date(Date.now()); |
| var buf = base.toBuffer({age: 12, time: +date}); |
| var person = derived.fromBuffer(buf); |
| assert.deepEqual(person.age, 12); |
| assert.deepEqual(person.time, date); |
| assert.throws(function () { derived.toBuffer({age: -1, date: date}); }); |
| |
| var invalid = {age: -1, time: date}; |
| assert.throws(function () { derived.toBuffer(invalid); }); |
| var hasError = false; |
| derived.isValid(invalid, {errorHook: function (path, any, type) { |
| hasError = true; |
| assert.deepEqual(path, ['age']); |
| assert.equal(any, -1); |
| assert(type instanceof AgeType); |
| }}); |
| assert(hasError); |
| }); |
| |
| test('recursive', function () { |
| |
| function Person(friends) { this.friends = friends || []; } |
| |
| function PersonType(attrs, opts) { |
| types.LogicalType.call(this, attrs, opts); |
| } |
| util.inherits(PersonType, types.LogicalType); |
| |
| PersonType.prototype._fromValue = function (val) { |
| return new Person(val.friends); |
| }; |
| |
| PersonType.prototype._toValue = function (val) { return val; }; |
| |
| var t = createType({ |
| type: 'record', |
| name: 'Person', |
| namespace: 'earth', |
| logicalType: 'person', |
| fields: [ |
| {name: 'friends', type: {type: 'array', items: 'Person'}}, |
| ] |
| }, {logicalTypes: {'person': PersonType}}); |
| |
| var p1 = new Person([new Person()]); |
| var buf = t.toBuffer(p1); |
| var p2 = t.fromBuffer(buf); |
| assert(p2 instanceof Person); |
| assert(p2.friends[0] instanceof Person); |
| assert.deepEqual(p2, p1); |
| }); |
| |
| test('resolve underlying > logical', function () { |
| var t1 = createType({type: 'string'}); |
| var t2 = createType({ |
| type: 'long', |
| logicalType: 'date' |
| }, {logicalTypes: logicalTypes}); |
| |
| var d1 = new Date(Date.now()); |
| var buf = t1.toBuffer('' + d1); |
| var res = t2.createResolver(t1); |
| assert.throws(function () { t2.createResolver(createType('float')); }); |
| var d2 = t2.fromBuffer(buf, res); |
| assert.deepEqual('' + d2, '' + d1); // Rounding error on date objects. |
| }); |
| |
| test('resolve logical > underlying', function () { |
| var t1 = createType({ |
| type: 'long', |
| logicalType: 'date' |
| }, {logicalTypes: logicalTypes}); |
| var t2 = createType({type: 'double'}); // Note long > double too. |
| |
| var d = new Date(Date.now()); |
| var buf = t1.toBuffer(d); |
| var res = t2.createResolver(t1); |
| assert.throws(function () { createType('int').createResolver(t1); }); |
| assert.equal(t2.fromBuffer(buf, res), +d); |
| }); |
| |
| suite('union logical types', function () { |
| // Unions are slightly tricky to override with logical types since their |
| // schemas aren't represented as objects. |
| |
| var schema = [ |
| 'null', |
| { |
| name: 'Person', |
| type: 'record', |
| fields: [ |
| {name: 'name', type: 'string'}, |
| {name: 'age', type: ['null', 'int'], 'default': null} |
| ] |
| } |
| ]; |
| |
| function createUnionTypeHook(Type) { |
| var visited = []; |
| return function(attrs, opts) { |
| if (attrs instanceof Array && !~visited.indexOf(attrs)) { |
| visited.push(attrs); |
| return new Type(attrs, opts); |
| } |
| }; |
| } |
| |
| test('unwrapped', function () { |
| |
| /** |
| * A generic union type which exposes its values directly. |
| * |
| * This implementation is pretty minimal, we could optimize it by caching |
| * the underlying union's type names for example. |
| * |
| */ |
| function UnwrappedUnionType(attrs, opts) { |
| types.LogicalType.call(this, attrs, opts); |
| } |
| util.inherits(UnwrappedUnionType, types.LogicalType); |
| |
| UnwrappedUnionType.prototype._fromValue = function (val) { |
| return val === null ? null : val[Object.keys(val)[0]]; |
| }; |
| |
| UnwrappedUnionType.prototype._toValue = function (any) { |
| return this.getUnderlyingType().clone(any, {wrapUnions: true}); |
| }; |
| |
| var t1 = createType( |
| schema, |
| {typeHook: createUnionTypeHook(UnwrappedUnionType)} |
| ); |
| var obj = {name: 'Ann', age: 23}; |
| assert(t1.isValid(obj)); |
| var buf = t1.toBuffer(obj); |
| var t2 = createType(schema); |
| assert.deepEqual( |
| t2.fromBuffer(buf), |
| {Person: {name: 'Ann', age: {'int': 23}}} |
| ); |
| |
| }); |
| |
| test('optional', function () { |
| |
| /** |
| * A basic optional type. |
| * |
| * It assumes an underlying union of the form `["null", ???]`. |
| * |
| * Enhancements include: |
| * |
| * + Performing a check in the constructor on the underlying type (i.e. |
| * union with the correct form). |
| * + Code-generating the conversion methods (especially a constructor |
| * for `_toValue`). |
| * |
| */ |
| function OptionalType(attrs, opts) { |
| types.LogicalType.call(this, attrs, opts); |
| var type = this.getUnderlyingType().getTypes()[1]; |
| this._name = type.getName() || type.getName(true); |
| } |
| util.inherits(OptionalType, types.LogicalType); |
| |
| OptionalType.prototype._fromValue = function (val) { |
| return val === null ? null : val[this._name]; |
| }; |
| |
| OptionalType.prototype._toValue = function (any) { |
| if (any === null) { |
| return null; |
| } |
| var obj = {}; |
| obj[this._name] = any; |
| return obj; |
| }; |
| |
| var t1 = createType( |
| schema, |
| {typeHook: createUnionTypeHook(OptionalType)} |
| ); |
| var obj = {name: 'Ann', age: 23}; |
| assert(t1.isValid(obj)); |
| var buf = t1.toBuffer(obj); |
| var t2 = createType(schema); |
| assert.deepEqual( |
| t2.fromBuffer(buf), |
| {Person: {name: 'Ann', age: {'int': 23}}} |
| ); |
| |
| }); |
| |
| }); |
| |
| test('even integer', function () { |
| function EvenIntType(attrs, opts) { |
| types.LogicalType.call(this, attrs, opts, [types.IntType]); |
| } |
| util.inherits(EvenIntType, types.LogicalType); |
| EvenIntType.prototype._fromValue = function (val) { |
| this._assertValid(val); |
| return val; |
| }; |
| EvenIntType.prototype._toValue = EvenIntType.prototype._fromValue; |
| EvenIntType.prototype._assertValid = function (any) { |
| if (any !== (any | 0) || any % 2) { |
| throw new Error('invalid'); |
| } |
| }; |
| |
| var opts = {logicalTypes: {'even-integer': EvenIntType}}; |
| var t = createType({type: 'int', logicalType: 'even-integer'}, opts); |
| assert(t.isValid(2)); |
| assert(!t.isValid(3)); |
| assert(!t.isValid('abc')); |
| assert.equal(t.fromBuffer(new Buffer([4])), 2); |
| assert.equal(t.clone(4), 4); |
| assert.equal(t.fromString('6'), 6); |
| assert.throws(function () { t.clone(3); }); |
| assert.throws(function () { t.fromString('5'); }); |
| assert.throws(function () { t.toBuffer(3); }); |
| assert.throws(function () { t.fromBuffer(new Buffer([2])); }); |
| }); |
| |
| }); |
| |
| suite('createType', function () { |
| |
| test('null type', function () { |
| assert.throws(function () { createType(null); }); |
| }); |
| |
| test('unknown types', function () { |
| assert.throws(function () { createType('a'); }); |
| assert.throws(function () { createType({type: 'b'}); }); |
| }); |
| |
| test('namespaced type', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Human', |
| namespace: 'earth', |
| fields: [ |
| { |
| name: 'id', |
| type: {type: 'fixed', name: 'Id', size: 2, namespace: 'all'} |
| }, |
| { |
| name: 'alien', |
| type: { |
| type: 'record', |
| name: 'Alien', |
| namespace: 'all', |
| fields: [ |
| {name: 'friend', type: 'earth.Human'}, |
| {name: 'id', type: 'Id'}, |
| ] |
| } |
| } |
| ] |
| }); |
| assert.equal(type._name, 'earth.Human'); |
| assert.equal(type._fields[0]._type._name, 'all.Id'); |
| assert.equal(type._fields[1]._type._name, 'all.Alien'); |
| }); |
| |
| test('wrapped primitive', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'nothing', type: {type: 'null'}}] |
| }); |
| assert.strictEqual(type._fields[0]._type.constructor, types.NullType); |
| }); |
| |
| test('fromBuffer truncated', function () { |
| var type = createType('int'); |
| assert.throws(function () { |
| type.fromBuffer(new Buffer([128])); |
| }); |
| }); |
| |
| test('fromBuffer bad resolver', function () { |
| var type = createType('int'); |
| assert.throws(function () { |
| type.fromBuffer(new Buffer([0]), 123, {}); |
| }); |
| }); |
| |
| test('fromBuffer trailing', function () { |
| var type = createType('int'); |
| assert.throws(function () { |
| type.fromBuffer(new Buffer([0, 2])); |
| }); |
| }); |
| |
| test('fromBuffer trailing with resolver', function () { |
| var type = createType('int'); |
| var resolver = type.createResolver(createType(['int'])); |
| assert.equal(type.fromBuffer(new Buffer([0, 2]), resolver), 1); |
| }); |
| |
| test('toBuffer', function () { |
| var type = createType('int'); |
| assert.throws(function () { type.toBuffer('abc'); }); |
| assert.doesNotThrow(function () { type.toBuffer(123); }); |
| }); |
| |
| test('toBuffer and resize', function () { |
| var type = createType('string'); |
| assert.deepEqual(type.toBuffer('\x01', 1), new Buffer([2, 1])); |
| }); |
| |
| test('type hook', function () { |
| var refs = []; |
| var ts = []; |
| var o = { |
| type: 'record', |
| name: 'Human', |
| fields: [ |
| {name: 'age', type: 'int'}, |
| {name: 'name', type: {type: 'string'}} |
| ] |
| }; |
| createType(o, {typeHook: hook}); |
| assert.equal(ts.length, 1); |
| assert.equal(ts[0].getName(), 'Human'); |
| |
| function hook(schema, opts) { |
| if (~refs.indexOf(schema)) { |
| // Already seen this schema. |
| return; |
| } |
| refs.push(schema); |
| |
| var type = createType(schema, opts); |
| if (type instanceof types.RecordType) { |
| ts.push(type); |
| } |
| return type; |
| } |
| }); |
| |
| test('type hook invalid return value', function () { |
| assert.throws(function () { |
| createType({type: 'int'}, {typeHook: hook}); |
| }); |
| |
| function hook() { return 'int'; } |
| }); |
| |
| test('fingerprint', function () { |
| var t = createType('int'); |
| var buf = new Buffer('ef524ea1b91e73173d938ade36c1db32', 'hex'); |
| assert.deepEqual(t.getFingerprint('md5'), buf); |
| assert.deepEqual(t.getFingerprint(), buf); |
| }); |
| |
| test('getSchema default', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Human', |
| fields: [ |
| {name: 'id1', type: ['string', 'null'], 'default': ''}, |
| {name: 'id2', type: ['null', 'string'], 'default': null} |
| ] |
| }); |
| assert.deepEqual( |
| JSON.parse(type.getSchema()), |
| { |
| type: 'record', |
| name: 'Human', |
| fields: [ |
| {name: 'id1', type: ['string', 'null']}, // Stripped defaults. |
| {name: 'id2', type: ['null', 'string']} |
| ] |
| } |
| ); |
| }); |
| |
| }); |
| |
| suite('fromString', function () { |
| |
| test('int', function () { |
| var t = createType('int'); |
| assert.equal(t.fromString('2'), 2); |
| assert.throws(function () { t.fromString('"a"'); }); |
| }); |
| |
| test('string', function () { |
| var t = createType('string'); |
| assert.equal(t.fromString('"2"'), '2'); |
| assert.throws(function () { t.fromString('a'); }); |
| }); |
| |
| test('coerce buffers', function () { |
| var t = createType({ |
| name: 'Ids', |
| type: 'record', |
| fields: [{name: 'id1', type: {name: 'Id1', type: 'fixed', size: 2}}] |
| }); |
| var o = {id1: new Buffer([0, 1])}; |
| var s = '{"id1": "\\u0000\\u0001"}'; |
| var c = t.fromString(s); |
| assert.deepEqual(c, o); |
| assert(c instanceof t.getRecordConstructor()); |
| }); |
| |
| }); |
| |
| suite('toString', function () { |
| |
| test('int', function () { |
| var t = createType('int'); |
| assert.equal(t.toString(2), '2'); |
| assert.throws(function () { t.toString('a'); }); |
| }); |
| |
| }); |
| |
| suite('resolve', function () { |
| |
| test('non type', function () { |
| var t = createType({type: 'map', values: 'int'}); |
| var obj = {type: 'map', values: 'int'}; |
| assert.throws(function () { t.createResolver(obj); }); |
| }); |
| |
| test('union to valid union', function () { |
| var t1 = createType(['int', 'string']); |
| var t2 = createType(['null', 'string', 'long']); |
| var resolver = t2.createResolver(t1); |
| var buf = t1.toBuffer({'int': 12}); |
| assert.deepEqual(t2.fromBuffer(buf, resolver), {'long': 12}); |
| }); |
| |
| test('union to invalid union', function () { |
| var t1 = createType(['int', 'string']); |
| var t2 = createType(['null', 'long']); |
| assert.throws(function () { t2.createResolver(t1); }); |
| }); |
| |
| test('union to non union', function () { |
| var t1 = createType(['int', 'long']); |
| var t2 = createType('long'); |
| var resolver = t2.createResolver(t1); |
| var buf = t1.toBuffer({'int': 12}); |
| assert.equal(t2.fromBuffer(buf, resolver), 12); |
| buf = new Buffer([4, 0]); |
| assert.throws(function () { t2.fromBuffer(buf, resolver); }); |
| }); |
| |
| test('union to invalid non union', function () { |
| var t1 = createType(['int', 'long']); |
| var t2 = createType('int'); |
| assert.throws(function() { t2.createResolver(t1); }); |
| }); |
| |
| }); |
| |
| suite('type references', function () { |
| |
| test('existing', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'so', type: 'Person'}] |
| }); |
| assert.strictEqual(type, type._fields[0]._type); |
| }); |
| |
| test('namespaced', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| { |
| name: 'so', |
| type: { |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int'}], |
| namespace: 'a' |
| } |
| } |
| ] |
| }); |
| assert.equal(type._name, 'Person'); |
| assert.equal(type._fields[0]._type._name, 'a.Person'); |
| }); |
| |
| test('redefining', function () { |
| assert.throws(function () { |
| createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [ |
| { |
| name: 'so', |
| type: { |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int'}] |
| } |
| } |
| ] |
| }); |
| }); |
| }); |
| |
| test('missing', function () { |
| assert.throws(function () { |
| createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'so', type: 'Friend'}] |
| }); |
| }); |
| }); |
| |
| test('redefining primitive', function () { |
| assert.throws( // Unqualified. |
| function () { createType({type: 'fixed', name: 'int', size: 2}); } |
| ); |
| assert.throws( // Qualified. |
| function () { |
| createType({type: 'fixed', name: 'int', size: 2, namespace: 'a'}); |
| } |
| ); |
| }); |
| |
| test('aliases', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Person', |
| namespace: 'a', |
| aliases: ['Human', 'b.Being'], |
| fields: [{name: 'age', type: 'int'}] |
| }); |
| assert.deepEqual(type._aliases, ['a.Human', 'b.Being']); |
| }); |
| |
| test('invalid', function () { |
| // Name. |
| assert.throws(function () { |
| createType({type: 'fixed', name: 'ID$', size: 3}); |
| }); |
| // Namespace. |
| assert.throws(function () { |
| createType({type: 'fixed', name: 'ID', size: 3, namespace: '1a'}); |
| }); |
| // Qualified. |
| assert.throws(function () { |
| createType({type: 'fixed', name: 'a.2.ID', size: 3}); |
| }); |
| }); |
| |
| }); |
| |
| suite('decode', function () { |
| |
| test('long valid', function () { |
| var t = createType('long'); |
| var buf = new Buffer([0, 128, 2, 0]); |
| var res = t.decode(buf, 1); |
| assert.deepEqual(res, {value: 128, offset: 3}); |
| }); |
| |
| test('bytes invalid', function () { |
| var t = createType('bytes'); |
| var buf = new Buffer([4, 1]); |
| var res = t.decode(buf, 0); |
| assert.deepEqual(res, {value: undefined, offset: -1}); |
| }); |
| |
| }); |
| |
| suite('encode', function () { |
| |
| test('int valid', function () { |
| var t = createType('int'); |
| var buf = new Buffer(2); |
| buf.fill(0); |
| var n = t.encode(5, buf, 1); |
| assert.equal(n, 2); |
| assert.deepEqual(buf, new Buffer([0, 10])); |
| }); |
| |
| test('string invalid', function () { |
| var t = createType('string'); |
| var buf = new Buffer(1); |
| var n = t.encode('\x01\x02', buf, 0); |
| assert.equal(n, -2); |
| }); |
| |
| test('invalid', function () { |
| var t = createType('float'); |
| var buf = new Buffer(2); |
| assert.throws(function () { t.encode('hi', buf, 0); }); |
| }); |
| |
| }); |
| |
| suite('inspect', function () { |
| |
| test('type', function () { |
| assert.equal(createType('int').inspect(), '<IntType>'); |
| assert.equal( |
| createType({type: 'map', values: 'string'}).inspect(), |
| '<MapType {"values":"string"}>' |
| ); |
| assert.equal( |
| createType({type: 'fixed', name: 'Id', size: 2}).inspect(), |
| '<FixedType "Id">' |
| ); |
| }); |
| |
| test('field', function () { |
| var type = createType({ |
| type: 'record', |
| name: 'Person', |
| fields: [{name: 'age', type: 'int'}] |
| }); |
| var field = type.getFields()[0]; |
| assert.equal(field.inspect(), '<Field "age">'); |
| }); |
| |
| test('resolver', function () { |
| var t1 = createType('int'); |
| var t2 = createType('double'); |
| var resolver = t2.createResolver(t1); |
| assert.equal(resolver.inspect(), '<Resolver>'); |
| }); |
| |
| }); |
| |
| test('reset', function () { |
| types.Type.__reset(0); |
| var t = createType('string'); |
| var buf = t.toBuffer('\x01'); |
| assert.deepEqual(buf, new Buffer([2, 1])); |
| }); |
| |
| }); |
| |
| function testType(Type, data, invalidSchemas) { |
| |
| data.forEach(function (elem) { |
| test('roundtrip', function () { |
| var type = new Type(elem.schema); |
| elem.valid.forEach(function (v) { |
| assert(type.isValid(v), '' + v); |
| var fn = elem.check || assert.deepEqual; |
| fn(type.fromBuffer(type.toBuffer(v)), v); |
| fn(type.fromString(type.toString(v), {coerceBuffers: true}), v); |
| }); |
| elem.invalid.forEach(function (v) { |
| assert(!type.isValid(v), '' + v); |
| assert.throws(function () { type.isValid(v, {errorHook: hook}); }); |
| assert.throws(function () { type.toBuffer(v); }); |
| |
| function hook() { throw new Error(); } |
| }); |
| var n = 50; |
| while (n--) { |
| // Run a few times to make sure we cover any branches. |
| assert(type.isValid(type.random())); |
| } |
| }); |
| }); |
| |
| test('skip', function () { |
| data.forEach(function (elem) { |
| var fn = elem.check || assert.deepEqual; |
| var items = elem.valid; |
| if (items.length > 1) { |
| var type = new Type(elem.schema); |
| var buf = new Buffer(1024); |
| var tap = new Tap(buf); |
| type._write(tap, items[0]); |
| type._write(tap, items[1]); |
| tap.pos = 0; |
| type._skip(tap); |
| fn(type._read(tap), items[1]); |
| } |
| }); |
| }); |
| |
| if (invalidSchemas) { |
| test('invalid', function () { |
| invalidSchemas.forEach(function (schema) { |
| assert.throws(function () { new Type(schema); }); |
| }); |
| }); |
| } |
| |
| } |
| |
| function getResolver(reader, writer) { |
| return createType(reader).createResolver(createType(writer)); |
| } |
| |
| function floatEquals(a, b) { |
| return Math.abs((a - b) / Math.min(a, b)) < 1e-7; |
| } |
| |
| function invert(buf) { |
| var len = buf.length; |
| while (len--) { |
| buf[len] = ~buf[len]; |
| } |
| } |