From 917f0bbef7b7132dca0c37e474a82a9bb07f0df1 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sat, 7 Feb 2026 06:25:29 -0500 Subject: [PATCH] test(cliproxy): add edge case coverage for Gemini schema sanitizer Add tests for: exclusiveMinimum/exclusiveMaximum/multipleOf stripping, uniqueItems/contains/additionalItems stripping, writeOnly/definitions stripping, example with array values, default with complex nested objects, empty properties/anyOf edge cases. Fix proxy log message to say "Gemini-unsupported" instead of "non-standard". --- src/cliproxy/tool-sanitization-proxy.ts | 2 +- tests/unit/cliproxy/schema-sanitizer.test.ts | 66 ++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/cliproxy/tool-sanitization-proxy.ts b/src/cliproxy/tool-sanitization-proxy.ts index 87c3b811..36c7c40f 100644 --- a/src/cliproxy/tool-sanitization-proxy.ts +++ b/src/cliproxy/tool-sanitization-proxy.ts @@ -218,7 +218,7 @@ export class ToolSanitizationProxy { for (const entry of schemaResult.removedByTool) { this.writeLog( 'warn', - `[tool-sanitization-proxy] Schema sanitized for "${entry.name}": removed ${entry.removed.length} non-standard properties` + `[tool-sanitization-proxy] Schema sanitized for "${entry.name}": removed ${entry.removed.length} Gemini-unsupported properties` ); } this.log( diff --git a/tests/unit/cliproxy/schema-sanitizer.test.ts b/tests/unit/cliproxy/schema-sanitizer.test.ts index 8138ef5b..88d1b922 100644 --- a/tests/unit/cliproxy/schema-sanitizer.test.ts +++ b/tests/unit/cliproxy/schema-sanitizer.test.ts @@ -305,6 +305,72 @@ describe('sanitizeInputSchema', () => { expect(result.schema).toEqual({ type: 'object' }); }); + test('strips number/array/object validation fields unsupported by Gemini', () => { + const schema = { + type: 'object', + properties: { + score: { + type: 'number', + exclusiveMinimum: 0, + exclusiveMaximum: 100, + multipleOf: 5, + }, + tags: { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + contains: { type: 'string' }, + additionalItems: false, + }, + meta: { + type: 'object', + writeOnly: true, + definitions: { addr: { type: 'string' } }, + }, + }, + }; + + const result = sanitizeInputSchema(schema); + + expect(result.removedPaths).toContain('properties.score.exclusiveMinimum'); + expect(result.removedPaths).toContain('properties.score.exclusiveMaximum'); + expect(result.removedPaths).toContain('properties.score.multipleOf'); + expect(result.removedPaths).toContain('properties.tags.uniqueItems'); + expect(result.removedPaths).toContain('properties.tags.contains'); + expect(result.removedPaths).toContain('properties.tags.additionalItems'); + expect(result.removedPaths).toContain('properties.meta.writeOnly'); + expect(result.removedPaths).toContain('properties.meta.definitions'); + expect(result.removedCount).toBe(8); + }); + + test('preserves example/default with complex values without recursion', () => { + const schema = { + type: 'object', + example: ['value1', 'value2'], + default: { nested: { deep: { unsupportedKey: true } } }, + }; + + const result = sanitizeInputSchema(schema); + + expect(result.removedCount).toBe(0); + expect(result.schema.example).toEqual(['value1', 'value2']); + expect(result.schema.default).toEqual({ nested: { deep: { unsupportedKey: true } } }); + }); + + test('handles empty properties and anyOf edge cases', () => { + const schema = { + type: 'object', + properties: {}, + anyOf: [], + }; + + const result = sanitizeInputSchema(schema); + + expect(result.removedCount).toBe(0); + expect(result.schema.properties).toEqual({}); + expect(result.schema.anyOf).toEqual([]); + }); + test('preserves all Gemini-supported fields', () => { const schema = { type: 'object',