blob: c2b9246713ed6d378800d711e1cae58719c67619 [file]
/*
* 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
*
* http://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.
*/
import assert from 'assert';
import ioc, { createPreciseReader, DataType } from '../../../lib/structure/io/binary/GraphBinary.js';
import StreamReader from '../../../lib/structure/io/binary/internals/StreamReader.js';
import { Float, Double, Int, Long, Short, Byte, toFloat, toDouble, toInt, toLong, toShort, toByte, unwrap } from '../../../lib/utils.js';
import Connection from '../../../lib/driver/connection.js';
import { Path } from '../../../lib/structure/graph.js';
const { anySerializer, graphBinaryReader } = ioc;
describe('Precise Mode Tests', () => {
let preciseReader;
before(() => {
preciseReader = createPreciseReader();
});
async function deserializeWithPrecise(buf) {
return preciseReader.ioc.anySerializer.deserialize(StreamReader.fromBuffer(buf));
}
async function deserializeWithDefault(buf) {
return anySerializer.deserialize(StreamReader.fromBuffer(buf));
}
describe('Basic deserialization', () => {
it('FLOAT bytes → Float instance', async () => {
const buf = anySerializer.serialize(toFloat(1.5));
const result = await deserializeWithPrecise(buf);
assert.ok(result instanceof Float);
assert.strictEqual(result.value, 1.5);
assert.strictEqual(result.type, 'float');
});
it('DOUBLE bytes → Double instance', async () => {
const buf = anySerializer.serialize(toDouble(3.14));
const result = await deserializeWithPrecise(buf);
assert.ok(result instanceof Double);
assert.strictEqual(result.value, 3.14);
assert.strictEqual(result.type, 'double');
});
it('INT bytes → Int instance', async () => {
const buf = anySerializer.serialize(toInt(42));
const result = await deserializeWithPrecise(buf);
assert.ok(result instanceof Int);
assert.strictEqual(result.value, 42);
assert.strictEqual(result.type, 'int');
});
it('LONG bytes (safe range) → Long instance with number value', async () => {
const buf = anySerializer.serialize(toLong(42));
const result = await deserializeWithPrecise(buf);
assert.ok(result instanceof Long);
assert.strictEqual(result.value, 42);
assert.strictEqual(result.type, 'long');
});
it('LONG bytes (unsafe range) → Long instance with bigint value', async () => {
const buf = anySerializer.serialize(toLong(9007199254740993n));
const result = await deserializeWithPrecise(buf);
assert.ok(result instanceof Long);
assert.strictEqual(result.value, 9007199254740993n);
});
it('SHORT bytes → Short instance', async () => {
const buf = anySerializer.serialize(toShort(5));
const result = await deserializeWithPrecise(buf);
assert.ok(result instanceof Short);
assert.strictEqual(result.value, 5);
assert.strictEqual(result.type, 'short');
});
it('BYTE bytes → Byte instance', async () => {
const buf = anySerializer.serialize(toByte(127));
const result = await deserializeWithPrecise(buf);
assert.ok(result instanceof Byte);
assert.strictEqual(result.value, 127);
assert.strictEqual(result.type, 'byte');
});
});
describe('Nested structures', () => {
it('MAP containing Float values → Float wrappers inside Map', async () => {
const map = new Map([['x', toFloat(1.5)]]);
const buf = anySerializer.serialize(map);
const result = await deserializeWithPrecise(buf);
assert.ok(result.get('x') instanceof Float);
assert.strictEqual(result.get('x').value, 1.5);
});
it('LIST with mixed numeric types → each wrapped correctly', async () => {
const list = [toFloat(1.5), toInt(2), toDouble(3.14)];
const buf = anySerializer.serialize(list);
const result = await deserializeWithPrecise(buf);
assert.ok(result[0] instanceof Float);
assert.ok(result[1] instanceof Int);
assert.ok(result[2] instanceof Double);
});
it('PATH with numeric vertex IDs → wrapped correctly', async () => {
const path = new Path([['a'], ['b']], [toInt(1), toInt(2)]);
const buf = anySerializer.serialize(path);
const result = await deserializeWithPrecise(buf);
assert.ok(result instanceof Path);
assert.ok(result.objects[0] instanceof Int);
assert.strictEqual(result.objects[0].value, 1);
assert.ok(result.objects[1] instanceof Int);
assert.strictEqual(result.objects[1].value, 2);
});
it('LIST with Short, Byte, Long → each wrapped correctly', async () => {
const list = [toShort(5), toByte(127), toLong(99)];
const buf = anySerializer.serialize(list);
const result = await deserializeWithPrecise(buf);
assert.ok(result[0] instanceof Short);
assert.strictEqual(result[0].value, 5);
assert.ok(result[1] instanceof Byte);
assert.strictEqual(result[1].value, 127);
assert.ok(result[2] instanceof Long);
assert.strictEqual(result[2].value, 99);
});
it('Non-numeric types still deserialize correctly', async () => {
const list = ['hello', true, 'world'];
const buf = anySerializer.serialize(list);
const result = await deserializeWithPrecise(buf);
assert.deepStrictEqual(result, ['hello', true, 'world']);
});
});
describe('Precise reader error paths', () => {
it('returns null for a null-flagged numeric value', async () => {
const buf = Buffer.from([DataType.INT, 0x01]);
const result = await deserializeWithPrecise(buf);
assert.strictEqual(result, null);
});
it('returns null for a null-flagged LONG value', async () => {
const buf = Buffer.from([DataType.LONG, 0x01]);
assert.strictEqual(await deserializeWithPrecise(buf), null);
});
it('returns null for a null-flagged FLOAT value', async () => {
const buf = Buffer.from([DataType.FLOAT, 0x01]);
assert.strictEqual(await deserializeWithPrecise(buf), null);
});
it('returns null for a null-flagged DOUBLE value', async () => {
const buf = Buffer.from([DataType.DOUBLE, 0x01]);
assert.strictEqual(await deserializeWithPrecise(buf), null);
});
it('returns null for a null-flagged SHORT value', async () => {
const buf = Buffer.from([DataType.SHORT, 0x01]);
assert.strictEqual(await deserializeWithPrecise(buf), null);
});
it('returns null for a null-flagged BYTE value', async () => {
const buf = Buffer.from([DataType.BYTE, 0x01]);
assert.strictEqual(await deserializeWithPrecise(buf), null);
});
it('throws on invalid value_flag', async () => {
const buf = Buffer.from([DataType.INT, 0xFF, 0, 0, 0, 0]);
await assert.rejects(() => deserializeWithPrecise(buf), /AnySerializer: unexpected \{value_flag}=0x/);
});
it('throws on unknown type_code', async () => {
const buf = Buffer.from([0xEE, 0x00]);
await assert.rejects(() => deserializeWithPrecise(buf), /AnySerializer: unknown \{type_code}=0xee/);
});
it('deserializes value_flag 0x02 as non-null', async () => {
const buf = Buffer.from([DataType.INT, 0x02, 0x00, 0x00, 0x00, 0x2A]);
const result = await deserializeWithPrecise(buf);
assert.ok(result instanceof Int);
assert.strictEqual(result.value, 42);
});
it('wraps deserializeValue errors with position info', async () => {
const buf = Buffer.from([DataType.INT, 0x00]);
await assert.rejects(() => deserializeWithPrecise(buf), /IntSerializer\.deserializeValue\(\) at position \d+/);
});
});
describe('Wrapper behavior', () => {
it('Float valueOf works in arithmetic', () => {
assert.strictEqual(new Float(1.5) + 1, 2.5);
});
it('Int valueOf works in arithmetic', () => {
assert.strictEqual(new Int(42) + 0, 42);
});
it('Double valueOf works in arithmetic', () => {
assert.strictEqual(new Double(3.14) + 0, 3.14);
});
it('Short valueOf works in arithmetic', () => {
assert.strictEqual(new Short(5) + 1, 6);
});
it('Byte valueOf works in arithmetic', () => {
assert.strictEqual(new Byte(127) + 0, 127);
});
it('Long with unsafe bigint throws RangeError on arithmetic', () => {
assert.throws(() => new Long(9007199254740993n) + 1, RangeError);
});
it('Long with unsafe string throws RangeError on arithmetic', () => {
assert.throws(() => new Long('9007199254740993') + 1, RangeError);
});
it('Long with safe bigint works in arithmetic', () => {
assert.strictEqual(new Long(42n) + 1, 43);
});
it('Long toPrimitive string hint works for unsafe values', () => {
assert.strictEqual(`${new Long(9007199254740993n)}`, '9007199254740993');
});
it('Float toPrimitive string hint', () => {
assert.strictEqual(`${new Float(1.5)}`, '1.5');
});
it('Double toPrimitive string hint', () => {
assert.strictEqual(`${new Double(3.14)}`, '3.14');
});
it('Int toPrimitive string hint', () => {
assert.strictEqual(`${new Int(67)}`, '67');
});
it('Short toPrimitive string hint', () => {
assert.strictEqual(`${new Short(5)}`, '5');
});
it('Byte toPrimitive string hint', () => {
assert.strictEqual(`${new Byte(127)}`, '127');
});
it('JSON.stringify Float', () => {
assert.strictEqual(JSON.stringify(new Float(1.5)), '1.5');
});
it('JSON.stringify Long with bigint', () => {
assert.strictEqual(JSON.stringify(new Long(9007199254740993n)), '"9007199254740993"');
});
it('Long valueOf with safe string returns number', () => {
assert.strictEqual(new Long('42').valueOf(), 42);
});
it('Long valueOf with unsafe string throws RangeError', () => {
assert.throws(() => new Long('9007199254740993').valueOf(), RangeError);
});
it('JSON.stringify Long with string value preserves string', () => {
assert.strictEqual(JSON.stringify(new Long('9007199254740993')), '"9007199254740993"');
});
it('JSON.stringify Long with number value is a number', () => {
assert.strictEqual(JSON.stringify(new Long(42)), '42');
});
it('JSON.stringify Double', () => {
assert.strictEqual(JSON.stringify(new Double(3.14)), '3.14');
});
it('JSON.stringify Int', () => {
assert.strictEqual(JSON.stringify(new Int(67)), '67');
});
it('JSON.stringify Short', () => {
assert.strictEqual(JSON.stringify(new Short(5)), '5');
});
it('JSON.stringify Byte', () => {
assert.strictEqual(JSON.stringify(new Byte(127)), '127');
});
it('unwrap Float', () => {
assert.strictEqual(unwrap(new Float(1.5)), 1.5);
});
it('unwrap Long with bigint', () => {
assert.strictEqual(unwrap(new Long(42n)), 42n);
});
it('unwrap Long with string', () => {
assert.strictEqual(unwrap(new Long('123')), '123');
});
it('unwrap Long with number', () => {
assert.strictEqual(unwrap(new Long(42)), 42);
});
it('unwrap Int', () => {
assert.strictEqual(unwrap(new Int(42)), 42);
});
it('unwrap Double', () => {
assert.strictEqual(unwrap(new Double(3.14)), 3.14);
});
it('unwrap Short', () => {
assert.strictEqual(unwrap(new Short(5)), 5);
});
it('unwrap Byte', () => {
assert.strictEqual(unwrap(new Byte(127)), 127);
});
it('unwrap plain number passthrough', () => {
assert.strictEqual(unwrap(42), 42);
});
it('unwrap null passthrough', () => {
assert.strictEqual(unwrap(null), null);
});
it('unwrap undefined passthrough', () => {
assert.strictEqual(unwrap(undefined), undefined);
});
});
describe('Long constructor validation', () => {
it('rejects non-numeric string', () => {
assert.throws(() => new Long('abc'), TypeError);
});
it('rejects injection attempt', () => {
assert.throws(() => new Long('1L).drop()'), TypeError);
});
it('rejects empty string', () => {
assert.throws(() => new Long(''), TypeError);
});
it('rejects non-integer number', () => {
assert.throws(() => new Long(1.5), TypeError);
});
it('accepts exact int64 max (bigint)', () => {
const l = new Long(9223372036854775807n);
assert.strictEqual(l.value, 9223372036854775807n);
});
it('accepts exact int64 min (bigint)', () => {
const l = new Long(-9223372036854775808n);
assert.strictEqual(l.value, -9223372036854775808n);
});
it('rejects one above int64 max (bigint)', () => {
assert.throws(() => new Long(9223372036854775808n), RangeError);
});
it('rejects one below int64 min (bigint)', () => {
assert.throws(() => new Long(-9223372036854775809n), RangeError);
});
it('accepts exact int64 max (string)', () => {
const l = new Long('9223372036854775807');
assert.strictEqual(l.value, '9223372036854775807');
});
it('rejects one above int64 max (string)', () => {
assert.throws(() => new Long('9223372036854775808'), RangeError);
});
it('rejects negative zero string', () => {
assert.throws(() => new Long('-0'), TypeError);
});
it('accepts negative zero number', () => {
const l = new Long(-0);
assert.strictEqual(l.valueOf(), -0);
});
it('rejects leading zeros in string', () => {
assert.throws(() => new Long('0042'), TypeError);
});
});
describe('Float constructor validation', () => {
it('rejects non-number argument', () => {
assert.throws(() => new Float('1.5'), TypeError);
});
});
describe('Double constructor validation', () => {
it('rejects non-number argument', () => {
assert.throws(() => new Double('3.14'), TypeError);
});
});
describe('Int constructor validation', () => {
it('accepts exact int32 max', () => {
assert.strictEqual(new Int(2147483647).value, 2147483647);
});
it('accepts exact int32 min', () => {
assert.strictEqual(new Int(-2147483648).value, -2147483648);
});
it('rejects above int32 max', () => {
assert.throws(() => new Int(2147483648), RangeError);
});
it('rejects below int32 min', () => {
assert.throws(() => new Int(-2147483649), RangeError);
});
it('rejects non-integer', () => {
assert.throws(() => new Int(1.5), TypeError);
});
it('rejects non-number argument', () => {
assert.throws(() => new Int('5'), TypeError);
});
});
describe('Short constructor validation', () => {
it('accepts exact int16 max', () => {
assert.strictEqual(new Short(32767).value, 32767);
});
it('accepts exact int16 min', () => {
assert.strictEqual(new Short(-32768).value, -32768);
});
it('rejects above int16 max', () => {
assert.throws(() => new Short(32768), RangeError);
});
it('rejects below int16 min', () => {
assert.throws(() => new Short(-32769), RangeError);
});
it('rejects non-integer', () => {
assert.throws(() => new Short(1.5), TypeError);
});
it('rejects non-number argument', () => {
assert.throws(() => new Short('5'), TypeError);
});
});
describe('Byte constructor validation', () => {
it('accepts exact int8 max', () => {
assert.strictEqual(new Byte(127).value, 127);
});
it('accepts exact int8 min', () => {
assert.strictEqual(new Byte(-128).value, -128);
});
it('rejects above int8 max', () => {
assert.throws(() => new Byte(128), RangeError);
});
it('rejects below int8 min', () => {
assert.throws(() => new Byte(-129), RangeError);
});
it('rejects non-integer', () => {
assert.throws(() => new Byte(1.5), TypeError);
});
it('rejects non-number argument', () => {
assert.throws(() => new Byte('127'), TypeError);
});
});
describe('Backward compatibility', () => {
it('default graphBinaryReader still returns plain numbers after createPreciseReader()', async () => {
const buf = anySerializer.serialize(toFloat(1.5));
const result = await deserializeWithDefault(buf);
assert.strictEqual(result, 1.5);
assert.ok(!(result instanceof Float));
});
});
describe('Connection option wiring', () => {
it('preciseNumbers: true uses a precise reader', () => {
const conn = new Connection('http://localhost:8182', { preciseNumbers: true });
assert.ok(conn._reader !== graphBinaryReader);
});
it('explicit reader takes precedence over preciseNumbers', () => {
const customReader = { custom: true };
const conn = new Connection('http://localhost:8182', { reader: customReader, preciseNumbers: true });
assert.strictEqual(conn._reader, customReader);
});
it('default uses the default reader', () => {
const conn = new Connection('http://localhost:8182', {});
assert.strictEqual(conn._reader, graphBinaryReader);
});
});
});