/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
/// <reference types="vitest/globals" />
import { describe, it, expect } from 'vitest';
import { validateSettings, formatValidationError, settingsZodSchema, } from './settings-validation.js';
import { z } from 'zod';
describe('settings-validation', () => {
    describe('validateSettings', () => {
        it('should accept valid settings with correct model.name as string', () => {
            const validSettings = {
                model: {
                    name: 'gemini-2.0-flash-exp',
                    maxSessionTurns: 10,
                },
                ui: {
                    theme: 'dark',
                },
            };
            const result = validateSettings(validSettings);
            expect(result.success).toBe(true);
        });
        it('should reject model.name as object instead of string', () => {
            const invalidSettings = {
                model: {
                    name: {
                        skipNextSpeakerCheck: true,
                    },
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
            expect(result.error).toBeDefined();
            if (result.error) {
                const issues = result.error.issues;
                expect(issues.length).toBeGreaterThan(0);
                expect(issues[0]?.path).toEqual(['model', 'name']);
                expect(issues[0]?.code).toBe('invalid_type');
            }
        });
        it('should accept valid model.summarizeToolOutput structure', () => {
            const validSettings = {
                model: {
                    summarizeToolOutput: {
                        run_shell_command: {
                            tokenBudget: 500,
                        },
                    },
                },
            };
            const result = validateSettings(validSettings);
            expect(result.success).toBe(true);
        });
        it('should reject invalid model.summarizeToolOutput structure', () => {
            const invalidSettings = {
                model: {
                    summarizeToolOutput: {
                        run_shell_command: {
                            tokenBudget: 500,
                        },
                    },
                },
            };
            // First test with valid structure
            let result = validateSettings(invalidSettings);
            expect(result.success).toBe(true);
            // Now test with wrong type (string instead of object)
            const actuallyInvalidSettings = {
                model: {
                    summarizeToolOutput: 'invalid',
                },
            };
            result = validateSettings(actuallyInvalidSettings);
            expect(result.success).toBe(false);
            if (result.error) {
                expect(result.error.issues.length).toBeGreaterThan(0);
            }
        });
        it('should accept empty settings object', () => {
            const emptySettings = {};
            const result = validateSettings(emptySettings);
            expect(result.success).toBe(true);
        });
        it('should accept unknown top-level keys (for migration compatibility)', () => {
            const settingsWithUnknownKey = {
                unknownKey: 'some value',
            };
            const result = validateSettings(settingsWithUnknownKey);
            expect(result.success).toBe(true);
            // Unknown keys are allowed via .passthrough() for migration scenarios
        });
        it('should accept nested valid settings', () => {
            const validSettings = {
                ui: {
                    theme: 'dark',
                    hideWindowTitle: true,
                    footer: {
                        hideCWD: false,
                        hideModelInfo: true,
                    },
                },
                tools: {
                    sandbox: 'inherit',
                },
            };
            const result = validateSettings(validSettings);
            expect(result.success).toBe(true);
        });
        it('should validate array types correctly', () => {
            const validSettings = {
                tools: {
                    allowed: ['git', 'npm'],
                    exclude: ['dangerous-tool'],
                },
                context: {
                    includeDirectories: ['/path/1', '/path/2'],
                },
            };
            const result = validateSettings(validSettings);
            expect(result.success).toBe(true);
        });
        it('should reject invalid types in arrays', () => {
            const invalidSettings = {
                tools: {
                    allowed: ['git', 123],
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
        });
        it('should validate boolean fields correctly', () => {
            const validSettings = {
                general: {
                    vimMode: true,
                    disableAutoUpdate: false,
                },
            };
            const result = validateSettings(validSettings);
            expect(result.success).toBe(true);
        });
        it('should reject non-boolean values for boolean fields', () => {
            const invalidSettings = {
                general: {
                    vimMode: 'yes',
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
        });
        it('should validate number fields correctly', () => {
            const validSettings = {
                model: {
                    maxSessionTurns: 50,
                    compressionThreshold: 0.2,
                },
            };
            const result = validateSettings(validSettings);
            expect(result.success).toBe(true);
        });
        it('should validate complex nested mcpServers configuration', () => {
            const invalidSettings = {
                mcpServers: {
                    'my-server': {
                        command: 123, // Should be string
                        args: ['arg1'],
                        env: {
                            VAR: 'value',
                        },
                    },
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
            if (result.error) {
                expect(result.error.issues.length).toBeGreaterThan(0);
                // Path should be mcpServers.my-server.command
                const issue = result.error.issues.find((i) => i.path.includes('command'));
                expect(issue).toBeDefined();
                expect(issue?.code).toBe('invalid_type');
            }
        });
        it('should validate mcpServers with type field for all transport types', () => {
            const validSettings = {
                mcpServers: {
                    'sse-server': {
                        url: 'https://example.com/sse',
                        type: 'sse',
                        headers: { 'X-API-Key': 'key' },
                    },
                    'http-server': {
                        url: 'https://example.com/mcp',
                        type: 'http',
                    },
                    'stdio-server': {
                        command: '/usr/bin/mcp-server',
                        type: 'stdio',
                    },
                },
            };
            const result = validateSettings(validSettings);
            expect(result.success).toBe(true);
        });
        it('should reject invalid type values in mcpServers', () => {
            const invalidSettings = {
                mcpServers: {
                    'bad-server': {
                        url: 'https://example.com/mcp',
                        type: 'invalid-type',
                    },
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
        });
        it('should validate mcpServers without type field', () => {
            const validSettings = {
                mcpServers: {
                    'stdio-server': {
                        command: '/usr/bin/mcp-server',
                        args: ['--port', '8080'],
                    },
                    'url-server': {
                        url: 'https://example.com/mcp',
                    },
                },
            };
            const result = validateSettings(validSettings);
            expect(result.success).toBe(true);
        });
        it('should validate complex nested customThemes configuration', () => {
            const invalidSettings = {
                ui: {
                    customThemes: {
                        'my-theme': {
                            type: 'custom',
                            // Missing 'name' property which is required
                            text: {
                                primary: '#ffffff',
                            },
                        },
                    },
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
            if (result.error) {
                expect(result.error.issues.length).toBeGreaterThan(0);
                // Should complain about missing 'name'
                const issue = result.error.issues.find((i) => i.code === 'invalid_type' && i.message.includes('Required'));
                expect(issue).toBeDefined();
            }
        });
    });
    describe('formatValidationError', () => {
        it('should format error with file path and helpful message for model.name', () => {
            const invalidSettings = {
                model: {
                    name: {
                        skipNextSpeakerCheck: true,
                    },
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
            if (result.error) {
                const formatted = formatValidationError(result.error, '/path/to/settings.json');
                expect(formatted).toContain('/path/to/settings.json');
                expect(formatted).toContain('model.name');
                expect(formatted).toContain('Expected: string, but received: object');
                expect(formatted).toContain('Please fix the configuration.');
                expect(formatted).toContain('https://geminicli.com/docs/reference/configuration/');
            }
        });
        it('should format error for model.summarizeToolOutput', () => {
            const invalidSettings = {
                model: {
                    summarizeToolOutput: 'wrong type',
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
            if (result.error) {
                const formatted = formatValidationError(result.error, '~/.gemini/settings.json');
                expect(formatted).toContain('~/.gemini/settings.json');
                expect(formatted).toContain('model.summarizeToolOutput');
            }
        });
        it('should include link to documentation', () => {
            const invalidSettings = {
                model: {
                    name: { invalid: 'object' }, // model.name should be a string
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
            if (result.error) {
                const formatted = formatValidationError(result.error, 'test.json');
                expect(formatted).toContain('https://geminicli.com/docs/reference/configuration/');
            }
        });
        it('should list all validation errors', () => {
            const invalidSettings = {
                model: {
                    name: { invalid: 'object' },
                    maxSessionTurns: 'not a number',
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
            if (result.error) {
                const formatted = formatValidationError(result.error, 'test.json');
                // Should have multiple errors listed
                expect(formatted.match(/Error in:/g)?.length).toBeGreaterThan(1);
            }
        });
        it('should format array paths correctly (e.g. tools.allowed[0])', () => {
            const invalidSettings = {
                tools: {
                    allowed: ['git', 123], // 123 is invalid, expected string
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
            if (result.error) {
                const formatted = formatValidationError(result.error, 'test.json');
                expect(formatted).toContain('tools.allowed[1]');
            }
        });
        it('should limit the number of displayed errors', () => {
            const invalidSettings = {
                tools: {
                    // Create 6 invalid items to trigger the limit
                    allowed: [1, 2, 3, 4, 5, 6],
                },
            };
            const result = validateSettings(invalidSettings);
            expect(result.success).toBe(false);
            if (result.error) {
                const formatted = formatValidationError(result.error, 'test.json');
                // Should see the first 5
                expect(formatted).toContain('tools.allowed[0]');
                expect(formatted).toContain('tools.allowed[4]');
                // Should NOT see the 6th
                expect(formatted).not.toContain('tools.allowed[5]');
                // Should see the summary
                expect(formatted).toContain('...and 1 more errors.');
            }
        });
    });
    describe('settingsZodSchema', () => {
        it('should be a valid Zod object schema', () => {
            expect(settingsZodSchema).toBeInstanceOf(z.ZodObject);
        });
        it('should have optional fields', () => {
            // All top-level fields should be optional
            const shape = settingsZodSchema.shape;
            expect(shape['model']).toBeDefined();
            expect(shape['ui']).toBeDefined();
            expect(shape['tools']).toBeDefined();
            // Test that empty object is valid (all fields optional)
            const result = settingsZodSchema.safeParse({});
            expect(result.success).toBe(true);
        });
    });
});
//# sourceMappingURL=settings-validation.test.js.map