blob: 9a206dc2fd055d2f4e8b08d895608cfd23b6bdea [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.
*/
/*
* Null and undefined handling tests for GraphBinaryV4. Validates
* fully-qualified and bare null serialization/deserialization for
* every supported type.
*/
import { assert } from 'chai';
import ioc, {enumSerializer} from '../../../lib/structure/io/binary/GraphBinary.js';
import StreamReader from '../../../lib/structure/io/binary/internals/StreamReader.js';
const { anySerializer, DataType } = ioc;
describe('GraphBinary v4 Null Handling Tests', () => {
describe('anySerializer level', () => {
it('serialize null', () => {
const result = anySerializer.serialize(null);
assert.deepStrictEqual(result, Buffer.from([0xFE, 0x01]));
});
it('serialize undefined', () => {
const result = anySerializer.serialize(undefined);
assert.deepStrictEqual(result, Buffer.from([0xFE, 0x01]));
});
it('deserialize unspecified null', async () => {
const result = await anySerializer.deserialize(StreamReader.fromBuffer(Buffer.from([0xFE, 0x01])));
assert.strictEqual(result, null);
});
});
describe('per-type fully-qualified null', () => {
const types = [
{ name: 'INT', code: DataType.INT, serializer: 'intSerializer' },
{ name: 'LONG', code: DataType.LONG, serializer: 'longSerializer' },
{ name: 'STRING', code: DataType.STRING, serializer: 'stringSerializer' },
{ name: 'DOUBLE', code: DataType.DOUBLE, serializer: 'doubleSerializer' },
{ name: 'FLOAT', code: DataType.FLOAT, serializer: 'floatSerializer' },
{ name: 'BOOLEAN', code: DataType.BOOLEAN, serializer: 'booleanSerializer' },
{ name: 'SHORT', code: DataType.SHORT, serializer: 'shortSerializer' },
{ name: 'BYTE', code: DataType.BYTE, serializer: 'byteSerializer' },
{ name: 'UUID', code: DataType.UUID, serializer: 'uuidSerializer' },
{ name: 'BIGINTEGER', code: DataType.BIGINTEGER, serializer: 'bigIntegerSerializer' },
{ name: 'DATETIME', code: DataType.DATETIME, serializer: 'dateTimeSerializer' },
{ name: 'BINARY', code: DataType.BINARY, serializer: 'binarySerializer' },
{ name: 'LIST', code: DataType.LIST, serializer: 'listSerializer' },
{ name: 'SET', code: DataType.SET, serializer: 'setSerializer' },
{ name: 'MAP', code: DataType.MAP, serializer: 'mapSerializer' },
{ name: 'VERTEX', code: DataType.VERTEX, serializer: 'vertexSerializer' },
{ name: 'EDGE', code: DataType.EDGE, serializer: 'edgeSerializer' },
{ name: 'PROPERTY', code: DataType.PROPERTY, serializer: 'propertySerializer' },
{ name: 'VERTEXPROPERTY', code: DataType.VERTEXPROPERTY, serializer: 'vertexPropertySerializer' },
{ name: 'PATH', code: DataType.PATH, serializer: 'pathSerializer' }
];
types.forEach(({ name, code, serializer }) => {
describe(name, () => {
it('serialize null fully-qualified', () => {
const result = ioc[serializer].serialize(null, true);
assert.deepStrictEqual(result, Buffer.from([code, 0x01]));
});
it('serialize undefined fully-qualified', () => {
const result = ioc[serializer].serialize(undefined, true);
assert.deepStrictEqual(result, Buffer.from([code, 0x01]));
});
it('deserialize fully-qualified null', async () => {
const result = await ioc[serializer].deserialize(StreamReader.fromBuffer(Buffer.from([code, 0x01])));
assert.strictEqual(result, null);
});
});
});
// EnumSerializer has special null handling - it doesn't support null/undefined directly
describe('DIRECTION', () => {
it('deserialize fully-qualified null', async () => {
const result = await ioc.enumSerializer.deserialize(StreamReader.fromBuffer(Buffer.from([DataType.DIRECTION, 0x01])));
assert.strictEqual(result, null);
});
});
describe('T', () => {
it('deserialize fully-qualified null', async () => {
const result = await ioc.enumSerializer.deserialize(StreamReader.fromBuffer(Buffer.from([DataType.T, 0x01])));
assert.strictEqual(result, null);
});
});
describe('MERGE', () => {
it('deserialize fully-qualified null', async () => {
const result = await ioc.enumSerializer.deserialize(StreamReader.fromBuffer(Buffer.from([DataType.MERGE, 0x01])));
assert.strictEqual(result, null);
});
});
});
describe('per-type bare null', () => {
const bareNullTests = [
{ name: 'INT', code: DataType.INT, serializer: 'intSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: 0 },
{ name: 'LONG', code: DataType.LONG, serializer: 'longSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), defaultValue: 0 },
{ name: 'STRING', code: DataType.STRING, serializer: 'stringSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: '' },
{ name: 'DOUBLE', code: DataType.DOUBLE, serializer: 'doubleSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), defaultValue: 0.0 },
{ name: 'FLOAT', code: DataType.FLOAT, serializer: 'floatSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: 0.0 },
{ name: 'BOOLEAN', code: DataType.BOOLEAN, serializer: 'booleanSerializer', expected: Buffer.from([0x00]), defaultValue: false },
{ name: 'SHORT', code: DataType.SHORT, serializer: 'shortSerializer', expected: Buffer.from([0x00, 0x00]), defaultValue: 0 },
{ name: 'BYTE', code: DataType.BYTE, serializer: 'byteSerializer', expected: Buffer.from([0x00]), defaultValue: 0 },
{ name: 'UUID', code: DataType.UUID, serializer: 'uuidSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), defaultValue: '00000000-0000-0000-0000-000000000000' },
{ name: 'BIGINTEGER', code: DataType.BIGINTEGER, serializer: 'bigIntegerSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x01, 0x00]), defaultValue: BigInt(0) },
{ name: 'DATETIME', code: DataType.DATETIME, serializer: 'dateTimeSerializer', expected: Buffer.alloc(18), defaultValue: null },
{ name: 'BINARY', code: DataType.BINARY, serializer: 'binarySerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: Buffer.alloc(0) }
];
bareNullTests.forEach(({ name, code, serializer, expected, defaultValue }) => {
describe(name, () => {
it('serialize null bare', () => {
const result = ioc[serializer].serialize(null, false);
assert.deepStrictEqual(result, expected);
});
it('serialize undefined bare', () => {
const result = ioc[serializer].serialize(undefined, false);
assert.deepStrictEqual(result, expected);
});
it('deserialize bare null', async () => {
const reader = StreamReader.fromBuffer(expected);
const result = await ioc[serializer].deserializeValue(reader, 0x00, code);
if (defaultValue === null) {
// DATETIME: 18 zero bytes produce a valid Date object but with implementation-defined value
assert.instanceOf(result, Date);
} else if (typeof defaultValue === 'bigint') {
assert.strictEqual(result, defaultValue);
} else if (Buffer.isBuffer(defaultValue)) {
assert.isTrue(Buffer.isBuffer(result));
assert.isTrue(result.equals(defaultValue));
} else if (defaultValue instanceof Date) {
assert.instanceOf(result, Date);
} else {
assert.strictEqual(result, defaultValue);
}
assert.strictEqual(reader.position, expected.length);
});
});
});
// Collection types have different bare null behavior
const collectionTests = [
{ name: 'LIST', code: DataType.LIST, serializer: 'listSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: [] },
{ name: 'SET', code: DataType.SET, serializer: 'setSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: new Set() },
{ name: 'MAP', code: DataType.MAP, serializer: 'mapSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: new Map() }
];
collectionTests.forEach(({ name, code, serializer, expected, defaultValue }) => {
describe(name, () => {
it('serialize null bare', () => {
const result = ioc[serializer].serialize(null, false);
assert.deepStrictEqual(result, expected);
});
it('serialize undefined bare', () => {
const result = ioc[serializer].serialize(undefined, false);
assert.deepStrictEqual(result, expected);
});
it('deserialize bare null', async () => {
const reader = StreamReader.fromBuffer(expected);
const result = await ioc[serializer].deserializeValue(reader, 0x00, code);
if (defaultValue instanceof Set) {
assert.instanceOf(result, Set);
assert.strictEqual(result.size, 0);
} else if (defaultValue instanceof Map) {
assert.instanceOf(result, Map);
assert.strictEqual(result.size, 0);
} else if (Array.isArray(defaultValue)) {
assert.isArray(result);
assert.strictEqual(result.length, 0);
}
assert.strictEqual(reader.position, expected.length);
});
});
});
// Graph types serialize to empty structures
const graphTests = [
{ name: 'VERTEX', code: DataType.VERTEX, serializer: 'vertexSerializer' },
{ name: 'EDGE', code: DataType.EDGE, serializer: 'edgeSerializer' },
{ name: 'PROPERTY', code: DataType.PROPERTY, serializer: 'propertySerializer' },
{ name: 'VERTEXPROPERTY', code: DataType.VERTEXPROPERTY, serializer: 'vertexPropertySerializer' },
{ name: 'PATH', code: DataType.PATH, serializer: 'pathSerializer' }
];
graphTests.forEach(({ name, code, serializer }) => {
describe(name, () => {
it('serialize null bare produces non-empty bytes', () => {
const result = ioc[serializer].serialize(null, false);
assert.isTrue(result.length > 0);
});
it('serialize undefined bare produces non-empty bytes', () => {
const result = ioc[serializer].serialize(undefined, false);
assert.isTrue(result.length > 0);
});
it('deserialize bare null produces default object', async () => {
const nullBytes = ioc[serializer].serialize(null, false);
const reader = StreamReader.fromBuffer(nullBytes);
const result = await ioc[serializer].deserializeValue(reader, 0x00, code);
assert.isNotNull(result);
assert.strictEqual(reader.position, nullBytes.length);
});
});
});
});
});