@safeaccess/inline 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +1 -1
- package/CHANGELOG.md +10 -5
- package/LICENSE +1 -1
- package/README.md +56 -14
- package/dist/accessors/abstract-accessor.d.ts +22 -10
- package/dist/accessors/abstract-accessor.js +21 -8
- package/dist/accessors/abstract-integration-accessor.d.ts +22 -0
- package/dist/accessors/abstract-integration-accessor.js +23 -0
- package/dist/accessors/formats/any-accessor.d.ts +10 -8
- package/dist/accessors/formats/any-accessor.js +9 -8
- package/dist/accessors/formats/array-accessor.d.ts +2 -0
- package/dist/accessors/formats/array-accessor.js +2 -0
- package/dist/accessors/formats/env-accessor.d.ts +2 -0
- package/dist/accessors/formats/env-accessor.js +2 -0
- package/dist/accessors/formats/ini-accessor.d.ts +2 -0
- package/dist/accessors/formats/ini-accessor.js +2 -0
- package/dist/accessors/formats/json-accessor.d.ts +2 -0
- package/dist/accessors/formats/json-accessor.js +2 -0
- package/dist/accessors/formats/ndjson-accessor.d.ts +2 -0
- package/dist/accessors/formats/ndjson-accessor.js +2 -0
- package/dist/accessors/formats/object-accessor.d.ts +2 -0
- package/dist/accessors/formats/object-accessor.js +2 -0
- package/dist/accessors/formats/xml-accessor.d.ts +2 -0
- package/dist/accessors/formats/xml-accessor.js +2 -0
- package/dist/accessors/formats/yaml-accessor.d.ts +3 -1
- package/dist/accessors/formats/yaml-accessor.js +4 -2
- package/dist/cache/simple-path-cache.d.ts +51 -0
- package/dist/cache/simple-path-cache.js +72 -0
- package/dist/contracts/accessors-interface.d.ts +2 -0
- package/dist/contracts/factory-accessors-interface.d.ts +2 -0
- package/dist/contracts/filter-evaluator-interface.d.ts +28 -0
- package/dist/contracts/filter-evaluator-interface.js +1 -0
- package/dist/contracts/parse-integration-interface.d.ts +2 -0
- package/dist/contracts/parser-interface.d.ts +92 -0
- package/dist/contracts/parser-interface.js +1 -0
- package/dist/contracts/path-cache-interface.d.ts +7 -6
- package/dist/contracts/readable-accessors-interface.d.ts +11 -6
- package/dist/contracts/security-guard-interface.d.ts +2 -0
- package/dist/contracts/security-parser-interface.d.ts +2 -0
- package/dist/contracts/validatable-parser-interface.d.ts +59 -0
- package/dist/contracts/validatable-parser-interface.js +1 -0
- package/dist/contracts/writable-accessors-interface.d.ts +5 -0
- package/dist/core/accessor-factory.d.ts +124 -0
- package/dist/core/accessor-factory.js +157 -0
- package/dist/core/dot-notation-parser.d.ts +34 -5
- package/dist/core/dot-notation-parser.js +51 -10
- package/dist/core/inline-builder-accessor.d.ts +82 -0
- package/dist/core/inline-builder-accessor.js +107 -0
- package/dist/exceptions/accessor-exception.d.ts +9 -0
- package/dist/exceptions/accessor-exception.js +9 -0
- package/dist/exceptions/invalid-format-exception.d.ts +5 -0
- package/dist/exceptions/invalid-format-exception.js +5 -0
- package/dist/exceptions/parser-exception.d.ts +4 -0
- package/dist/exceptions/parser-exception.js +4 -0
- package/dist/exceptions/path-not-found-exception.d.ts +4 -0
- package/dist/exceptions/path-not-found-exception.js +4 -0
- package/dist/exceptions/readonly-violation-exception.d.ts +4 -0
- package/dist/exceptions/readonly-violation-exception.js +4 -0
- package/dist/exceptions/security-exception.d.ts +6 -0
- package/dist/exceptions/security-exception.js +6 -0
- package/dist/exceptions/unsupported-type-exception.d.ts +4 -0
- package/dist/exceptions/unsupported-type-exception.js +4 -0
- package/dist/exceptions/yaml-parse-exception.d.ts +4 -0
- package/dist/exceptions/yaml-parse-exception.js +4 -0
- package/dist/index.js +2 -1
- package/dist/inline.d.ts +22 -56
- package/dist/inline.js +39 -111
- package/dist/parser/xml-parser.js +23 -10
- package/dist/parser/yaml-parser.d.ts +54 -7
- package/dist/parser/yaml-parser.js +268 -51
- package/dist/path-query/segment-filter-parser.d.ts +142 -0
- package/dist/path-query/segment-filter-parser.js +384 -0
- package/dist/path-query/segment-parser.d.ts +98 -0
- package/dist/path-query/segment-parser.js +283 -0
- package/dist/path-query/segment-path-resolver.d.ts +149 -0
- package/dist/path-query/segment-path-resolver.js +351 -0
- package/dist/path-query/segment-type.d.ts +85 -0
- package/dist/path-query/segment-type.js +35 -0
- package/dist/security/forbidden-keys.d.ts +2 -2
- package/dist/security/forbidden-keys.js +5 -5
- package/dist/security/security-guard.d.ts +3 -1
- package/dist/security/security-guard.js +5 -2
- package/dist/security/security-parser.d.ts +10 -1
- package/dist/security/security-parser.js +10 -1
- package/dist/type-format.d.ts +2 -0
- package/dist/type-format.js +2 -0
- package/package.json +11 -3
- package/src/accessors/abstract-accessor.ts +23 -19
- package/src/accessors/abstract-integration-accessor.ts +27 -0
- package/src/accessors/formats/any-accessor.ts +11 -11
- package/src/accessors/formats/array-accessor.ts +2 -0
- package/src/accessors/formats/env-accessor.ts +2 -0
- package/src/accessors/formats/ini-accessor.ts +2 -0
- package/src/accessors/formats/json-accessor.ts +2 -0
- package/src/accessors/formats/ndjson-accessor.ts +2 -0
- package/src/accessors/formats/object-accessor.ts +2 -0
- package/src/accessors/formats/xml-accessor.ts +2 -0
- package/src/accessors/formats/yaml-accessor.ts +4 -2
- package/src/cache/simple-path-cache.ts +77 -0
- package/src/contracts/accessors-interface.ts +2 -0
- package/src/contracts/factory-accessors-interface.ts +2 -0
- package/src/contracts/filter-evaluator-interface.ts +30 -0
- package/src/contracts/parse-integration-interface.ts +2 -0
- package/src/contracts/parser-interface.ts +114 -0
- package/src/contracts/path-cache-interface.ts +8 -6
- package/src/contracts/readable-accessors-interface.ts +11 -6
- package/src/contracts/security-guard-interface.ts +2 -0
- package/src/contracts/security-parser-interface.ts +2 -0
- package/src/contracts/validatable-parser-interface.ts +64 -0
- package/src/contracts/writable-accessors-interface.ts +5 -0
- package/src/core/accessor-factory.ts +173 -0
- package/src/core/dot-notation-parser.ts +74 -11
- package/src/core/inline-builder-accessor.ts +163 -0
- package/src/exceptions/accessor-exception.ts +9 -0
- package/src/exceptions/invalid-format-exception.ts +5 -0
- package/src/exceptions/parser-exception.ts +4 -0
- package/src/exceptions/path-not-found-exception.ts +4 -0
- package/src/exceptions/readonly-violation-exception.ts +4 -0
- package/src/exceptions/security-exception.ts +6 -0
- package/src/exceptions/unsupported-type-exception.ts +4 -0
- package/src/exceptions/yaml-parse-exception.ts +4 -0
- package/src/index.ts +3 -1
- package/src/inline.ts +42 -120
- package/src/parser/xml-parser.ts +31 -10
- package/src/parser/yaml-parser.ts +310 -45
- package/src/path-query/segment-filter-parser.ts +444 -0
- package/src/path-query/segment-parser.ts +321 -0
- package/src/path-query/segment-path-resolver.ts +521 -0
- package/src/path-query/segment-type.ts +82 -0
- package/src/security/forbidden-keys.ts +5 -5
- package/src/security/security-guard.ts +7 -2
- package/src/security/security-parser.ts +18 -3
- package/src/type-format.ts +2 -0
- package/stryker.config.json +8 -10
- package/tests/accessors/abstract-accessor.test.ts +217 -0
- package/tests/accessors/abstract-integration-accessor.test.ts +37 -0
- package/tests/accessors/formats/any-accessor.test.ts +57 -0
- package/tests/accessors/formats/array-accessor.test.ts +42 -0
- package/tests/accessors/formats/env-accessor.test.ts +103 -0
- package/tests/accessors/formats/ini-accessor.test.ts +186 -0
- package/tests/accessors/{json-accessor.test.ts → formats/json-accessor.test.ts} +6 -6
- package/tests/accessors/formats/ndjson-accessor.test.ts +49 -0
- package/tests/accessors/formats/object-accessor.test.ts +172 -0
- package/tests/accessors/formats/xml-accessor.test.ts +162 -0
- package/tests/accessors/formats/yaml-accessor.test.ts +36 -0
- package/tests/cache/simple-path-cache.test.ts +168 -0
- package/tests/core/accessor-factory.test.ts +157 -0
- package/tests/core/dot-notation-parser-edge-cases.test.ts +415 -0
- package/tests/core/dot-notation-parser.test.ts +0 -288
- package/tests/core/inline-builder-accessor.test.ts +114 -0
- package/tests/exceptions/accessor-exception.test.ts +28 -0
- package/tests/exceptions/invalid-format-exception.test.ts +31 -0
- package/tests/exceptions/path-not-found-exception.test.ts +33 -0
- package/tests/exceptions/readonly-violation-exception.test.ts +35 -0
- package/tests/exceptions/security-exception.test.ts +33 -0
- package/tests/exceptions/unsupported-type-exception.test.ts +33 -0
- package/tests/exceptions/yaml-parse-exception.test.ts +38 -0
- package/tests/mocks/fake-path-cache.ts +4 -3
- package/tests/parity-from.test.ts +118 -0
- package/tests/parity.test.ts +227 -10
- package/tests/parser/xml-parser-mutations.test.ts +579 -0
- package/tests/parser/xml-parser-scanner.test.ts +332 -0
- package/tests/parser/xml-parser.test.ts +10 -334
- package/tests/parser/yaml-parser-mutations.test.ts +750 -0
- package/tests/parser/yaml-parser.test.ts +844 -18
- package/tests/path-query/segment-filter-parser-mutations.test.ts +735 -0
- package/tests/path-query/segment-filter-parser.test.ts +1091 -0
- package/tests/path-query/segment-parser-mutations.test.ts +539 -0
- package/tests/path-query/segment-parser.test.ts +606 -0
- package/tests/path-query/segment-path-resolver-mutations.test.ts +626 -0
- package/tests/path-query/segment-path-resolver.test.ts +1009 -0
- package/tests/security/security-guard-advanced.test.ts +413 -0
- package/tests/security/security-guard-forbidden-keys.test.ts +87 -0
- package/tests/security/security-guard.test.ts +3 -484
- package/tests/security/security-parser.test.ts +18 -14
- package/vitest.config.ts +3 -3
- package/benchmarks/get.bench.ts +0 -26
- package/benchmarks/parse.bench.ts +0 -41
- package/tests/accessors/accessors.test.ts +0 -1017
|
@@ -3,7 +3,6 @@ import { DotNotationParser } from '../../src/core/dot-notation-parser.js';
|
|
|
3
3
|
import { SecurityGuard } from '../../src/security/security-guard.js';
|
|
4
4
|
import { SecurityParser } from '../../src/security/security-parser.js';
|
|
5
5
|
import { SecurityException } from '../../src/exceptions/security-exception.js';
|
|
6
|
-
import { FakePathCache } from '../mocks/fake-path-cache.js';
|
|
7
6
|
|
|
8
7
|
function makeParser(): DotNotationParser {
|
|
9
8
|
return new DotNotationParser();
|
|
@@ -298,290 +297,3 @@ describe(`${DotNotationParser.name} > getMaxDepth`, () => {
|
|
|
298
297
|
expect(parser.getMaxDepth()).toBe(7);
|
|
299
298
|
});
|
|
300
299
|
});
|
|
301
|
-
|
|
302
|
-
describe(`${DotNotationParser.name} > pathCache integration`, () => {
|
|
303
|
-
it('stores parsed segments in the cache on first access', () => {
|
|
304
|
-
const cache = new FakePathCache();
|
|
305
|
-
const parser = new DotNotationParser(new SecurityGuard(), new SecurityParser(), cache);
|
|
306
|
-
parser.get({ a: { b: 1 } }, 'a.b');
|
|
307
|
-
expect(cache.store.has('a.b')).toBe(true);
|
|
308
|
-
expect(cache.store.get('a.b')).toEqual(['a', 'b']);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it('reads from the cache on subsequent calls without re-parsing', () => {
|
|
312
|
-
const cache = new FakePathCache();
|
|
313
|
-
const parser = new DotNotationParser(new SecurityGuard(), new SecurityParser(), cache);
|
|
314
|
-
parser.get({ a: { b: 1 } }, 'a.b');
|
|
315
|
-
const getCountAfterFirst = cache.getCallCount;
|
|
316
|
-
parser.get({ a: { b: 1 } }, 'a.b');
|
|
317
|
-
// Second call should hit the cache (getCallCount increases, setCallCount stays the same)
|
|
318
|
-
expect(cache.setCallCount).toBe(1);
|
|
319
|
-
expect(cache.getCallCount).toBeGreaterThan(getCountAfterFirst);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it('returns the correct value when cache is used', () => {
|
|
323
|
-
const cache = new FakePathCache();
|
|
324
|
-
const parser = new DotNotationParser(new SecurityGuard(), new SecurityParser(), cache);
|
|
325
|
-
expect(parser.get({ a: { b: 42 } }, 'a.b')).toBe(42);
|
|
326
|
-
expect(parser.get({ a: { b: 42 } }, 'a.b')).toBe(42);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('works without a cache (undefined)', () => {
|
|
330
|
-
const parser = new DotNotationParser(new SecurityGuard(), new SecurityParser());
|
|
331
|
-
expect(parser.get({ a: { b: 1 } }, 'a.b')).toBe(1);
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// Additional branch-coverage tests (targeting Stryker survivors)
|
|
336
|
-
|
|
337
|
-
describe(`${DotNotationParser.name} > get empty path branch`, () => {
|
|
338
|
-
// Kills lines 50:13/22/26 — `path === ''` condition in get()
|
|
339
|
-
it('returns defaultValue (not null) for empty path', () => {
|
|
340
|
-
const parser = makeParser();
|
|
341
|
-
expect(parser.get({ a: 1 }, '', 'custom_default')).toBe('custom_default');
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it('returns null when empty path and no default', () => {
|
|
345
|
-
const parser = makeParser();
|
|
346
|
-
expect(parser.get({ a: 1 }, '')).toBeNull();
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
describe(`${DotNotationParser.name} > has empty path branch`, () => {
|
|
351
|
-
// Kills lines 86:13/22/26 — `path === ''` condition in has()
|
|
352
|
-
it('returns false for empty path (not "has everything")', () => {
|
|
353
|
-
const parser = makeParser();
|
|
354
|
-
// If the condition were removed, sentinel lookup would always find the data itself → true
|
|
355
|
-
expect(parser.has({ a: 1 }, '')).toBe(false);
|
|
356
|
-
});
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
describe(`${DotNotationParser.name} > getAt branch conditions`, () => {
|
|
360
|
-
// Kills lines 128/129 — conditions inside getAt loop
|
|
361
|
-
it('returns defaultValue when current is null mid-path', () => {
|
|
362
|
-
const parser = makeParser();
|
|
363
|
-
expect(parser.getAt({ a: null }, ['a', 'b'], 'default')).toBe('default');
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it('returns defaultValue when key does not exist as own property', () => {
|
|
367
|
-
const parser = makeParser();
|
|
368
|
-
const data = Object.create({ inherited: true }) as Record<string, unknown>;
|
|
369
|
-
expect(parser.getAt(data, ['inherited'], 'fallback')).toBe('fallback');
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
it('returns value when key is a direct own property', () => {
|
|
373
|
-
const parser = makeParser();
|
|
374
|
-
expect(parser.getAt({ key: 'value' }, ['key'])).toBe('value');
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
describe(`${DotNotationParser.name} > removeAt empty segments`, () => {
|
|
379
|
-
// Kills line 189:36/13 — `segments.length === 0` early return
|
|
380
|
-
it('returns original data for empty segments', () => {
|
|
381
|
-
const parser = makeParser();
|
|
382
|
-
const data = { a: 1 };
|
|
383
|
-
expect(parser.removeAt(data, [])).toBe(data);
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
it('returns a different object when segments are non-empty', () => {
|
|
387
|
-
const parser = makeParser();
|
|
388
|
-
const data = { a: 1, b: 2 };
|
|
389
|
-
const result = parser.removeAt(data, ['a']);
|
|
390
|
-
expect(result).not.toBe(data);
|
|
391
|
-
expect(result).toEqual({ b: 2 });
|
|
392
|
-
});
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
describe(`${DotNotationParser.name} > merge existing is non-object`, () => {
|
|
396
|
-
// Kills line 214:45 — typeof existing === 'object' check in merge()
|
|
397
|
-
it('merges into empty object when existing path value is a primitive', () => {
|
|
398
|
-
const parser = makeParser();
|
|
399
|
-
// 'a' is a string (primitive), not an object — should merge into {}
|
|
400
|
-
const result = parser.merge({ a: 'string' }, 'a', { key: 'val' });
|
|
401
|
-
expect(result).toEqual({ a: { key: 'val' } });
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
it('merges into empty object when existing path value is null', () => {
|
|
405
|
-
const parser = makeParser();
|
|
406
|
-
const result = parser.merge({ a: null }, 'a', { key: 'val' });
|
|
407
|
-
expect(result).toEqual({ a: { key: 'val' } });
|
|
408
|
-
});
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
describe(`${DotNotationParser.name} > eraseAt hasOwnProperty check`, () => {
|
|
412
|
-
// Kills line 281:22 — hasOwnProperty check in eraseAt
|
|
413
|
-
it('does not remove inherited (non-own) properties via prototype chain', () => {
|
|
414
|
-
const parser = makeParser();
|
|
415
|
-
const proto = { inherited: 1 };
|
|
416
|
-
const data = Object.create(proto) as Record<string, unknown>;
|
|
417
|
-
data['own'] = 2;
|
|
418
|
-
const result = parser.removeAt(data, ['inherited']);
|
|
419
|
-
// 'inherited' is not an own property so eraseAt returns data unchanged
|
|
420
|
-
expect(Object.prototype.hasOwnProperty.call(result, 'inherited')).toBe(false);
|
|
421
|
-
expect(result['own']).toBe(2);
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
describe(`${DotNotationParser.name} > eraseAt child null/non-object`, () => {
|
|
426
|
-
// Kills lines 290:13/30/42/60 — `typeof child !== 'object' || child === null`
|
|
427
|
-
it('returns copy unchanged when intermediate child is null', () => {
|
|
428
|
-
const parser = makeParser();
|
|
429
|
-
const data = { a: null };
|
|
430
|
-
const result = parser.removeAt(data as Record<string, unknown>, ['a', 'b']);
|
|
431
|
-
expect(result).toEqual({ a: null });
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
it('returns copy unchanged when intermediate child is a number', () => {
|
|
435
|
-
const parser = makeParser();
|
|
436
|
-
const result = parser.removeAt({ a: 42 }, ['a', 'b']);
|
|
437
|
-
expect(result).toEqual({ a: 42 });
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
it('returns copy unchanged when intermediate child is a string', () => {
|
|
441
|
-
const parser = makeParser();
|
|
442
|
-
const result = parser.removeAt({ a: 'text' }, ['a', 'b']);
|
|
443
|
-
expect(result).toEqual({ a: 'text' });
|
|
444
|
-
});
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
describe(`${DotNotationParser.name} > writeAt single segment`, () => {
|
|
448
|
-
// Kills lines 305:63/13 — `index === segments.length - 1` check (terminal condition)
|
|
449
|
-
it('sets value at a single-segment path correctly', () => {
|
|
450
|
-
const parser = makeParser();
|
|
451
|
-
const result = parser.setAt({}, ['key'], 'value');
|
|
452
|
-
expect(result).toEqual({ key: 'value' });
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
it('sets value at a single-segment path, overwriting existing', () => {
|
|
456
|
-
const parser = makeParser();
|
|
457
|
-
const result = parser.setAt({ key: 'old' }, ['key'], 'new');
|
|
458
|
-
expect(result).toEqual({ key: 'new' });
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
describe(`${DotNotationParser.name} > writeAt child handling`, () => {
|
|
463
|
-
// Kills lines 317:13/42/58 — typeof child === 'object' check in writeAt
|
|
464
|
-
it('creates nested object when child is null', () => {
|
|
465
|
-
const parser = makeParser();
|
|
466
|
-
const result = parser.setAt({ a: null }, ['a', 'b'], 1);
|
|
467
|
-
expect(result).toEqual({ a: { b: 1 } });
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
it('creates nested object when child is a primitive', () => {
|
|
471
|
-
const parser = makeParser();
|
|
472
|
-
const result = parser.setAt({ a: 42 }, ['a', 'b'], 1);
|
|
473
|
-
expect(result).toEqual({ a: { b: 1 } });
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
it('creates nested object when child is an array', () => {
|
|
477
|
-
const parser = makeParser();
|
|
478
|
-
const result = parser.setAt({ a: [1, 2] }, ['a', 'b'], 1);
|
|
479
|
-
expect(result).toEqual({ a: { b: 1 } });
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
it('preserves existing nested object when overwriting a key', () => {
|
|
483
|
-
const parser = makeParser();
|
|
484
|
-
const result = parser.setAt({ a: { x: 1, y: 2 } }, ['a', 'z'], 3);
|
|
485
|
-
expect(result).toEqual({ a: { x: 1, y: 2, z: 3 } });
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
describe(`${DotNotationParser.name} > write-path forbidden key validation`, () => {
|
|
490
|
-
it('throws SecurityException when setting a forbidden key via set', () => {
|
|
491
|
-
const parser = makeParser();
|
|
492
|
-
expect(() => parser.set({}, 'constructor', 'bad')).toThrow(SecurityException);
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
it('throws SecurityException when setting a nested forbidden key via set', () => {
|
|
496
|
-
const parser = makeParser();
|
|
497
|
-
expect(() => parser.set({}, 'prototype.nested', 'bad')).toThrow(SecurityException);
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
it('throws SecurityException when removing a forbidden key via remove', () => {
|
|
501
|
-
const parser = makeParser();
|
|
502
|
-
expect(() => parser.remove({ safe: 1 }, 'constructor')).toThrow(SecurityException);
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
it('throws SecurityException when setting a forbidden key via setAt', () => {
|
|
506
|
-
const parser = makeParser();
|
|
507
|
-
expect(() => parser.setAt({}, ['__proto__'], 'bad')).toThrow(SecurityException);
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
it('throws SecurityException when removing a forbidden key via removeAt', () => {
|
|
511
|
-
const parser = makeParser();
|
|
512
|
-
expect(() => parser.removeAt({ safe: 1 }, ['constructor'])).toThrow(SecurityException);
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
it('throws SecurityException when merge source contains a forbidden key', () => {
|
|
516
|
-
const parser = makeParser();
|
|
517
|
-
expect(() => parser.merge({}, '', { hasOwnProperty: 'bad' })).toThrow(SecurityException);
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
it('throws SecurityException when merge source contains a nested forbidden key', () => {
|
|
521
|
-
const parser = makeParser();
|
|
522
|
-
expect(() => parser.merge({ user: { name: 'Alice' } }, '', { user: { prototype: 'bad' } as Record<string, unknown> })).toThrow(SecurityException);
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
it('allows safe keys through write-path operations', () => {
|
|
526
|
-
const parser = makeParser();
|
|
527
|
-
expect(parser.set({}, 'username', 'Alice')).toEqual({ username: 'Alice' });
|
|
528
|
-
expect(parser.remove({ username: 'Alice' }, 'username')).toEqual({});
|
|
529
|
-
expect(parser.merge({}, '', { name: 'Bob' })).toEqual({ name: 'Bob' });
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
it('write-path error message contains the forbidden key name', () => {
|
|
533
|
-
const parser = makeParser();
|
|
534
|
-
expect(() => parser.set({}, 'hasOwnProperty', 'bad')).toThrow("Forbidden key 'hasOwnProperty' detected.");
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
it('throws SecurityException for prototype pollution key via set', () => {
|
|
538
|
-
const parser = makeParser();
|
|
539
|
-
expect(() => parser.set({}, 'prototype', 'bad')).toThrow(SecurityException);
|
|
540
|
-
});
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
describe(`${DotNotationParser.name} > deepMerge branch conditions`, () => {
|
|
544
|
-
it('recursively merges when both target and source values are objects', () => {
|
|
545
|
-
const parser = makeParser();
|
|
546
|
-
const result = parser.merge({ a: { x: 1 } }, 'a', { y: 2 });
|
|
547
|
-
expect(result).toEqual({ a: { x: 1, y: 2 } });
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
it('overwrites when source value is null (not an object)', () => {
|
|
551
|
-
const parser = makeParser();
|
|
552
|
-
const result = parser.merge({ a: { x: 1 } }, 'a', { x: null as unknown as Record<string, unknown> });
|
|
553
|
-
expect((result['a'] as Record<string, unknown>)['x']).toBeNull();
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
it('overwrites when source value is an array (not a plain object)', () => {
|
|
557
|
-
const parser = makeParser();
|
|
558
|
-
const result = parser.merge({ a: { x: 1 } }, '', { a: [1, 2, 3] as unknown as Record<string, unknown> });
|
|
559
|
-
expect(result['a']).toEqual([1, 2, 3]);
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
it('overwrites when target value is null (not an object) and source is an object', () => {
|
|
563
|
-
const parser = makeParser();
|
|
564
|
-
const result = parser.merge({ a: null }, '', { a: { key: 'val' } as Record<string, unknown> });
|
|
565
|
-
expect(result['a']).toEqual({ key: 'val' });
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
it('overwrites when target value is an array and source is an object', () => {
|
|
569
|
-
const parser = makeParser();
|
|
570
|
-
const result = parser.merge({ a: [1, 2] }, '', { a: { key: 'val' } as unknown as Record<string, unknown> });
|
|
571
|
-
expect(result['a']).toEqual({ key: 'val' });
|
|
572
|
-
});
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
describe(`${DotNotationParser.name} > getMaxKeys`, () => {
|
|
576
|
-
it('returns the max key count from the configured SecurityParser', () => {
|
|
577
|
-
const parser = new DotNotationParser(
|
|
578
|
-
new SecurityGuard(),
|
|
579
|
-
new SecurityParser({ maxKeys: 42 }),
|
|
580
|
-
);
|
|
581
|
-
expect(parser.getMaxKeys()).toBe(42);
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
it('returns the default max key count when not overridden', () => {
|
|
585
|
-
expect(makeParser().getMaxKeys()).toBe(10_000);
|
|
586
|
-
});
|
|
587
|
-
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { InlineBuilderAccessor } from '../../src/core/inline-builder-accessor.js';
|
|
3
|
+
import { AccessorFactory } from '../../src/core/accessor-factory.js';
|
|
4
|
+
import { SecurityGuard } from '../../src/security/security-guard.js';
|
|
5
|
+
import { SecurityParser } from '../../src/security/security-parser.js';
|
|
6
|
+
import { FakeParseIntegration } from '../mocks/fake-parse-integration.js';
|
|
7
|
+
import { FakePathCache } from '../mocks/fake-path-cache.js';
|
|
8
|
+
|
|
9
|
+
describe(InlineBuilderAccessor.name, () => {
|
|
10
|
+
describe('builder', () => {
|
|
11
|
+
it('returns an AccessorFactory', () => {
|
|
12
|
+
const builder = new InlineBuilderAccessor();
|
|
13
|
+
expect(builder.builder()).toBeInstanceOf(AccessorFactory);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('creates a working parser that can resolve paths', () => {
|
|
17
|
+
const factory = new InlineBuilderAccessor().builder();
|
|
18
|
+
const accessor = factory.json('{"name":"Alice"}');
|
|
19
|
+
expect(accessor.get('name')).toBe('Alice');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('withSecurityGuard', () => {
|
|
24
|
+
it('returns a new instance with the provided guard', () => {
|
|
25
|
+
const original = new InlineBuilderAccessor();
|
|
26
|
+
const guard = new SecurityGuard();
|
|
27
|
+
const result = original.withSecurityGuard(guard);
|
|
28
|
+
expect(result).not.toBe(original);
|
|
29
|
+
expect(result).toBeInstanceOf(InlineBuilderAccessor);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('uses the custom guard in created accessors', () => {
|
|
33
|
+
const guard = new SecurityGuard(5, ['customForbidden']);
|
|
34
|
+
const builder = new InlineBuilderAccessor(guard);
|
|
35
|
+
const factory = builder.builder();
|
|
36
|
+
expect(() => factory.json('{"customForbidden":"x"}')).toThrow();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('withSecurityParser', () => {
|
|
41
|
+
it('returns a new instance with the provided security parser', () => {
|
|
42
|
+
const original = new InlineBuilderAccessor();
|
|
43
|
+
const parser = new SecurityParser();
|
|
44
|
+
const result = original.withSecurityParser(parser);
|
|
45
|
+
expect(result).not.toBe(original);
|
|
46
|
+
expect(result).toBeInstanceOf(InlineBuilderAccessor);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('uses the custom security parser in created accessors', () => {
|
|
50
|
+
const tinyParser = new SecurityParser({ maxPayloadBytes: 5 });
|
|
51
|
+
const builder = new InlineBuilderAccessor(undefined, tinyParser);
|
|
52
|
+
const factory = builder.builder();
|
|
53
|
+
expect(() => factory.json('{"name":"Alice"}')).toThrow();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('withParserIntegration', () => {
|
|
58
|
+
it('returns a new instance with the provided integration', () => {
|
|
59
|
+
const original = new InlineBuilderAccessor();
|
|
60
|
+
const integration = new FakeParseIntegration();
|
|
61
|
+
const result = original.withParserIntegration(integration);
|
|
62
|
+
expect(result).not.toBe(original);
|
|
63
|
+
expect(result).toBeInstanceOf(InlineBuilderAccessor);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('integration is available in AnyAccessor via builder', () => {
|
|
67
|
+
const integration = new FakeParseIntegration(true, { key: 'val' });
|
|
68
|
+
const factory = new InlineBuilderAccessor()
|
|
69
|
+
.withParserIntegration(integration)
|
|
70
|
+
.builder();
|
|
71
|
+
const accessor = factory.any('raw');
|
|
72
|
+
expect(accessor.get('key')).toBe('val');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('withPathCache', () => {
|
|
77
|
+
it('returns a new instance with the provided cache', () => {
|
|
78
|
+
const original = new InlineBuilderAccessor();
|
|
79
|
+
const cache = new FakePathCache();
|
|
80
|
+
const result = original.withPathCache(cache);
|
|
81
|
+
expect(result).not.toBe(original);
|
|
82
|
+
expect(result).toBeInstanceOf(InlineBuilderAccessor);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('uses the custom cache in the parser', () => {
|
|
86
|
+
const cache = new FakePathCache();
|
|
87
|
+
const factory = new InlineBuilderAccessor(undefined, undefined, cache).builder();
|
|
88
|
+
const accessor = factory.json('{"a":1}');
|
|
89
|
+
accessor.get('a');
|
|
90
|
+
accessor.get('a');
|
|
91
|
+
expect(cache.getCallCount).toBeGreaterThanOrEqual(1);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('withStrictMode', () => {
|
|
96
|
+
it('returns a new instance with strict mode set', () => {
|
|
97
|
+
const original = new InlineBuilderAccessor();
|
|
98
|
+
const result = original.withStrictMode(false);
|
|
99
|
+
expect(result).not.toBe(original);
|
|
100
|
+
expect(result).toBeInstanceOf(InlineBuilderAccessor);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('strict(false) bypasses security validation', () => {
|
|
104
|
+
const factory = new InlineBuilderAccessor().withStrictMode(false).builder();
|
|
105
|
+
const accessor = factory.json('{"__proto__":"ok"}');
|
|
106
|
+
expect(accessor.get('__proto__')).toBe('ok');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('strict(true) enforces security validation', () => {
|
|
110
|
+
const factory = new InlineBuilderAccessor().withStrictMode(true).builder();
|
|
111
|
+
expect(() => factory.json('{"__proto__":"fail"}')).toThrow();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { AccessorException } from '../../src/exceptions/accessor-exception.js';
|
|
3
|
+
|
|
4
|
+
describe(AccessorException.name, () => {
|
|
5
|
+
it('stores the provided message', () => {
|
|
6
|
+
expect(new AccessorException('Something went wrong.').message).toBe(
|
|
7
|
+
'Something went wrong.',
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('sets name to AccessorException', () => {
|
|
12
|
+
expect(new AccessorException('msg').name).toBe('AccessorException');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('is an instance of Error', () => {
|
|
16
|
+
expect(new AccessorException('msg')).toBeInstanceOf(Error);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('supports cause chaining via ErrorOptions', () => {
|
|
20
|
+
const cause = new Error('root cause');
|
|
21
|
+
const err = new AccessorException('outer', { cause });
|
|
22
|
+
expect(err.cause).toBe(cause);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('can be constructed without options', () => {
|
|
26
|
+
expect(() => new AccessorException('msg')).not.toThrow();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { InvalidFormatException } from '../../src/exceptions/invalid-format-exception.js';
|
|
3
|
+
import { AccessorException } from '../../src/exceptions/accessor-exception.js';
|
|
4
|
+
|
|
5
|
+
describe(InvalidFormatException.name, () => {
|
|
6
|
+
it('stores the provided message', () => {
|
|
7
|
+
expect(new InvalidFormatException('Expected JSON.').message).toBe('Expected JSON.');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('sets name to InvalidFormatException', () => {
|
|
11
|
+
expect(new InvalidFormatException('msg').name).toBe('InvalidFormatException');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('is an instance of AccessorException', () => {
|
|
15
|
+
expect(new InvalidFormatException('msg')).toBeInstanceOf(AccessorException);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('is an instance of Error', () => {
|
|
19
|
+
expect(new InvalidFormatException('msg')).toBeInstanceOf(Error);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('supports cause chaining via ErrorOptions', () => {
|
|
23
|
+
const cause = new Error('root cause');
|
|
24
|
+
const err = new InvalidFormatException('outer', { cause });
|
|
25
|
+
expect(err.cause).toBe(cause);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('can be constructed without options', () => {
|
|
29
|
+
expect(() => new InvalidFormatException('msg')).not.toThrow();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { PathNotFoundException } from '../../src/exceptions/path-not-found-exception.js';
|
|
3
|
+
import { AccessorException } from '../../src/exceptions/accessor-exception.js';
|
|
4
|
+
|
|
5
|
+
describe(PathNotFoundException.name, () => {
|
|
6
|
+
it('stores the provided message', () => {
|
|
7
|
+
expect(new PathNotFoundException("Path 'user.address.zip' not found.").message).toBe(
|
|
8
|
+
"Path 'user.address.zip' not found.",
|
|
9
|
+
);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('sets name to PathNotFoundException', () => {
|
|
13
|
+
expect(new PathNotFoundException('msg').name).toBe('PathNotFoundException');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('is an instance of AccessorException', () => {
|
|
17
|
+
expect(new PathNotFoundException('msg')).toBeInstanceOf(AccessorException);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('is an instance of Error', () => {
|
|
21
|
+
expect(new PathNotFoundException('msg')).toBeInstanceOf(Error);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('supports cause chaining via ErrorOptions', () => {
|
|
25
|
+
const cause = new Error('root cause');
|
|
26
|
+
const err = new PathNotFoundException('outer', { cause });
|
|
27
|
+
expect(err.cause).toBe(cause);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('can be constructed without options', () => {
|
|
31
|
+
expect(() => new PathNotFoundException('msg')).not.toThrow();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ReadonlyViolationException } from '../../src/exceptions/readonly-violation-exception.js';
|
|
3
|
+
import { AccessorException } from '../../src/exceptions/accessor-exception.js';
|
|
4
|
+
|
|
5
|
+
describe(ReadonlyViolationException.name, () => {
|
|
6
|
+
it('uses default message when none is provided', () => {
|
|
7
|
+
expect(new ReadonlyViolationException().message).toBe('Cannot modify a readonly accessor.');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('accepts a custom message', () => {
|
|
11
|
+
expect(new ReadonlyViolationException('custom msg').message).toBe('custom msg');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('sets name to ReadonlyViolationException', () => {
|
|
15
|
+
expect(new ReadonlyViolationException().name).toBe('ReadonlyViolationException');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('is an instance of AccessorException', () => {
|
|
19
|
+
expect(new ReadonlyViolationException()).toBeInstanceOf(AccessorException);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('is an instance of Error', () => {
|
|
23
|
+
expect(new ReadonlyViolationException()).toBeInstanceOf(Error);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('supports cause chaining via ErrorOptions', () => {
|
|
27
|
+
const cause = new Error('root cause');
|
|
28
|
+
const err = new ReadonlyViolationException('msg', { cause });
|
|
29
|
+
expect(err.cause).toBe(cause);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('can be constructed without options', () => {
|
|
33
|
+
expect(() => new ReadonlyViolationException()).not.toThrow();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { SecurityException } from '../../src/exceptions/security-exception.js';
|
|
3
|
+
import { AccessorException } from '../../src/exceptions/accessor-exception.js';
|
|
4
|
+
|
|
5
|
+
describe(SecurityException.name, () => {
|
|
6
|
+
it('stores the provided message', () => {
|
|
7
|
+
expect(new SecurityException("Forbidden key '__proto__' detected.").message).toBe(
|
|
8
|
+
"Forbidden key '__proto__' detected.",
|
|
9
|
+
);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('sets name to SecurityException', () => {
|
|
13
|
+
expect(new SecurityException('msg').name).toBe('SecurityException');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('is an instance of AccessorException', () => {
|
|
17
|
+
expect(new SecurityException('msg')).toBeInstanceOf(AccessorException);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('is an instance of Error', () => {
|
|
21
|
+
expect(new SecurityException('msg')).toBeInstanceOf(Error);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('supports cause chaining via ErrorOptions', () => {
|
|
25
|
+
const cause = new Error('root cause');
|
|
26
|
+
const err = new SecurityException('outer', { cause });
|
|
27
|
+
expect(err.cause).toBe(cause);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('can be constructed without options', () => {
|
|
31
|
+
expect(() => new SecurityException('msg')).not.toThrow();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { UnsupportedTypeException } from '../../src/exceptions/unsupported-type-exception.js';
|
|
3
|
+
import { AccessorException } from '../../src/exceptions/accessor-exception.js';
|
|
4
|
+
|
|
5
|
+
describe(UnsupportedTypeException.name, () => {
|
|
6
|
+
it('stores the provided message', () => {
|
|
7
|
+
expect(new UnsupportedTypeException('TypeFormat.Csv is not supported.').message).toBe(
|
|
8
|
+
'TypeFormat.Csv is not supported.',
|
|
9
|
+
);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('sets name to UnsupportedTypeException', () => {
|
|
13
|
+
expect(new UnsupportedTypeException('msg').name).toBe('UnsupportedTypeException');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('is an instance of AccessorException', () => {
|
|
17
|
+
expect(new UnsupportedTypeException('msg')).toBeInstanceOf(AccessorException);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('is an instance of Error', () => {
|
|
21
|
+
expect(new UnsupportedTypeException('msg')).toBeInstanceOf(Error);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('supports cause chaining via ErrorOptions', () => {
|
|
25
|
+
const cause = new Error('root cause');
|
|
26
|
+
const err = new UnsupportedTypeException('outer', { cause });
|
|
27
|
+
expect(err.cause).toBe(cause);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('can be constructed without options', () => {
|
|
31
|
+
expect(() => new UnsupportedTypeException('msg')).not.toThrow();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { YamlParseException } from '../../src/exceptions/yaml-parse-exception.js';
|
|
3
|
+
import { InvalidFormatException } from '../../src/exceptions/invalid-format-exception.js';
|
|
4
|
+
import { AccessorException } from '../../src/exceptions/accessor-exception.js';
|
|
5
|
+
|
|
6
|
+
describe(YamlParseException.name, () => {
|
|
7
|
+
it('stores the provided message', () => {
|
|
8
|
+
expect(new YamlParseException('YAML anchors are not supported.').message).toBe(
|
|
9
|
+
'YAML anchors are not supported.',
|
|
10
|
+
);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('sets name to YamlParseException', () => {
|
|
14
|
+
expect(new YamlParseException('msg').name).toBe('YamlParseException');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('is an instance of InvalidFormatException', () => {
|
|
18
|
+
expect(new YamlParseException('msg')).toBeInstanceOf(InvalidFormatException);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('is an instance of AccessorException', () => {
|
|
22
|
+
expect(new YamlParseException('msg')).toBeInstanceOf(AccessorException);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('is an instance of Error', () => {
|
|
26
|
+
expect(new YamlParseException('msg')).toBeInstanceOf(Error);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('supports cause chaining via ErrorOptions', () => {
|
|
30
|
+
const cause = new Error('root cause');
|
|
31
|
+
const err = new YamlParseException('outer', { cause });
|
|
32
|
+
expect(err.cause).toBe(cause);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('can be constructed without options', () => {
|
|
36
|
+
expect(() => new YamlParseException('msg')).not.toThrow();
|
|
37
|
+
});
|
|
38
|
+
});
|