blob: 1d4616beaef0fd93e436e6f3173d94021fc6a65d [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.
*/
/*
* Error handling tests for GraphBinaryV4 serializers. Validates behavior for
* malformed input: invalid buffers, unknown type codes, bad value flags,
* truncated data, negative lengths, and unsupported types.
*/
import { assert } from 'chai';
import { Buffer } from 'buffer';
import ioc from '../../../lib/structure/io/binary/GraphBinary.js';
import { P, TextP, Traverser } from '../../../lib/process/traversal.js';
import { OptionsStrategy } from '../../../lib/process/traversal-strategy.js';
const { anySerializer, intSerializer, longSerializer, stringSerializer, listSerializer, mapSerializer, uuidSerializer, dateTimeSerializer, floatSerializer, shortSerializer, byteSerializer, bigIntegerSerializer, binarySerializer, setSerializer, enumSerializer } = ioc;
describe('GraphBinary v4 Error Cases', () => {
describe('Buffer validation', () => {
it('undefined buffer throws error', () => {
assert.throws(() => anySerializer.deserialize(undefined), /buffer is missing/);
});
it('null buffer throws error', () => {
assert.throws(() => anySerializer.deserialize(null), /buffer is missing/);
});
it('non-Buffer object throws error', () => {
assert.throws(() => anySerializer.deserialize('not a buffer'), /buffer is missing/);
});
it('empty buffer throws error', () => {
assert.throws(() => anySerializer.deserialize(Buffer.alloc(0)), /buffer is empty/);
});
it('buffer with only type_code, no value_flag throws error', () => {
assert.throws(() => anySerializer.deserialize(Buffer.from([0x01])), /value_flag.*missing/);
});
it('individual serializer with undefined buffer throws error', () => {
assert.throws(() => intSerializer.deserialize(undefined), /buffer is missing/);
});
it('individual serializer with null buffer throws error', () => {
assert.throws(() => intSerializer.deserialize(null), /buffer is missing/);
});
it('individual serializer with empty buffer throws error', () => {
assert.throws(() => intSerializer.deserialize(Buffer.alloc(0)), /buffer is empty/);
});
});
describe('Type code errors', () => {
it('unknown type code throws error', () => {
assert.throws(() => anySerializer.deserialize(Buffer.from([0x99, 0x00])), /unknown.*type_code/);
});
it('wrong type code for INT throws error', () => {
assert.throws(() => intSerializer.deserialize(Buffer.from([0x02, 0x00, 0x00, 0x00, 0x00, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for LONG throws error', () => {
assert.throws(() => longSerializer.deserialize(Buffer.from([0x01, 0x00, 0x00, 0x00, 0x00, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for STRING throws error', () => {
assert.throws(() => stringSerializer.deserialize(Buffer.from([0x01, 0x00, 0x00, 0x00, 0x00, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for DOUBLE throws error', () => {
assert.throws(() => ioc.doubleSerializer.deserialize(Buffer.from([0x01, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for BOOLEAN throws error', () => {
assert.throws(() => ioc.booleanSerializer.deserialize(Buffer.from([0x01, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for UUID throws error', () => {
assert.throws(() => uuidSerializer.deserialize(Buffer.from([0x01, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for LIST throws error', () => {
assert.throws(() => listSerializer.deserialize(Buffer.from([0x01, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for MAP throws error', () => {
assert.throws(() => mapSerializer.deserialize(Buffer.from([0x01, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for FLOAT throws error', () => {
assert.throws(() => floatSerializer.deserialize(Buffer.from([0x09, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for SHORT throws error', () => {
assert.throws(() => shortSerializer.deserialize(Buffer.from([0x27, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for BYTE throws error', () => {
assert.throws(() => byteSerializer.deserialize(Buffer.from([0x25, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for BIGINTEGER throws error', () => {
assert.throws(() => bigIntegerSerializer.deserialize(Buffer.from([0x24, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for BINARY throws error', () => {
assert.throws(() => binarySerializer.deserialize(Buffer.from([0x26, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for SET throws error', () => {
assert.throws(() => setSerializer.deserialize(Buffer.from([0x0C, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for DATETIME throws error', () => {
assert.throws(() => dateTimeSerializer.deserialize(Buffer.from([0x05, 0x00])), /unexpected.*type_code/);
});
it('wrong type code for ENUM throws error', () => {
assert.throws(() => enumSerializer.deserialize(Buffer.from([0x19, 0x00])), /unexpected.*type_code/);
});
});
describe('Value flag errors', () => {
it('invalid value_flag 0x03 for INT throws error', () => {
assert.throws(() => intSerializer.deserialize(Buffer.from([0x01, 0x03])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x0F for STRING throws error', () => {
assert.throws(() => stringSerializer.deserialize(Buffer.from([0x03, 0x0F])), /unexpected.*value_flag/);
});
it('invalid value_flag 0xFF for LIST throws error', () => {
assert.throws(() => listSerializer.deserialize(Buffer.from([0x09, 0xFF])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x05 for MAP throws error', () => {
assert.throws(() => mapSerializer.deserialize(Buffer.from([0x0A, 0x05])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x10 for DOUBLE throws error', () => {
assert.throws(() => ioc.doubleSerializer.deserialize(Buffer.from([0x07, 0x10])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x04 for FLOAT throws error', () => {
assert.throws(() => floatSerializer.deserialize(Buffer.from([0x08, 0x04])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x06 for SHORT throws error', () => {
assert.throws(() => shortSerializer.deserialize(Buffer.from([0x26, 0x06])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x07 for BYTE throws error', () => {
assert.throws(() => byteSerializer.deserialize(Buffer.from([0x24, 0x07])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x08 for BIGINTEGER throws error', () => {
assert.throws(() => bigIntegerSerializer.deserialize(Buffer.from([0x23, 0x08])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x09 for BINARY throws error', () => {
assert.throws(() => binarySerializer.deserialize(Buffer.from([0x25, 0x09])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x0A for SET throws error', () => {
assert.throws(() => setSerializer.deserialize(Buffer.from([0x0B, 0x0A])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x0B for DATETIME throws error', () => {
assert.throws(() => dateTimeSerializer.deserialize(Buffer.from([0x04, 0x0B])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x0C for UUID throws error', () => {
assert.throws(() => uuidSerializer.deserialize(Buffer.from([0x0C, 0x0C])), /unexpected.*value_flag/);
});
it('invalid value_flag 0x0D for ENUM throws error', () => {
assert.throws(() => enumSerializer.deserialize(Buffer.from([0x18, 0x0D])), /unexpected.*value_flag/);
});
it('missing value_flag for INT throws error', () => {
assert.throws(() => intSerializer.deserialize(Buffer.from([0x01])), /value_flag.*missing/);
});
it('missing value_flag for STRING throws error', () => {
assert.throws(() => stringSerializer.deserialize(Buffer.from([0x03])), /value_flag.*missing/);
});
it('missing value_flag for LIST throws error', () => {
assert.throws(() => listSerializer.deserialize(Buffer.from([0x09])), /value_flag.*missing/);
});
it('missing value_flag for MAP throws error', () => {
assert.throws(() => mapSerializer.deserialize(Buffer.from([0x0A])), /value_flag.*missing/);
});
});
describe('Truncated data', () => {
it('INT with only 2 of 4 value bytes throws error', () => {
assert.throws(() => intSerializer.deserialize(Buffer.from([0x01, 0x00, 0x00, 0x01])), /unexpected.*value.*length/);
});
it('LONG with only 4 of 8 value bytes throws error', () => {
assert.throws(() => longSerializer.deserialize(Buffer.from([0x02, 0x00, 0x00, 0x00, 0x00, 0x01])), /unexpected.*value.*length/);
});
it('DOUBLE with only 4 of 8 value bytes throws error', () => {
assert.throws(() => ioc.doubleSerializer.deserialize(Buffer.from([0x07, 0x00, 0x00, 0x00, 0x00, 0x01])), /unexpected.*value.*length/);
});
it('FLOAT with only 2 of 4 value bytes throws error', () => {
assert.throws(() => ioc.floatSerializer.deserialize(Buffer.from([0x08, 0x00, 0x00, 0x01])), /unexpected.*value.*length/);
});
it('SHORT with only 1 of 2 value bytes throws error', () => {
assert.throws(() => ioc.shortSerializer.deserialize(Buffer.from([0x26, 0x00, 0x01])), /unexpected.*value.*length/);
});
it('STRING with length 10 but only 3 bytes throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x03, 0x00]), // STRING, value_flag=0x00
Buffer.from([0x00, 0x00, 0x00, 0x0A]), // length=10
Buffer.from([0x41, 0x42, 0x43]) // only 3 bytes: "ABC"
]);
assert.throws(() => stringSerializer.deserialize(buffer), /unexpected.*text_value.*length/);
});
it('LIST with length 5 but only 1 item throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x09, 0x00]), // LIST, value_flag=0x00
Buffer.from([0x00, 0x00, 0x00, 0x05]), // length=5
Buffer.from([0x01, 0x00, 0x00, 0x00, 0x00, 0x01]) // only 1 INT item
]);
assert.throws(() => listSerializer.deserialize(buffer), /item_1.*buffer is empty/);
});
it('MAP with length 3 but only 1 entry throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x0A, 0x00]), // MAP, value_flag=0x00
Buffer.from([0x00, 0x00, 0x00, 0x03]), // length=3
Buffer.from([0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x6B, 0x65, 0x79]), // key: "key"
Buffer.from([0x01, 0x00, 0x00, 0x00, 0x00, 0x01]) // value: 1
]);
assert.throws(() => mapSerializer.deserialize(buffer), /{item_1}.*buffer is empty/);
});
it('UUID with only 8 of 16 bytes throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x0C, 0x00]), // UUID, value_flag=0x00
Buffer.from([0x41, 0xD2, 0xE2, 0x8A, 0x20, 0x13, 0x4E, 0x35]) // only 8 bytes
]);
assert.throws(() => uuidSerializer.deserialize(buffer), /unexpected.*value.*length/);
});
it('DATETIME with only 10 of 18 bytes throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x04, 0x00]), // DATETIME, value_flag=0x00
Buffer.from([0x07, 0xB2, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) // only 10 bytes
]);
assert.throws(() => dateTimeSerializer.deserialize(buffer), /unexpected.*value.*length/);
});
it('BOOLEAN with no value byte throws error', () => {
assert.throws(() => ioc.booleanSerializer.deserialize(Buffer.from([0x27, 0x00])), /unexpected.*value.*length/);
});
it('BYTE with no value byte throws error', () => {
assert.throws(() => ioc.byteSerializer.deserialize(Buffer.from([0x24, 0x00])), /unexpected.*value.*length/);
});
it('BIGINTEGER with truncated length throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x23, 0x00]), // BIGINTEGER, value_flag=0x00
Buffer.from([0x00, 0x00]) // only 2 bytes of length instead of 4
]);
assert.throws(() => bigIntegerSerializer.deserialize(buffer), /{length}.*unexpected.*value.*length/);
});
it('BIGINTEGER with length=0 throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x23, 0x00]), // BIGINTEGER, value_flag=0x00
Buffer.from([0x00, 0x00, 0x00, 0x00]) // length=0
]);
assert.throws(() => bigIntegerSerializer.deserialize(buffer), /{length}=0 is less than one/);
});
it('BINARY with truncated value data throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x25, 0x00]), // BINARY, value_flag=0x00
Buffer.from([0x00, 0x00, 0x00, 0x05]), // length=5
Buffer.from([0x01, 0x02]) // only 2 bytes instead of 5
]);
assert.throws(() => binarySerializer.deserialize(buffer), /{value}.*unexpected.*value.*length/);
});
it('SET with length 5 but only 1 item throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x0B, 0x00]), // SET, value_flag=0x00
Buffer.from([0x00, 0x00, 0x00, 0x05]), // length=5
Buffer.from([0x01, 0x00, 0x00, 0x00, 0x00, 0x01]) // only 1 INT item
]);
assert.throws(() => setSerializer.deserialize(buffer), /{item_1}.*buffer is empty/);
});
it('ENUM with truncated elementName throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x18, 0x00]), // DIRECTION, value_flag=0x00
Buffer.from([0x03, 0x00, 0x00, 0x00, 0x00, 0x05]) // STRING with length=5 but no data
]);
assert.throws(() => enumSerializer.deserialize(buffer), /elementName.*unexpected.*text_value.*length/);
});
});
describe('Negative lengths', () => {
it('STRING with negative length throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x03, 0x00]), // STRING, value_flag=0x00
Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]) // length=-1
]);
assert.throws(() => stringSerializer.deserialize(buffer), /length.*less than zero/);
});
it('LIST with negative length throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x09, 0x00]), // LIST, value_flag=0x00
Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]) // length=-1
]);
assert.throws(() => listSerializer.deserialize(buffer), /length.*less than zero/);
});
it('MAP with negative length throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x0A, 0x00]), // MAP, value_flag=0x00
Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]) // length=-1
]);
assert.throws(() => mapSerializer.deserialize(buffer), /length.*less than zero/);
});
it('SET with negative length throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x0B, 0x00]), // SET, value_flag=0x00
Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]) // length=-1
]);
assert.throws(() => ioc.setSerializer.deserialize(buffer), /length.*less than zero/);
});
it('BINARY with negative length throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x25, 0x00]), // BINARY, value_flag=0x00
Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]) // length=-1
]);
assert.throws(() => ioc.binarySerializer.deserialize(buffer), /length.*less than zero/);
});
});
describe('Bulk flag errors', () => {
it('LIST with bulk flag but missing bulk count data throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x09, 0x02]), // LIST, value_flag=0x02 (bulk)
Buffer.from([0x00, 0x00, 0x00, 0x01]), // length=1
Buffer.from([0x01, 0x00, 0x00, 0x00, 0x00, 0x01]) // 1 INT item but no bulk count
]);
assert.throws(() => listSerializer.deserialize(buffer), /{item_0}.*bulk count is missing/);
});
it('SET with bulk flag but missing bulk count data throws error', () => {
const buffer = Buffer.concat([
Buffer.from([0x0B, 0x02]), // SET, value_flag=0x02 (bulk)
Buffer.from([0x00, 0x00, 0x00, 0x01]), // length=1
Buffer.from([0x01, 0x00, 0x00, 0x00, 0x00, 0x01]) // 1 INT item but no bulk count
]);
assert.throws(() => setSerializer.deserialize(buffer), /{item_0}.*bulk count is missing/);
});
});
describe('Serialization rejection for unsupported types', () => {
it('Symbol throws error', () => {
assert.throws(() => anySerializer.serialize(Symbol('test')), /No serializer found to support item/);
});
it('function throws error', () => {
assert.throws(() => anySerializer.serialize(() => {}), /No serializer found to support item/);
});
it('arrow function throws error', () => {
assert.throws(() => anySerializer.serialize((x) => x + 1), /No serializer found to support item/);
});
it('async function throws error', () => {
assert.throws(() => anySerializer.serialize(async () => {}), /No serializer found to support item/);
});
it('generator function throws error', () => {
assert.throws(() => anySerializer.serialize(function* () {}), /No serializer found to support item/);
});
it('class constructor throws error', () => {
class TestClass {}
assert.throws(() => anySerializer.serialize(TestClass), /No serializer found to support item/);
});
});
describe('v1-removed types serialization behavior', () => {
it('P.eq("cole") serializes as Map (v1 types fall through to MapSerializer)', () => {
// v1 P types no longer have dedicated serializers, so they serialize as Maps
const result = anySerializer.serialize(P.eq('cole'));
assert.ok(result.length > 0, 'Should serialize successfully as Map');
});
it('TextP.containing("x") serializes as Map (v1 types fall through to MapSerializer)', () => {
// v1 TextP types no longer have dedicated serializers, so they serialize as Maps
const result = anySerializer.serialize(TextP.containing('x'));
assert.ok(result.length > 0, 'Should serialize successfully as Map');
});
it('new Traverser("cole", 1) serializes as Map (v1 types fall through to MapSerializer)', () => {
// v1 Traverser types no longer have dedicated serializers, so they serialize as Maps
const result = anySerializer.serialize(new Traverser('cole', 1));
assert.ok(result.length > 0, 'Should serialize successfully as Map');
});
it('new OptionsStrategy({}) serializes as Map (v1 types fall through to MapSerializer)', () => {
// v1 OptionsStrategy types no longer have dedicated serializers, so they serialize as Maps
const result = anySerializer.serialize(new OptionsStrategy({}));
assert.ok(result.length > 0, 'Should serialize successfully as Map');
});
});
});