blob: fc9f8bf2aef4370ed6dbf3869124ccf422a95e9e [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 from '../../../lib/structure/io/binary/GraphBinary.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', () => {
const result = anySerializer.deserialize(Buffer.from([0xFE, 0x01]));
assert.strictEqual(result.v, null);
assert.strictEqual(result.len, 2);
});
});
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', () => {
const result = ioc[serializer].deserialize(Buffer.from([code, 0x01]), true);
assert.strictEqual(result.v, null);
assert.strictEqual(result.len, 2);
});
});
});
// EnumSerializer has special null handling - it doesn't support null/undefined directly
describe('DIRECTION', () => {
it('deserialize fully-qualified null', () => {
const result = ioc.enumSerializer.deserialize(Buffer.from([DataType.DIRECTION, 0x01]), true);
assert.strictEqual(result.v, null);
assert.strictEqual(result.len, 2);
});
});
describe('T', () => {
it('deserialize fully-qualified null', () => {
const result = ioc.enumSerializer.deserialize(Buffer.from([DataType.T, 0x01]), true);
assert.strictEqual(result.v, null);
assert.strictEqual(result.len, 2);
});
});
describe('MERGE', () => {
it('deserialize fully-qualified null', () => {
const result = ioc.enumSerializer.deserialize(Buffer.from([DataType.MERGE, 0x01]), true);
assert.strictEqual(result.v, null);
assert.strictEqual(result.len, 2);
});
});
});
describe('per-type bare null', () => {
const bareNullTests = [
{ name: 'INT', serializer: 'intSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: 0 },
{ name: 'LONG', serializer: 'longSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), defaultValue: 0 },
{ name: 'STRING', serializer: 'stringSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: '' },
{ name: 'DOUBLE', serializer: 'doubleSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), defaultValue: 0.0 },
{ name: 'FLOAT', serializer: 'floatSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: 0.0 },
{ name: 'BOOLEAN', serializer: 'booleanSerializer', expected: Buffer.from([0x00]), defaultValue: false },
{ name: 'SHORT', serializer: 'shortSerializer', expected: Buffer.from([0x00, 0x00]), defaultValue: 0 },
{ name: 'BYTE', serializer: 'byteSerializer', expected: Buffer.from([0x00]), defaultValue: 0 },
{ name: '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', serializer: 'bigIntegerSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x01, 0x00]), defaultValue: BigInt(0) },
{ name: 'DATETIME', serializer: 'dateTimeSerializer', expected: Buffer.alloc(18), defaultValue: null },
{ name: 'BINARY', serializer: 'binarySerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: Buffer.alloc(0) }
];
bareNullTests.forEach(({ name, 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', () => {
const result = ioc[serializer].deserialize(expected, false);
if (defaultValue === null) {
// DATETIME: 18 zero bytes produce a valid Date object but with implementation-defined value
assert.instanceOf(result.v, Date);
} else if (typeof defaultValue === 'bigint') {
assert.strictEqual(result.v, defaultValue);
} else if (Buffer.isBuffer(defaultValue)) {
assert.isTrue(Buffer.isBuffer(result.v));
assert.isTrue(result.v.equals(defaultValue));
} else if (defaultValue instanceof Date) {
assert.instanceOf(result.v, Date);
} else {
assert.strictEqual(result.v, defaultValue);
}
assert.strictEqual(result.len, expected.length);
});
});
});
// Collection types have different bare null behavior
const collectionTests = [
{ name: 'LIST', serializer: 'listSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: [] },
{ name: 'SET', serializer: 'setSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: new Set() },
{ name: 'MAP', serializer: 'mapSerializer', expected: Buffer.from([0x00, 0x00, 0x00, 0x00]), defaultValue: new Map() }
];
collectionTests.forEach(({ name, 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', () => {
const result = ioc[serializer].deserialize(expected, false);
if (defaultValue instanceof Set) {
assert.instanceOf(result.v, Set);
assert.strictEqual(result.v.size, 0);
} else if (defaultValue instanceof Map) {
assert.instanceOf(result.v, Map);
assert.strictEqual(result.v.size, 0);
} else if (Array.isArray(defaultValue)) {
assert.isArray(result.v);
assert.strictEqual(result.v.length, 0);
}
assert.strictEqual(result.len, expected.length);
});
});
});
// Graph types serialize to empty structures
const graphTests = [
{ name: 'VERTEX', serializer: 'vertexSerializer' },
{ name: 'EDGE', serializer: 'edgeSerializer' },
{ name: 'PROPERTY', serializer: 'propertySerializer' },
{ name: 'VERTEXPROPERTY', serializer: 'vertexPropertySerializer' },
{ name: 'PATH', serializer: 'pathSerializer' }
];
graphTests.forEach(({ name, 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', () => {
const nullBytes = ioc[serializer].serialize(null, false);
const result = ioc[serializer].deserialize(nullBytes, false);
assert.isNotNull(result.v);
assert.strictEqual(result.len, nullBytes.length);
});
});
});
});
});