/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { createContentGenerator, AuthType, createContentGeneratorConfig, validateBaseUrl, } from './contentGenerator.js';
import { createCodeAssistContentGenerator } from '../code_assist/codeAssist.js';
import { GoogleGenAI } from '@google/genai';
import { LoggingContentGenerator } from './loggingContentGenerator.js';
import { loadApiKey } from './apiKeyCredentialStorage.js';
import { FakeContentGenerator } from './fakeContentGenerator.js';
import { RecordingContentGenerator } from './recordingContentGenerator.js';
import { resetVersionCache } from '../utils/version.js';
vi.mock('../code_assist/codeAssist.js');
vi.mock('@google/genai');
vi.mock('./apiKeyCredentialStorage.js', () => ({
    loadApiKey: vi.fn(),
}));
vi.mock('./fakeContentGenerator.js');
const mockConfig = {
    getModel: vi.fn().mockReturnValue('gemini-pro'),
    getProxy: vi.fn().mockReturnValue(undefined),
    getUsageStatisticsEnabled: vi.fn().mockReturnValue(true),
    getClientName: vi.fn().mockReturnValue(undefined),
};
describe('createContentGenerator', () => {
    beforeEach(() => {
        resetVersionCache();
        vi.clearAllMocks();
    });
    afterEach(() => {
        vi.unstubAllEnvs();
    });
    it('should create a FakeContentGenerator', async () => {
        const mockGenerator = {};
        vi.mocked(FakeContentGenerator.fromFile).mockResolvedValue(mockGenerator);
        const fakeResponsesFile = 'fake/responses.yaml';
        const mockConfigWithFake = {
            fakeResponses: fakeResponsesFile,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const generator = await createContentGenerator({
            authType: AuthType.USE_GEMINI,
        }, mockConfigWithFake);
        expect(FakeContentGenerator.fromFile).toHaveBeenCalledWith(fakeResponsesFile);
        expect(generator).toEqual(new LoggingContentGenerator(mockGenerator, mockConfigWithFake));
    });
    it('should create a RecordingContentGenerator', async () => {
        const fakeResponsesFile = 'fake/responses.yaml';
        const recordResponsesFile = 'record/responses.yaml';
        const mockConfigWithRecordResponses = {
            fakeResponses: fakeResponsesFile,
            recordResponses: recordResponsesFile,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const generator = await createContentGenerator({
            authType: AuthType.USE_GEMINI,
        }, mockConfigWithRecordResponses);
        expect(generator).toBeInstanceOf(RecordingContentGenerator);
    });
    it('should create a CodeAssistContentGenerator when AuthType is LOGIN_WITH_GOOGLE', async () => {
        const mockGenerator = {};
        vi.mocked(createCodeAssistContentGenerator).mockResolvedValue(mockGenerator);
        const generator = await createContentGenerator({
            authType: AuthType.LOGIN_WITH_GOOGLE,
        }, mockConfig);
        expect(createCodeAssistContentGenerator).toHaveBeenCalled();
        expect(generator).toEqual(new LoggingContentGenerator(mockGenerator, mockConfig));
    });
    it('should create a CodeAssistContentGenerator when AuthType is COMPUTE_ADC', async () => {
        const mockGenerator = {};
        vi.mocked(createCodeAssistContentGenerator).mockResolvedValue(mockGenerator);
        const generator = await createContentGenerator({
            authType: AuthType.COMPUTE_ADC,
        }, mockConfig);
        expect(createCodeAssistContentGenerator).toHaveBeenCalled();
        expect(generator).toEqual(new LoggingContentGenerator(mockGenerator, mockConfig));
    });
    it('should create a GoogleGenAI content generator', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => true,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        // Set a fixed version for testing
        vi.stubEnv('CLI_VERSION', '1.2.3');
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        const generator = await createContentGenerator({
            apiKey: 'test-api-key',
            authType: AuthType.USE_GEMINI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith({
            apiKey: 'test-api-key',
            vertexai: undefined,
            httpOptions: expect.objectContaining({
                headers: expect.objectContaining({
                    'User-Agent': expect.stringMatching(/GeminiCLI\/1\.2\.3\/gemini-pro \(.*; .*; .*\)/),
                }),
            }),
        });
        expect(generator).toEqual(new LoggingContentGenerator(mockGenerator.models, mockConfig));
    });
    it('should include clientName prefix in User-Agent when specified', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => true,
            getClientName: vi.fn().mockReturnValue('a2a-server'),
        };
        // Set a fixed version for testing
        vi.stubEnv('CLI_VERSION', '1.2.3');
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        await createContentGenerator({ apiKey: 'test-api-key', authType: AuthType.USE_GEMINI }, mockConfig, undefined);
        expect(GoogleGenAI).toHaveBeenCalledWith(expect.objectContaining({
            httpOptions: expect.objectContaining({
                headers: expect.objectContaining({
                    'User-Agent': expect.stringMatching(/GeminiCLI-a2a-server\/.*\/gemini-pro \(.*; .*; .*\)/),
                }),
            }),
        }));
    });
    it('should include custom headers from GEMINI_CLI_CUSTOM_HEADERS for Code Assist requests', async () => {
        const mockGenerator = {};
        vi.mocked(createCodeAssistContentGenerator).mockResolvedValue(mockGenerator);
        vi.stubEnv('GEMINI_CLI_CUSTOM_HEADERS', 'X-Test-Header: test-value, Another-Header: another value');
        await createContentGenerator({
            authType: AuthType.LOGIN_WITH_GOOGLE,
        }, mockConfig);
        expect(createCodeAssistContentGenerator).toHaveBeenCalledWith({
            headers: expect.objectContaining({
                'User-Agent': expect.any(String),
                'X-Test-Header': 'test-value',
                'Another-Header': 'another value',
            }),
        }, AuthType.LOGIN_WITH_GOOGLE, mockConfig, undefined);
    });
    it('should include custom headers from GEMINI_CLI_CUSTOM_HEADERS for GoogleGenAI requests without inferring auth mechanism', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        vi.stubEnv('GEMINI_CLI_CUSTOM_HEADERS', 'X-Test-Header: test, Another: value');
        await createContentGenerator({
            apiKey: 'test-api-key',
            authType: AuthType.USE_GEMINI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith({
            apiKey: 'test-api-key',
            vertexai: undefined,
            httpOptions: expect.objectContaining({
                headers: expect.objectContaining({
                    'User-Agent': expect.any(String),
                    'X-Test-Header': 'test',
                    Another: 'value',
                }),
            }),
        });
        expect(GoogleGenAI).toHaveBeenCalledWith(expect.not.objectContaining({
            httpOptions: expect.objectContaining({
                headers: expect.objectContaining({
                    Authorization: expect.any(String),
                }),
            }),
        }));
    });
    it('should pass api key as Authorization Header when GEMINI_API_KEY_AUTH_MECHANISM is set to bearer', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        vi.stubEnv('GEMINI_API_KEY_AUTH_MECHANISM', 'bearer');
        await createContentGenerator({
            apiKey: 'test-api-key',
            authType: AuthType.USE_GEMINI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith({
            apiKey: 'test-api-key',
            vertexai: undefined,
            httpOptions: expect.objectContaining({
                headers: expect.objectContaining({
                    'User-Agent': expect.any(String),
                    Authorization: 'Bearer test-api-key',
                }),
            }),
        });
    });
    it('should not pass api key as Authorization Header when GEMINI_API_KEY_AUTH_MECHANISM is not set (default behavior)', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        // GEMINI_API_KEY_AUTH_MECHANISM is not stubbed, so it will be undefined, triggering default 'x-goog-api-key'
        await createContentGenerator({
            apiKey: 'test-api-key',
            authType: AuthType.USE_GEMINI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith({
            apiKey: 'test-api-key',
            vertexai: undefined,
            httpOptions: expect.objectContaining({
                headers: expect.objectContaining({
                    'User-Agent': expect.any(String),
                }),
            }),
        });
        // Explicitly assert that Authorization header is NOT present
        expect(GoogleGenAI).toHaveBeenCalledWith(expect.not.objectContaining({
            httpOptions: expect.objectContaining({
                headers: expect.objectContaining({
                    Authorization: expect.any(String),
                }),
            }),
        }));
    });
    it('should create a GoogleGenAI content generator with client install id logging disabled', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        const generator = await createContentGenerator({
            apiKey: 'test-api-key',
            authType: AuthType.USE_GEMINI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith({
            apiKey: 'test-api-key',
            vertexai: undefined,
            httpOptions: expect.objectContaining({
                headers: {
                    'User-Agent': expect.any(String),
                },
            }),
        });
        expect(generator).toEqual(new LoggingContentGenerator(mockGenerator.models, mockConfig));
    });
    it('should pass apiVersion to GoogleGenAI when GOOGLE_GENAI_API_VERSION is set', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        vi.stubEnv('GOOGLE_GENAI_API_VERSION', 'v1');
        await createContentGenerator({
            apiKey: 'test-api-key',
            authType: AuthType.USE_GEMINI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith({
            apiKey: 'test-api-key',
            vertexai: undefined,
            httpOptions: expect.objectContaining({
                headers: expect.objectContaining({
                    'User-Agent': expect.any(String),
                }),
            }),
            apiVersion: 'v1',
        });
    });
    it('should not include apiVersion when GOOGLE_GENAI_API_VERSION is not set', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        await createContentGenerator({
            apiKey: 'test-api-key',
            authType: AuthType.USE_GEMINI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith({
            apiKey: 'test-api-key',
            vertexai: undefined,
            httpOptions: expect.objectContaining({
                headers: expect.objectContaining({
                    'User-Agent': expect.any(String),
                }),
            }),
        });
        expect(GoogleGenAI).toHaveBeenCalledWith(expect.not.objectContaining({
            apiVersion: expect.any(String),
        }));
    });
    it('should not include apiVersion when GOOGLE_GENAI_API_VERSION is an empty string', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        vi.stubEnv('GOOGLE_GENAI_API_VERSION', '');
        await createContentGenerator({
            apiKey: 'test-api-key',
            authType: AuthType.USE_GEMINI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith({
            apiKey: 'test-api-key',
            vertexai: undefined,
            httpOptions: expect.objectContaining({
                headers: expect.objectContaining({
                    'User-Agent': expect.any(String),
                }),
            }),
        });
        expect(GoogleGenAI).toHaveBeenCalledWith(expect.not.objectContaining({
            apiVersion: expect.any(String),
        }));
    });
    it('should pass GOOGLE_GEMINI_BASE_URL as httpOptions.baseUrl for Gemini API', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        vi.stubEnv('GOOGLE_GEMINI_BASE_URL', 'https://my-gemini-proxy.example.com');
        await createContentGenerator({
            apiKey: 'test-api-key',
            authType: AuthType.USE_GEMINI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith(expect.objectContaining({
            httpOptions: expect.objectContaining({
                baseUrl: 'https://my-gemini-proxy.example.com',
            }),
        }));
    });
    it('should pass GOOGLE_VERTEX_BASE_URL as httpOptions.baseUrl for Vertex AI', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        vi.stubEnv('GOOGLE_VERTEX_BASE_URL', 'https://my-vertex-proxy.example.com');
        await createContentGenerator({
            apiKey: 'test-api-key',
            vertexai: true,
            authType: AuthType.USE_VERTEX_AI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith(expect.objectContaining({
            httpOptions: expect.objectContaining({
                baseUrl: 'https://my-vertex-proxy.example.com',
            }),
        }));
    });
    it('should not include baseUrl in httpOptions when GOOGLE_GEMINI_BASE_URL is not set', async () => {
        vi.stubEnv('GOOGLE_GEMINI_BASE_URL', '');
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        await createContentGenerator({
            apiKey: 'test-api-key',
            authType: AuthType.USE_GEMINI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith(expect.not.objectContaining({
            httpOptions: expect.objectContaining({
                baseUrl: expect.any(String),
            }),
        }));
    });
    it('should reject an insecure GOOGLE_GEMINI_BASE_URL for non-local hosts', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        vi.stubEnv('GOOGLE_GEMINI_BASE_URL', 'http://evil-proxy.example.com');
        await expect(createContentGenerator({
            apiKey: 'test-api-key',
            authType: AuthType.USE_GEMINI,
        }, mockConfig)).rejects.toThrow('Custom base URL must use HTTPS unless it is localhost.');
    });
    it('should pass apiVersion for Vertex AI when GOOGLE_GENAI_API_VERSION is set', async () => {
        const mockConfig = {
            getModel: vi.fn().mockReturnValue('gemini-pro'),
            getProxy: vi.fn().mockReturnValue(undefined),
            getUsageStatisticsEnabled: () => false,
            getClientName: vi.fn().mockReturnValue(undefined),
        };
        const mockGenerator = {
            models: {},
        };
        vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator);
        vi.stubEnv('GOOGLE_GENAI_API_VERSION', 'v1alpha');
        await createContentGenerator({
            apiKey: 'test-api-key',
            vertexai: true,
            authType: AuthType.USE_VERTEX_AI,
        }, mockConfig);
        expect(GoogleGenAI).toHaveBeenCalledWith({
            apiKey: 'test-api-key',
            vertexai: true,
            httpOptions: expect.objectContaining({
                headers: expect.objectContaining({
                    'User-Agent': expect.any(String),
                }),
            }),
            apiVersion: 'v1alpha',
        });
    });
});
describe('createContentGeneratorConfig', () => {
    const mockConfig = {
        getModel: vi.fn().mockReturnValue('gemini-pro'),
        setModel: vi.fn(),
        flashFallbackHandler: vi.fn(),
        getProxy: vi.fn(),
        getClientName: vi.fn().mockReturnValue(undefined),
    };
    beforeEach(() => {
        // Reset modules to re-evaluate imports and environment variables
        vi.resetModules();
        vi.clearAllMocks();
    });
    afterEach(() => {
        vi.unstubAllEnvs();
    });
    it('should configure for Gemini using GEMINI_API_KEY when set', async () => {
        vi.stubEnv('GEMINI_API_KEY', 'env-gemini-key');
        const config = await createContentGeneratorConfig(mockConfig, AuthType.USE_GEMINI);
        expect(config.apiKey).toBe('env-gemini-key');
        expect(config.vertexai).toBe(false);
    });
    it('should not configure for Gemini if GEMINI_API_KEY is empty', async () => {
        vi.stubEnv('GEMINI_API_KEY', '');
        const config = await createContentGeneratorConfig(mockConfig, AuthType.USE_GEMINI);
        expect(config.apiKey).toBeUndefined();
        expect(config.vertexai).toBeUndefined();
    });
    it('should not configure for Gemini if GEMINI_API_KEY is not set and storage is empty', async () => {
        vi.stubEnv('GEMINI_API_KEY', '');
        vi.mocked(loadApiKey).mockResolvedValue(null);
        const config = await createContentGeneratorConfig(mockConfig, AuthType.USE_GEMINI);
        expect(config.apiKey).toBeUndefined();
        expect(config.vertexai).toBeUndefined();
    });
    it('should configure for Vertex AI using GOOGLE_API_KEY when set', async () => {
        vi.stubEnv('GOOGLE_API_KEY', 'env-google-key');
        const config = await createContentGeneratorConfig(mockConfig, AuthType.USE_VERTEX_AI);
        expect(config.apiKey).toBe('env-google-key');
        expect(config.vertexai).toBe(true);
    });
    it('should configure for Vertex AI using GCP project and location when set', async () => {
        vi.stubEnv('GOOGLE_API_KEY', undefined);
        vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'env-gcp-project');
        vi.stubEnv('GOOGLE_CLOUD_LOCATION', 'env-gcp-location');
        const config = await createContentGeneratorConfig(mockConfig, AuthType.USE_VERTEX_AI);
        expect(config.vertexai).toBe(true);
        expect(config.apiKey).toBeUndefined();
    });
    it('should not configure for Vertex AI if required env vars are empty', async () => {
        vi.stubEnv('GOOGLE_API_KEY', '');
        vi.stubEnv('GOOGLE_CLOUD_PROJECT', '');
        vi.stubEnv('GOOGLE_CLOUD_LOCATION', '');
        const config = await createContentGeneratorConfig(mockConfig, AuthType.USE_VERTEX_AI);
        expect(config.apiKey).toBeUndefined();
        expect(config.vertexai).toBeUndefined();
    });
});
describe('validateBaseUrl', () => {
    it('should accept a valid HTTPS URL', () => {
        expect(() => validateBaseUrl('https://my-proxy.example.com')).not.toThrow();
    });
    it('should accept HTTP for localhost', () => {
        expect(() => validateBaseUrl('http://localhost:8080')).not.toThrow();
    });
    it('should accept HTTP for 127.0.0.1', () => {
        expect(() => validateBaseUrl('http://127.0.0.1:3000')).not.toThrow();
    });
    it('should accept HTTP for ::1', () => {
        expect(() => validateBaseUrl('http://[::1]:8080')).not.toThrow();
    });
    it('should reject HTTP for non-local hosts', () => {
        expect(() => validateBaseUrl('http://my-proxy.example.com')).toThrow('Custom base URL must use HTTPS unless it is localhost.');
    });
    it('should reject an invalid URL', () => {
        expect(() => validateBaseUrl('not-a-url')).toThrow('Invalid custom base URL: not-a-url');
    });
});
//# sourceMappingURL=contentGenerator.test.js.map