bebop
Version:
The TypeScript runtime for Bebop, a schema-based binary serialization format.
384 lines (349 loc) • 14 kB
text/typescript
import exp from "constants";
import {
BebopTypeGuard,
BebopRuntimeError,
BebopJson,
GuidMap,
Guid,
} from "./index";
import { describe, expect, it } from "vitest";
describe("BebopJson", () => {
describe("replace/revive", () => {
it("should support BigInt values", () => {
const obj = { bigInt: BigInt(Number.MAX_SAFE_INTEGER) + 1n };
const jsonString = JSON.stringify(obj, BebopJson.replacer);
const parsedObj = JSON.parse(jsonString, BebopJson.reviver);
expect(parsedObj.bigInt).toEqual(obj.bigInt);
});
it("should support Map values", () => {
const obj = { map: new Map([["key", "value"]]) };
const jsonString = JSON.stringify(obj, BebopJson.replacer);
const parsedObj = JSON.parse(jsonString, BebopJson.reviver);
expect(parsedObj.map.get("key")).toEqual("value");
});
it("should support GuidMap", () => {
const guid = Guid.newGuid();
const obj = { map: new GuidMap([[guid, "value"]]) };
const jsonString = JSON.stringify(obj, BebopJson.replacer);
const parsedObj = JSON.parse(jsonString, BebopJson.reviver) as typeof obj;
expect(parsedObj.map.get(guid)).toEqual("value");
});
it("should support Uint8Array values", () => {
const obj = { uint8: new Uint8Array([0, 1, 2, 3]) };
const jsonString = JSON.stringify(obj, BebopJson.replacer);
const parsedObj = JSON.parse(jsonString, BebopJson.reviver);
expect(parsedObj.uint8).toEqual(obj.uint8);
});
it("should support a complex object with multiple types", () => {
const guid = Guid.newGuid();
const obj = {
bigInt: BigInt(Number.MAX_SAFE_INTEGER) + 1n,
nestedObj: {
bigInt: BigInt(Number.MAX_SAFE_INTEGER) + 1n,
},
map: new Map([["key", "value"]]),
nestedMap: new Map([["key", new GuidMap([[guid, "value"]])]]),
date: new Date("2022-01-01T00:00:00.000Z"),
uint8: new Uint8Array([0, 1, 2, 3]),
};
const jsonString = JSON.stringify(obj, BebopJson.replacer, 4);
const parsedObj = JSON.parse(jsonString, BebopJson.reviver) as typeof obj;
expect(parsedObj.bigInt).toEqual(obj.bigInt);
expect(parsedObj.map.get("key")).toEqual("value");
expect(parsedObj.nestedMap.get("key")).toBeInstanceOf(GuidMap);
expect(parsedObj.nestedMap.get("key")?.get(guid)).toEqual("value");
expect(parsedObj.date.getTime()).toEqual(obj.date.getTime());
expect(parsedObj.uint8).toEqual(obj.uint8);
});
it("should support arrays with multiple types", () => {
const data = ["value", 200, { a: "" }, [BigInt(100)]];
const obj = { map: new Map([["key", data]]) };
const jsonString = JSON.stringify(obj, BebopJson.replacer);
const parsedObj = JSON.parse(jsonString, BebopJson.reviver);
expect(parsedObj.map.get("key")).toStrictEqual(data);
});
// Edge case: Empty Map
it("should throw for empty Map values", () => {
const obj = { map: new Map() };
expect(() => JSON.stringify(obj, BebopJson.replacer)).toThrow(BebopRuntimeError);
});
// Edge case: Empty Uint8Array
it("should handle empty Uint8Array values", () => {
const obj = { uint8: new Uint8Array() };
const jsonString = JSON.stringify(obj, BebopJson.replacer);
const parsedObj = JSON.parse(jsonString, BebopJson.reviver);
expect(parsedObj.uint8).toEqual(obj.uint8);
});
});
describe("security checks", () => {
it("should not be vulnerable to prototype pollution", () => {
const json = '{"user":{"__proto__":{"admin": true}}}';
expect(() => JSON.parse(json, BebopJson.reviver)).toThrow(
BebopRuntimeError
);
});
});
});
describe("BebopTypeGuard", () => {
describe("ensureBoolean", () => {
it("should not throw an error for a valid boolean", () => {
expect(() => BebopTypeGuard.ensureBoolean(true)).not.toThrow();
expect(() => BebopTypeGuard.ensureBoolean(false)).not.toThrow();
});
it("should throw an error for an invalid boolean", () => {
expect(() => BebopTypeGuard.ensureBoolean(null)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureBoolean(undefined)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureBoolean("true")).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureBoolean(1)).toThrow(BebopRuntimeError);
});
});
describe("ensureUint8", () => {
it("should not throw an error for a valid Uint8 number", () => {
expect(() => BebopTypeGuard.ensureUint8(0)).not.toThrow();
expect(() => BebopTypeGuard.ensureUint8(255)).not.toThrow();
});
it("should throw an error for an invalid Uint8 number", () => {
expect(() => BebopTypeGuard.ensureUint8(null)).toThrow(BebopRuntimeError);
expect(() => BebopTypeGuard.ensureUint8(undefined)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureUint8(-1)).toThrow(BebopRuntimeError);
expect(() => BebopTypeGuard.ensureUint8(256)).toThrow(BebopRuntimeError);
expect(() => BebopTypeGuard.ensureUint8(1.5)).toThrow(BebopRuntimeError);
});
});
describe("ensureUint16", () => {
it("should not throw an error for a valid Uint16 number", () => {
expect(() => BebopTypeGuard.ensureUint16(0)).not.toThrow();
expect(() => BebopTypeGuard.ensureUint16(65535)).not.toThrow();
});
it("should throw an error for an invalid Uint16 number", () => {
expect(() => BebopTypeGuard.ensureUint16(null)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureUint16(undefined)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureUint16(-1)).toThrow(BebopRuntimeError);
expect(() => BebopTypeGuard.ensureUint16(65536)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureUint16(1.5)).toThrow(BebopRuntimeError);
});
});
describe("ensureInt16", () => {
it("should not throw an error for a valid Int16 number", () => {
expect(() => BebopTypeGuard.ensureInt16(-32768)).not.toThrow();
expect(() => BebopTypeGuard.ensureInt16(32767)).not.toThrow();
});
it("should throw an error for an invalid Int16 number", () => {
expect(() => BebopTypeGuard.ensureInt16(null)).toThrow(BebopRuntimeError);
expect(() => BebopTypeGuard.ensureInt16(undefined)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureInt16(-32769)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureInt16(32768)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureInt16(1.5)).toThrow(BebopRuntimeError);
});
});
describe("ensureUint32", () => {
it("should not throw an error for a valid Uint32 number", () => {
expect(() => BebopTypeGuard.ensureUint32(0)).not.toThrow();
expect(() => BebopTypeGuard.ensureUint32(4294967295)).not.toThrow();
});
it("should throw an error for an invalid Uint32 number", () => {
expect(() => BebopTypeGuard.ensureUint32(null)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureUint32(undefined)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureUint32(-1)).toThrow(BebopRuntimeError);
expect(() => BebopTypeGuard.ensureUint32(4294967296)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureUint32(1.5)).toThrow(BebopRuntimeError);
});
});
describe("ensureInt32", () => {
it("should not throw an error for a valid Int32 number", () => {
expect(() => BebopTypeGuard.ensureInt32(-2147483648)).not.toThrow();
expect(() => BebopTypeGuard.ensureInt32(2147483647)).not.toThrow();
});
it("should throw an error for an invalid Int32 number", () => {
expect(() => BebopTypeGuard.ensureInt32(null)).toThrow(BebopRuntimeError);
expect(() => BebopTypeGuard.ensureInt32(undefined)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureInt32(-2147483649)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureInt32(2147483648)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureInt32(1.5)).toThrow(BebopRuntimeError);
});
});
describe("ensureFloat", () => {
it("should not throw an error for a valid Float number", () => {
expect(() => BebopTypeGuard.ensureFloat(-3.4028235e38)).not.toThrow();
expect(() => BebopTypeGuard.ensureFloat(3.4028235e38)).not.toThrow();
});
it("should throw an error for an invalid Float32 number", () => {
expect(() => BebopTypeGuard.ensureFloat(null)).toThrow(BebopRuntimeError);
expect(() => BebopTypeGuard.ensureFloat(undefined)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureFloat("1.5")).toThrow(
BebopRuntimeError
);
});
});
describe("ensureString", () => {
it("should not throw an error for a valid string", () => {
expect(() => BebopTypeGuard.ensureString("hello")).not.toThrow();
expect(() => BebopTypeGuard.ensureString("")).not.toThrow();
});
it("should throw an error for an invalid string", () => {
expect(() => BebopTypeGuard.ensureString(null)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureString(undefined)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureString(1)).toThrow(BebopRuntimeError);
expect(() => BebopTypeGuard.ensureString({})).toThrow(BebopRuntimeError);
});
});
describe("ensureArray", () => {
const validator = (value: any) => {
if (typeof value !== "number") {
throw new BebopRuntimeError("Invalid type");
}
};
it("should not throw an error for a valid array", () => {
expect(() => BebopTypeGuard.ensureArray([], validator)).not.toThrow();
expect(() =>
BebopTypeGuard.ensureArray([1, 2, 3], validator)
).not.toThrow();
});
it("should throw an error for an invalid array", () => {
expect(() => BebopTypeGuard.ensureArray(null, validator)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureArray(undefined, validator)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureArray(1, validator)).toThrow(
BebopRuntimeError
);
expect(() => BebopTypeGuard.ensureArray({}, validator)).toThrow(
BebopRuntimeError
);
});
});
describe("ensureMap", () => {
it("should not throw an error for a valid map", () => {
const keyTypeValidator = (value: any) => {
if (typeof value !== "number") {
throw new BebopRuntimeError("Invalid type");
}
};
const valueTypeValidator = (value: any) => {
if (typeof value !== "string") {
throw new BebopRuntimeError("Invalid type");
}
};
const map = new Map<number, string>();
map.set(1, "one");
map.set(2, "two");
expect(() =>
BebopTypeGuard.ensureMap(map, keyTypeValidator, valueTypeValidator)
).not.toThrow();
});
it("should throw an error for an invalid map", () => {
const keyTypeValidator = (value: any) => {
if (typeof value !== "number") {
throw new BebopRuntimeError("Invalid type");
}
};
const valueTypeValidator = (value: any) => {
if (typeof value !== "string") {
throw new BebopRuntimeError("Invalid type");
}
};
const map = new Map<number, any>();
map.set(1, "one");
map.set(2, 3);
expect(() =>
BebopTypeGuard.ensureMap(map, keyTypeValidator, valueTypeValidator)
).toThrow(BebopRuntimeError);
expect(() =>
BebopTypeGuard.ensureMap(null, keyTypeValidator, valueTypeValidator)
).toThrow(BebopRuntimeError);
expect(() =>
BebopTypeGuard.ensureMap(
undefined,
keyTypeValidator,
valueTypeValidator
)
).toThrow(BebopRuntimeError);
expect(() =>
BebopTypeGuard.ensureMap(1, keyTypeValidator, valueTypeValidator)
).toThrow(BebopRuntimeError);
expect(() =>
BebopTypeGuard.ensureMap({}, keyTypeValidator, valueTypeValidator)
).toThrow(BebopRuntimeError);
});
it("should throw an error for array with mixed types", () => {
const validator = (value: any) => {
if (typeof value !== "number") {
throw new BebopRuntimeError("Invalid type");
}
};
expect(() =>
BebopTypeGuard.ensureArray([1, "string"], validator)
).toThrow(BebopRuntimeError);
});
it("should throw an error for a map with inconsistent value types", () => {
const keyTypeValidator = (value: any) => {
if (typeof value !== "number") {
throw new BebopRuntimeError("Invalid type");
}
};
const valueTypeValidator = (value: any) => {
if (typeof value !== "string") {
throw new BebopRuntimeError("Invalid type");
}
};
const map = new Map<number, any>();
map.set(1, "one");
map.set(2, true); // not a string
expect(() =>
BebopTypeGuard.ensureMap(map, keyTypeValidator, valueTypeValidator)
).toThrow(BebopRuntimeError);
});
// Edge case: Extremely large number for Int32
it("should throw an error for an extremely large number for Int32", () => {
expect(() => BebopTypeGuard.ensureInt32(Number.MAX_VALUE)).toThrow(
BebopRuntimeError
);
});
// Edge case: Extremely small number for Int32
it("should throw an error for an extremely small number for Int32", () => {
expect(() => BebopTypeGuard.ensureInt32(Number.MIN_VALUE)).toThrow(
BebopRuntimeError
);
});
});
});