Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/utils/agent-config/__tests__/json-pointer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { describe, expect, it } from 'vitest';
import {
formatPointer,
getAt,
hasAt,
isPrefix,
parsePointer,
pointer,
} from '../json-pointer';

describe('json-pointer', () => {
it.each([
['', []],
[
'/plugins/local-ssh/config/port',
['plugins', 'local-ssh', 'config', 'port'],
],
['/plugins/p/labels/team~1owner', ['plugins', 'p', 'labels', 'team/owner']],
['/x/~0tilde~1slash', ['x', '~tilde/slash']],
['/a/', ['a', '']],
])('parses %s', (ptr, tokens) => {
expect(parsePointer(ptr)).toEqual(tokens);
expect(formatPointer(tokens)).toBe(ptr);
});

it('rejects pointers without a leading slash', () => {
expect(() => parsePointer('plugins')).toThrow();
});

it('pointer() escapes tokens', () => {
expect(pointer('plugins', 'p', 'policy_data', 'dir/x')).toBe(
'/plugins/p/policy_data/dir~1x',
);
});

const doc = { a: { b: null, c: [10, { d: 1 }] }, e: 0 };
it.each([
['/a/b', null, true],
['/a/x', undefined, false],
['/a/c/0', 10, true],
['/a/c/1/d', 1, true],
['/a/c/2', undefined, false],
['/a/b/z', undefined, false],
['/e', 0, true],
['', doc, true],
])('getAt/hasAt %s', (ptr, value, present) => {
expect(getAt(doc, ptr)).toEqual(value);
expect(hasAt(doc, ptr)).toBe(present);
});

it.each([
['/plugins/a', '/plugins/a/config', true],
['/plugins/a', '/plugins/a', true],
['/plugins/a', '/plugins/ab', false],
['', '/x', true],
['/plugins/a/config', '/plugins/a', false],
])('isPrefix(%s, %s)', (a, b, expected) => {
expect(isPrefix(a, b)).toBe(expected);
});
});
56 changes: 56 additions & 0 deletions src/utils/agent-config/__tests__/merge-patch.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, expect, it } from 'vitest';
import { clone, deepEqual, mergePatch } from '../merge-patch';

describe('mergePatch (RFC 7396 Appendix A)', () => {
const cases: [unknown, unknown, unknown][] = [
[{ a: 'b' }, { a: 'c' }, { a: 'c' }],
[{ a: 'b' }, { b: 'c' }, { a: 'b', b: 'c' }],
[{ a: 'b' }, { a: null }, {}],
[{ a: 'b', b: 'c' }, { a: null }, { b: 'c' }],
[{ a: ['b'] }, { a: 'c' }, { a: 'c' }],
[{ a: 'c' }, { a: ['b'] }, { a: ['b'] }],
[{ a: { b: 'c' } }, { a: { b: 'd', c: null } }, { a: { b: 'd' } }],
[{ a: [{ b: 'c' }] }, { a: [1] }, { a: [1] }],
[
['a', 'b'],
['c', 'd'],
['c', 'd'],
],
[{ a: 'b' }, ['c'], ['c']],
[{ a: 'foo' }, null, null],
[{ a: 'foo' }, 'bar', 'bar'],
[{ e: null }, { a: 1 }, { e: null, a: 1 }],
[[1, 2], { a: 'b', c: null }, { a: 'b' }],
[{}, { a: { bb: { ccc: null } } }, { a: { bb: {} } }],
];

it.each(cases)('%j + %j', (target, patch, expected) => {
expect(mergePatch(target, patch)).toEqual(expected);
});

it('never mutates its inputs', () => {
const target = { a: { b: 1 }, c: [1] };
const patch = { a: { b: null, d: 2 } };
const t = clone(target);
const p = clone(patch);
mergePatch(target, patch);
expect(target).toEqual(t);
expect(patch).toEqual(p);
});
});

describe('deepEqual / clone', () => {
it('ignores key order but not array order', () => {
expect(deepEqual({ a: 1, b: [1, 2] }, { b: [1, 2], a: 1 })).toBe(true);
expect(deepEqual({ b: [2, 1] }, { b: [1, 2] })).toBe(false);
expect(deepEqual({ a: undefined }, {})).toBe(false);
expect(deepEqual(null, {})).toBe(false);
});

it('clones deeply', () => {
const src = { a: { b: [{ c: 1 }] } };
const copy = clone(src);
copy.a.b[0].c = 2;
expect(src.a.b[0].c).toBe(1);
});
});
70 changes: 70 additions & 0 deletions src/utils/agent-config/__tests__/overlay-ops.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, expect, it } from 'vitest';
import { nullAt, setAt, unsetAt } from '../overlay-ops';

describe('overlay-ops', () => {
it('setAt creates intermediates and never mutates', () => {
const o = { plugins: { a: { schedule: 'x' } } };
const next = setAt(o, '/plugins/a/config/port', '22');
expect(next).toEqual({
plugins: { a: { schedule: 'x', config: { port: '22' } } },
});
expect(o).toEqual({ plugins: { a: { schedule: 'x' } } });
});

it('setAt replaces a null intermediate (un-remove a plugin)', () => {
const o = { plugins: { a: null } };
expect(setAt(o, '/plugins/a/enabled', true)).toEqual({
plugins: { a: { enabled: true } },
});
});

it('unsetAt removes the key and prunes empty parents up to depth 1', () => {
const o = { verbosity: 1, plugins: { a: { config: { port: '22' } } } };
expect(unsetAt(o, '/plugins/a/config/port')).toEqual({ verbosity: 1 });
});

it('unsetAt keeps non-empty parents and the root', () => {
expect(
unsetAt(
{ plugins: { a: { config: { p: '1', q: '2' } } } },
'/plugins/a/config/p',
),
).toEqual({ plugins: { a: { config: { q: '2' } } } });
expect(unsetAt({ verbosity: 1 }, '/verbosity')).toEqual({});
});

it('unsetAt of an absent key is a no-op copy', () => {
const o = { plugins: { a: { schedule: 'x' } } };
expect(unsetAt(o, '/plugins/b/schedule')).toEqual(o);
});

it('nullAt writes an explicit null', () => {
expect(nullAt({}, '/plugins/a')).toEqual({ plugins: { a: null } });
});
});

describe('"__proto__" keys are data, never prototypes', () => {
it('setAt does not pollute Object.prototype and keeps the key', async () => {
const { setAt } = await import('../overlay-ops');
const out = setAt(
{ plugins: {} },
'/plugins/__proto__/config/k',
'v',
) as Record<string, unknown>;
expect(({} as Record<string, unknown>).config).toBeUndefined();
expect(Object.prototype.hasOwnProperty.call(out.plugins, '__proto__')).toBe(
true,
);
});

it('clone and mergePatch preserve an own "__proto__" key', async () => {
const { clone, mergePatch } = await import('../merge-patch');
const doc = JSON.parse('{"policy_data":{"__proto__":{"x":1}}}');
expect(JSON.stringify(clone(doc))).toBe(
'{"policy_data":{"__proto__":{"x":1}}}',
);
expect(JSON.stringify(mergePatch({}, doc))).toBe(
'{"policy_data":{"__proto__":{"x":1}}}',
);
});
});
68 changes: 68 additions & 0 deletions src/utils/agent-config/__tests__/provenance.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { describe, expect, it } from 'vitest';
import { pluginProvenance, provenanceOf } from '../provenance';

const base = {
verbosity: 0,
plugins: { a: { schedule: 'x', config: { k: 'v' } } },
};

describe('provenance', () => {
it.each([
['/verbosity', {}, 'file'],
['/verbosity', { verbosity: 1 }, 'overrides-file'],
['/plugins/b', { plugins: { b: { source: 's' } } }, 'overlay'],
['/plugins/a', { plugins: { a: null } }, 'removed-by-overlay'],
['/plugins/a/schedule', { plugins: { a: null } }, 'removed-by-overlay'],
[
'/plugins/a/config/k',
{ plugins: { a: { config: { k: null } } } },
'removed-by-overlay',
],
[
'/plugins/a/config/new',
{ plugins: { a: { config: { new: '1' } } } },
'overlay',
],
['/plugins/zzz', { plugins: { zzz: null } }, 'file'],
])('%s with %j → %s', (ptr, overlay, expected) => {
expect(provenanceOf(ptr, base, overlay)).toBe(expected);
});

it('treats a null file value as present', () => {
const b = { policy_data: { k: null } };
expect(provenanceOf('/policy_data/k', b, { policy_data: { k: 1 } })).toBe(
'overrides-file',
);
expect(
provenanceOf('/policy_data/k', b, { policy_data: { k: null } }),
).toBe('removed-by-overlay');
expect(provenanceOf('/policy_data/k', b, {})).toBe('file');
});

it('classifies descendants of an array or scalar replacement by the replacement', () => {
const b = { items: [1], objs: [{ x: 1, y: 2 }], m: { a: { b: 1 } } };
// A null inside a replacing array is data, not a deletion.
expect(provenanceOf('/items/0', b, { items: [null] })).toBe(
'overrides-file',
);
expect(provenanceOf('/objs/0/x', b, { objs: [{ x: 3 }] })).toBe(
'overrides-file',
);
expect(provenanceOf('/objs/0/y', b, { objs: [{ x: 3 }] })).toBe(
'removed-by-overlay',
);
expect(provenanceOf('/objs/1', b, { objs: [{ x: 3 }, 4] })).toBe('overlay');
// A scalar replacing an object removes what was below it.
expect(provenanceOf('/m/a/b', b, { m: 'flat' })).toBe('removed-by-overlay');
expect(provenanceOf('/m/a/b', b, { m: { a: 7 } })).toBe(
'removed-by-overlay',
);
});

it('plugin provenance', () => {
expect(
pluginProvenance('a', base, { plugins: { a: { schedule: 'y' } } }),
).toBe('overrides-file');
expect(pluginProvenance('a', base, {})).toBe('file');
});
});
Loading
Loading