mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
110 lines
3.7 KiB
TypeScript
110 lines
3.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
JsonStringifyByteLimitError,
|
|
stringifyJsonWithinByteLimit
|
|
} from './node-bounded-json-stringify'
|
|
|
|
describe('stringifyJsonWithinByteLimit', () => {
|
|
it('matches native JSON for nested values, escaping, and omitted fields', () => {
|
|
const shared = { label: 'same object' }
|
|
const value = {
|
|
text: 'quote " slash \\ control\n emoji 🐋 lone \ud800',
|
|
nested: [1, undefined, Number.NaN, { omitted: undefined, kept: true }],
|
|
repeated: [shared, shared]
|
|
}
|
|
const native = JSON.stringify(value)
|
|
|
|
const result = stringifyJsonWithinByteLimit(value, Buffer.byteLength(native))
|
|
|
|
expect(result.serialized).toBe(native)
|
|
expect(result.byteLength).toBe(Buffer.byteLength(native, 'utf8'))
|
|
expect(() => stringifyJsonWithinByteLimit(value, result.byteLength - 1)).toThrow(
|
|
JsonStringifyByteLimitError
|
|
)
|
|
})
|
|
|
|
it('matches native indented JSON and counts whitespace before materializing it', () => {
|
|
const value = { nested: [{ quote: '"', unicode: '🐋' }], empty: {} }
|
|
const native = JSON.stringify(value, null, 2)
|
|
|
|
expect(stringifyJsonWithinByteLimit(value, Buffer.byteLength(native), 2)).toEqual({
|
|
byteLength: Buffer.byteLength(native),
|
|
serialized: native
|
|
})
|
|
expect(() => stringifyJsonWithinByteLimit(value, Buffer.byteLength(native) - 1, 2)).toThrow(
|
|
JsonStringifyByteLimitError
|
|
)
|
|
})
|
|
|
|
it('matches native numeric and string indentation normalization', () => {
|
|
const value = { nested: { value: true } }
|
|
for (const space of [Number.NaN, -1, 20, '🐋'.repeat(6)]) {
|
|
const native = JSON.stringify(value, null, space)
|
|
expect(stringifyJsonWithinByteLimit(value, Buffer.byteLength(native), space)).toEqual({
|
|
byteLength: Buffer.byteLength(native),
|
|
serialized: native
|
|
})
|
|
}
|
|
})
|
|
|
|
it('stops visiting indented collections when whitespace crosses the limit', () => {
|
|
let visits = 0
|
|
const value = Array.from({ length: 10_000 }, () => ({
|
|
toJSON() {
|
|
visits += 1
|
|
return 1
|
|
}
|
|
}))
|
|
|
|
expect(() => stringifyJsonWithinByteLimit(value, 64, 2)).toThrow(JsonStringifyByteLimitError)
|
|
expect(visits).toBeLessThan(value.length)
|
|
})
|
|
|
|
it('measures values after toJSON without invoking it twice', () => {
|
|
let calls = 0
|
|
const value = {
|
|
toJSON() {
|
|
calls += 1
|
|
return { rendered: 'value' }
|
|
}
|
|
}
|
|
|
|
expect(stringifyJsonWithinByteLimit(value, 100).serialized).toBe('{"rendered":"value"}')
|
|
expect(calls).toBe(1)
|
|
})
|
|
|
|
it('rejects a large root string before materializing escaped JSON', () => {
|
|
const value = '\n'.repeat(1024 * 1024)
|
|
|
|
expect(() => stringifyJsonWithinByteLimit(value, 1024)).toThrow(JsonStringifyByteLimitError)
|
|
})
|
|
|
|
it('stops visiting a large collection as soon as it crosses the limit', () => {
|
|
let visits = 0
|
|
const value = Array.from({ length: 10_000 }, () => ({
|
|
toJSON() {
|
|
visits += 1
|
|
return 1
|
|
}
|
|
}))
|
|
|
|
expect(() => stringifyJsonWithinByteLimit(value, 64)).toThrow(JsonStringifyByteLimitError)
|
|
expect(visits).toBeLessThan(value.length)
|
|
})
|
|
|
|
it('measures raw JSON values before native serialization materializes them', () => {
|
|
const createRawJson = (JSON as { rawJSON?: (value: string) => unknown }).rawJSON
|
|
if (!createRawJson) {
|
|
return
|
|
}
|
|
const value = createRawJson(JSON.stringify('x'.repeat(1024)))
|
|
|
|
expect(() => stringifyJsonWithinByteLimit(value, 32)).toThrow(JsonStringifyByteLimitError)
|
|
})
|
|
|
|
it('rejects invalid limits and unserializable roots like native JSON', () => {
|
|
expect(() => stringifyJsonWithinByteLimit('value', -1)).toThrow(RangeError)
|
|
expect(() => stringifyJsonWithinByteLimit(undefined, 100)).toThrow(TypeError)
|
|
})
|
|
})
|