From c2d38bac54aa4c353769523cd1dbcecf22354375 Mon Sep 17 00:00:00 2001 From: Krrish Verma Date: Wed, 11 Mar 2026 12:30:37 +0530 Subject: [PATCH] test(core): add missing tests for prompts/utils.ts (#19941) Co-authored-by: Jacob Richman --- packages/core/src/prompts/utils.test.ts | 304 ++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 packages/core/src/prompts/utils.test.ts diff --git a/packages/core/src/prompts/utils.test.ts b/packages/core/src/prompts/utils.test.ts new file mode 100644 index 0000000000..1c7d1e03c1 --- /dev/null +++ b/packages/core/src/prompts/utils.test.ts @@ -0,0 +1,304 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { + resolvePathFromEnv, + isSectionEnabled, + applySubstitutions, +} from './utils.js'; +import type { Config } from '../config/config.js'; + +vi.mock('../utils/paths.js', () => ({ + homedir: vi.fn().mockReturnValue('/mock/home'), +})); + +vi.mock('../utils/debugLogger.js', () => ({ + debugLogger: { + warn: vi.fn(), + }, +})); + +vi.mock('./snippets.js', () => ({ + renderSubAgents: vi.fn().mockReturnValue('mocked-sub-agents'), +})); + +vi.mock('./snippets.legacy.js', () => ({ + renderSubAgents: vi.fn().mockReturnValue('mocked-legacy-sub-agents'), +})); + +describe('resolvePathFromEnv', () => { + it('should return default values for undefined input', () => { + const result = resolvePathFromEnv(undefined); + expect(result).toEqual({ + isSwitch: false, + value: null, + isDisabled: false, + }); + }); + + it('should return default values for empty string input', () => { + const result = resolvePathFromEnv(''); + expect(result).toEqual({ + isSwitch: false, + value: null, + isDisabled: false, + }); + }); + + it('should return default values for whitespace-only input', () => { + const result = resolvePathFromEnv(' '); + expect(result).toEqual({ + isSwitch: false, + value: null, + isDisabled: false, + }); + }); + + it('should recognize "true" as an enabled switch', () => { + const result = resolvePathFromEnv('true'); + expect(result).toEqual({ + isSwitch: true, + value: 'true', + isDisabled: false, + }); + }); + + it('should recognize "1" as an enabled switch', () => { + const result = resolvePathFromEnv('1'); + expect(result).toEqual({ + isSwitch: true, + value: '1', + isDisabled: false, + }); + }); + + it('should recognize "false" as a disabled switch', () => { + const result = resolvePathFromEnv('false'); + expect(result).toEqual({ + isSwitch: true, + value: 'false', + isDisabled: true, + }); + }); + + it('should recognize "0" as a disabled switch', () => { + const result = resolvePathFromEnv('0'); + expect(result).toEqual({ + isSwitch: true, + value: '0', + isDisabled: true, + }); + }); + + it('should handle case-insensitive switch values', () => { + const result = resolvePathFromEnv('TRUE'); + expect(result).toEqual({ + isSwitch: true, + value: 'true', + isDisabled: false, + }); + }); + + it('should handle case-insensitive FALSE', () => { + const result = resolvePathFromEnv('FALSE'); + expect(result).toEqual({ + isSwitch: true, + value: 'false', + isDisabled: true, + }); + }); + + it('should trim whitespace before evaluating switch values', () => { + const result = resolvePathFromEnv(' true '); + expect(result).toEqual({ + isSwitch: true, + value: 'true', + isDisabled: false, + }); + }); + + it('should resolve a regular path', () => { + const result = resolvePathFromEnv('/some/absolute/path'); + expect(result.isSwitch).toBe(false); + expect(result.value).toBe('/some/absolute/path'); + expect(result.isDisabled).toBe(false); + }); + + it('should resolve a tilde path to the home directory', () => { + const result = resolvePathFromEnv('~/my/custom/path'); + expect(result.isSwitch).toBe(false); + expect(result.value).toContain('/mock/home'); + expect(result.value).toContain('my/custom/path'); + expect(result.isDisabled).toBe(false); + }); + + it('should resolve a bare tilde to the home directory', () => { + const result = resolvePathFromEnv('~'); + expect(result.isSwitch).toBe(false); + expect(result.value).toBe('/mock/home'); + expect(result.isDisabled).toBe(false); + }); + + it('should handle home directory resolution failure gracefully', async () => { + const { homedir } = await import('../utils/paths.js'); + vi.mocked(homedir).mockImplementationOnce(() => { + throw new Error('No home directory'); + }); + + const result = resolvePathFromEnv('~/some/path'); + expect(result).toEqual({ + isSwitch: false, + value: null, + isDisabled: false, + }); + }); +}); + +describe('isSectionEnabled', () => { + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it('should return true when the env var is not set', () => { + expect(isSectionEnabled('SOME_KEY')).toBe(true); + }); + + it('should return true when the env var is set to "1"', () => { + vi.stubEnv('GEMINI_PROMPT_SOME_KEY', '1'); + expect(isSectionEnabled('SOME_KEY')).toBe(true); + }); + + it('should return true when the env var is set to "true"', () => { + vi.stubEnv('GEMINI_PROMPT_SOME_KEY', 'true'); + expect(isSectionEnabled('SOME_KEY')).toBe(true); + }); + + it('should return false when the env var is set to "0"', () => { + vi.stubEnv('GEMINI_PROMPT_SOME_KEY', '0'); + expect(isSectionEnabled('SOME_KEY')).toBe(false); + }); + + it('should return false when the env var is set to "false"', () => { + vi.stubEnv('GEMINI_PROMPT_SOME_KEY', 'false'); + expect(isSectionEnabled('SOME_KEY')).toBe(false); + }); + + it('should handle case-insensitive key conversion', () => { + vi.stubEnv('GEMINI_PROMPT_MY_SECTION', '0'); + expect(isSectionEnabled('my_section')).toBe(false); + }); + + it('should handle whitespace around the env var value', () => { + vi.stubEnv('GEMINI_PROMPT_SOME_KEY', ' false '); + expect(isSectionEnabled('SOME_KEY')).toBe(false); + }); + + it('should return true for any non-falsy value', () => { + vi.stubEnv('GEMINI_PROMPT_SOME_KEY', 'enabled'); + expect(isSectionEnabled('SOME_KEY')).toBe(true); + }); +}); + +describe('applySubstitutions', () => { + let mockConfig: Config; + + beforeEach(() => { + mockConfig = { + getAgentRegistry: vi.fn().mockReturnValue({ + getAllDefinitions: vi.fn().mockReturnValue([]), + }), + getToolRegistry: vi.fn().mockReturnValue({ + getAllToolNames: vi.fn().mockReturnValue([]), + }), + } as unknown as Config; + }); + + it('should replace ${AgentSkills} with the skills prompt', () => { + const result = applySubstitutions( + 'Skills: ${AgentSkills}', + mockConfig, + 'my-skills-content', + ); + expect(result).toBe('Skills: my-skills-content'); + }); + + it('should replace multiple ${AgentSkills} occurrences', () => { + const result = applySubstitutions( + '${AgentSkills} and ${AgentSkills}', + mockConfig, + 'skills', + ); + expect(result).toBe('skills and skills'); + }); + + it('should replace ${SubAgents} with rendered sub-agents content', () => { + const result = applySubstitutions( + 'Agents: ${SubAgents}', + mockConfig, + '', + true, + ); + expect(result).toContain('mocked-sub-agents'); + }); + + it('should use legacy snippets when isGemini3 is false', () => { + const result = applySubstitutions( + 'Agents: ${SubAgents}', + mockConfig, + '', + false, + ); + expect(result).toContain('mocked-legacy-sub-agents'); + }); + + it('should replace ${AvailableTools} with tool names list', () => { + vi.mocked(mockConfig.getToolRegistry).mockReturnValue({ + getAllToolNames: vi.fn().mockReturnValue(['read_file', 'write_file']), + getAllTools: vi.fn().mockReturnValue([]), + } as unknown as ReturnType); + + const result = applySubstitutions( + 'Tools: ${AvailableTools}', + mockConfig, + '', + ); + expect(result).toContain('- read_file'); + expect(result).toContain('- write_file'); + }); + + it('should show no tools message when no tools available', () => { + const result = applySubstitutions( + 'Tools: ${AvailableTools}', + mockConfig, + '', + ); + expect(result).toContain('No tools are currently available.'); + }); + + it('should replace tool-specific ${toolName_ToolName} variables', () => { + vi.mocked(mockConfig.getToolRegistry).mockReturnValue({ + getAllToolNames: vi.fn().mockReturnValue(['read_file']), + getAllTools: vi.fn().mockReturnValue([]), + } as unknown as ReturnType); + + const result = applySubstitutions( + 'Use ${read_file_ToolName} to read', + mockConfig, + '', + ); + expect(result).toBe('Use read_file to read'); + }); + + it('should handle a prompt with no substitution placeholders', () => { + const result = applySubstitutions( + 'A plain prompt with no variables.', + mockConfig, + '', + ); + expect(result).toBe('A plain prompt with no variables.'); + }); +});