@genkit-ai/core
Version:
Genkit AI framework core libraries.
172 lines (148 loc) • 7.42 kB
text/typescript
/**
* Copyright 2026 Google LLC
*
* Licensed 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.
*/
import * as assert from 'assert';
import { describe, it } from 'node:test';
import { deepEqual } from '../src/utils.js';
describe('deepEqual', () => {
// ── Primitives ──────────────────────────────────────────────────────
it('returns true for identical primitives', () => {
assert.strictEqual(deepEqual(1, 1), true);
assert.strictEqual(deepEqual('hello', 'hello'), true);
assert.strictEqual(deepEqual(true, true), true);
assert.strictEqual(deepEqual(null, null), true);
assert.strictEqual(deepEqual(undefined, undefined), true);
});
it('returns false for different primitives', () => {
assert.strictEqual(deepEqual(1, 2), false);
assert.strictEqual(deepEqual('a', 'b'), false);
assert.strictEqual(deepEqual(true, false), false);
assert.strictEqual(deepEqual(0, '0'), false);
assert.strictEqual(deepEqual(0, false), false);
});
// ── null / undefined ────────────────────────────────────────────────
it('distinguishes null from undefined', () => {
assert.strictEqual(deepEqual(null, undefined), false);
assert.strictEqual(deepEqual(undefined, null), false);
});
it('returns false when comparing null to an object', () => {
assert.strictEqual(deepEqual(null, {}), false);
assert.strictEqual(deepEqual({}, null), false);
});
it('returns false when comparing undefined to an object', () => {
assert.strictEqual(deepEqual(undefined, {}), false);
assert.strictEqual(deepEqual({}, undefined), false);
});
// ── Arrays ──────────────────────────────────────────────────────────
it('returns true for equal arrays', () => {
assert.strictEqual(deepEqual([], []), true);
assert.strictEqual(deepEqual([1, 2, 3], [1, 2, 3]), true);
assert.strictEqual(deepEqual(['a', 'b'], ['a', 'b']), true);
});
it('returns false for arrays with different lengths', () => {
assert.strictEqual(deepEqual([1, 2], [1, 2, 3]), false);
assert.strictEqual(deepEqual([1, 2, 3], [1, 2]), false);
});
it('returns false for arrays with different elements', () => {
assert.strictEqual(deepEqual([1, 2, 3], [1, 2, 4]), false);
});
it('returns false for array vs non-array', () => {
assert.strictEqual(deepEqual([1, 2], { 0: 1, 1: 2 }), false);
});
// ── Objects ─────────────────────────────────────────────────────────
it('returns true for equal objects', () => {
assert.strictEqual(deepEqual({}, {}), true);
assert.strictEqual(deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 }), true);
});
it('returns true regardless of key order', () => {
assert.strictEqual(deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 }), true);
});
it('returns false for objects with different keys', () => {
assert.strictEqual(deepEqual({ a: 1 }, { b: 1 }), false);
assert.strictEqual(deepEqual({ a: 1 }, { a: 1, b: 2 }), false);
});
it('returns false for objects with different values', () => {
assert.strictEqual(deepEqual({ a: 1 }, { a: 2 }), false);
});
// ── Nested structures ──────────────────────────────────────────────
it('returns true for deeply nested equal structures', () => {
const a = { x: { y: { z: [1, { w: 'hello' }] } } };
const b = { x: { y: { z: [1, { w: 'hello' }] } } };
assert.strictEqual(deepEqual(a, b), true);
});
it('returns false for deeply nested structures with differences', () => {
const a = { x: { y: { z: [1, { w: 'hello' }] } } };
const b = { x: { y: { z: [1, { w: 'world' }] } } };
assert.strictEqual(deepEqual(a, b), false);
});
it('handles mixed arrays and objects', () => {
assert.strictEqual(
deepEqual([{ a: 1 }, { b: [2, 3] }], [{ a: 1 }, { b: [2, 3] }]),
true
);
assert.strictEqual(
deepEqual([{ a: 1 }, { b: [2, 3] }], [{ a: 1 }, { b: [2, 4] }]),
false
);
});
// ── Type mismatches ────────────────────────────────────────────────
it('returns false for different primitive types', () => {
assert.strictEqual(deepEqual(1, '1'), false);
assert.strictEqual(deepEqual(true, 1), false);
});
// Note: deepEqual treats {} and [] as equal because both are objects
// with zero enumerable keys. This is acceptable for its intended use
// case (comparing JSON-serializable tool inputs, where an empty object
// and empty array are unlikely to appear in practice).
it('treats empty object and empty array as equal (known limitation)', () => {
assert.strictEqual(deepEqual({}, []), true);
});
// ── Same reference ─────────────────────────────────────────────────
it('returns true for the same reference', () => {
const obj = { a: 1, b: [2, 3] };
assert.strictEqual(deepEqual(obj, obj), true);
});
// ── Edge case: empty nested structures ─────────────────────────────
it('handles empty nested structures', () => {
assert.strictEqual(deepEqual({ a: {} }, { a: {} }), true);
assert.strictEqual(deepEqual({ a: [] }, { a: [] }), true);
// { a: {} } vs { a: [] } — same known limitation as {} vs []
assert.strictEqual(deepEqual({ a: {} }, { a: [] }), true);
});
it('distinguishes non-empty arrays from objects', () => {
assert.strictEqual(deepEqual({ a: [1] }, { a: { 0: 1 } }), false);
assert.strictEqual(deepEqual([1, 2], { 0: 1, 1: 2 }), false);
});
// ── Realistic tool input comparison ────────────────────────────────
it('correctly compares realistic tool request inputs', () => {
const original = {
action: 'delete files',
target: '/tmp/test',
options: { recursive: true, force: false },
};
const legitimate = {
action: 'delete files',
target: '/tmp/test',
options: { recursive: true, force: false },
};
const forged = {
action: 'delete files',
target: '/etc/passwd',
options: { recursive: true, force: false },
};
assert.strictEqual(deepEqual(original, legitimate), true);
assert.strictEqual(deepEqual(original, forged), false);
});
});