diff options
| author | Yuki Okita <[email protected]> | 2025-08-20 10:55:47 +0900 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-20 01:55:47 +0000 |
| commit | 21c6480b65528a98ac0e1e3855f3c78c1f9b7cbe (patch) | |
| tree | 5555ec429209e87e0c21483c9e5fddd53ac01dbc /packages/cli/src/services/FileCommandLoader.test.ts | |
| parent | 1049d388451120587a8643a401fd71430a8cd5fe (diff) | |
Refac: Centralize storage file management (#4078)
Co-authored-by: Taylor Mullen <[email protected]>
Diffstat (limited to 'packages/cli/src/services/FileCommandLoader.test.ts')
| -rw-r--r-- | packages/cli/src/services/FileCommandLoader.test.ts | 89 |
1 files changed, 46 insertions, 43 deletions
diff --git a/packages/cli/src/services/FileCommandLoader.test.ts b/packages/cli/src/services/FileCommandLoader.test.ts index 42d93074..9960a632 100644 --- a/packages/cli/src/services/FileCommandLoader.test.ts +++ b/packages/cli/src/services/FileCommandLoader.test.ts @@ -5,11 +5,7 @@ */ import * as path from 'node:path'; -import { - Config, - getProjectCommandsDir, - getUserCommandsDir, -} from '@google/gemini-cli-core'; +import { Config, Storage } from '@google/gemini-cli-core'; import mock from 'mock-fs'; import { FileCommandLoader } from './FileCommandLoader.js'; import { assert, vi } from 'vitest'; @@ -57,6 +53,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { await importOriginal<typeof import('@google/gemini-cli-core')>(); return { ...original, + Storage: original.Storage, isCommandAllowed: vi.fn(), ShellExecutionService: { execute: vi.fn(), @@ -86,7 +83,7 @@ describe('FileCommandLoader', () => { }); it('loads a single command from a file', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'test.toml': 'prompt = "This is a test prompt"', @@ -127,7 +124,7 @@ describe('FileCommandLoader', () => { itif(process.platform !== 'win32')( 'loads commands from a symlinked directory', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); const realCommandsDir = '/real/commands'; mock({ [realCommandsDir]: { @@ -152,7 +149,7 @@ describe('FileCommandLoader', () => { itif(process.platform !== 'win32')( 'loads commands from a symlinked subdirectory', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); const realNamespacedDir = '/real/namespaced-commands'; mock({ [userCommandsDir]: { @@ -176,7 +173,7 @@ describe('FileCommandLoader', () => { ); it('loads multiple commands', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'test1.toml': 'prompt = "Prompt 1"', @@ -191,7 +188,7 @@ describe('FileCommandLoader', () => { }); it('creates deeply nested namespaces correctly', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { @@ -205,7 +202,7 @@ describe('FileCommandLoader', () => { const mockConfig = { getProjectRoot: vi.fn(() => '/path/to/project'), getExtensions: vi.fn(() => []), - } as unknown as Config; + } as Config; const loader = new FileCommandLoader(mockConfig); const commands = await loader.loadCommands(signal); expect(commands).toHaveLength(1); @@ -213,7 +210,7 @@ describe('FileCommandLoader', () => { }); it('creates namespaces from nested directories', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { git: { @@ -232,8 +229,10 @@ describe('FileCommandLoader', () => { }); it('returns both user and project commands in order', async () => { - const userCommandsDir = getUserCommandsDir(); - const projectCommandsDir = getProjectCommandsDir(process.cwd()); + const userCommandsDir = Storage.getUserCommandsDir(); + const projectCommandsDir = new Storage( + process.cwd(), + ).getProjectCommandsDir(); mock({ [userCommandsDir]: { 'test.toml': 'prompt = "User prompt"', @@ -246,7 +245,7 @@ describe('FileCommandLoader', () => { const mockConfig = { getProjectRoot: vi.fn(() => process.cwd()), getExtensions: vi.fn(() => []), - } as unknown as Config; + } as Config; const loader = new FileCommandLoader(mockConfig); const commands = await loader.loadCommands(signal); @@ -284,7 +283,7 @@ describe('FileCommandLoader', () => { }); it('ignores files with TOML syntax errors', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'invalid.toml': 'this is not valid toml', @@ -300,7 +299,7 @@ describe('FileCommandLoader', () => { }); it('ignores files that are semantically invalid (missing prompt)', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'no_prompt.toml': 'description = "This file is missing a prompt"', @@ -316,7 +315,7 @@ describe('FileCommandLoader', () => { }); it('handles filename edge cases correctly', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'test.v1.toml': 'prompt = "Test prompt"', @@ -338,7 +337,7 @@ describe('FileCommandLoader', () => { }); it('uses a default description if not provided', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'test.toml': 'prompt = "Test prompt"', @@ -353,7 +352,7 @@ describe('FileCommandLoader', () => { }); it('uses the provided description', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'test.toml': 'prompt = "Test prompt"\ndescription = "My test command"', @@ -368,7 +367,7 @@ describe('FileCommandLoader', () => { }); it('should sanitize colons in filenames to prevent namespace conflicts', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'legacy:command.toml': 'prompt = "This is a legacy command"', @@ -388,7 +387,7 @@ describe('FileCommandLoader', () => { describe('Processor Instantiation Logic', () => { it('instantiates only DefaultArgumentProcessor if no {{args}} or !{} are present', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'simple.toml': `prompt = "Just a regular prompt"`, @@ -403,7 +402,7 @@ describe('FileCommandLoader', () => { }); it('instantiates only ShellProcessor if {{args}} is present (but not !{})', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'args.toml': `prompt = "Prompt with {{args}}"`, @@ -418,7 +417,7 @@ describe('FileCommandLoader', () => { }); it('instantiates ShellProcessor and DefaultArgumentProcessor if !{} is present (but not {{args}})', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'shell.toml': `prompt = "Prompt with !{cmd}"`, @@ -433,7 +432,7 @@ describe('FileCommandLoader', () => { }); it('instantiates only ShellProcessor if both {{args}} and !{} are present', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'both.toml': `prompt = "Prompt with {{args}} and !{cmd}"`, @@ -450,8 +449,10 @@ describe('FileCommandLoader', () => { describe('Extension Command Loading', () => { it('loads commands from active extensions', async () => { - const userCommandsDir = getUserCommandsDir(); - const projectCommandsDir = getProjectCommandsDir(process.cwd()); + const userCommandsDir = Storage.getUserCommandsDir(); + const projectCommandsDir = new Storage( + process.cwd(), + ).getProjectCommandsDir(); const extensionDir = path.join( process.cwd(), '.gemini/extensions/test-ext', @@ -485,7 +486,7 @@ describe('FileCommandLoader', () => { path: extensionDir, }, ]), - } as unknown as Config; + } as Config; const loader = new FileCommandLoader(mockConfig); const commands = await loader.loadCommands(signal); @@ -499,8 +500,10 @@ describe('FileCommandLoader', () => { }); it('extension commands have extensionName metadata for conflict resolution', async () => { - const userCommandsDir = getUserCommandsDir(); - const projectCommandsDir = getProjectCommandsDir(process.cwd()); + const userCommandsDir = Storage.getUserCommandsDir(); + const projectCommandsDir = new Storage( + process.cwd(), + ).getProjectCommandsDir(); const extensionDir = path.join( process.cwd(), '.gemini/extensions/test-ext', @@ -534,7 +537,7 @@ describe('FileCommandLoader', () => { path: extensionDir, }, ]), - } as unknown as Config; + } as Config; const loader = new FileCommandLoader(mockConfig); const commands = await loader.loadCommands(signal); @@ -641,7 +644,7 @@ describe('FileCommandLoader', () => { path: extensionDir2, }, ]), - } as unknown as Config; + } as Config; const loader = new FileCommandLoader(mockConfig); const commands = await loader.loadCommands(signal); @@ -677,7 +680,7 @@ describe('FileCommandLoader', () => { path: extensionDir, }, ]), - } as unknown as Config; + } as Config; const loader = new FileCommandLoader(mockConfig); const commands = await loader.loadCommands(signal); expect(commands).toHaveLength(0); @@ -709,7 +712,7 @@ describe('FileCommandLoader', () => { getExtensions: vi.fn(() => [ { name: 'a', version: '1.0.0', isActive: true, path: extensionDir }, ]), - } as unknown as Config; + } as Config; const loader = new FileCommandLoader(mockConfig); const commands = await loader.loadCommands(signal); @@ -742,7 +745,7 @@ describe('FileCommandLoader', () => { describe('Argument Handling Integration (via ShellProcessor)', () => { it('correctly processes a command with {{args}}', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'shorthand.toml': @@ -774,7 +777,7 @@ describe('FileCommandLoader', () => { describe('Default Argument Processor Integration', () => { it('correctly processes a command without {{args}}', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'model_led.toml': @@ -808,7 +811,7 @@ describe('FileCommandLoader', () => { describe('Shell Processor Integration', () => { it('instantiates ShellProcessor if {{args}} is present (even without shell trigger)', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'args_only.toml': `prompt = "Hello {{args}}"`, @@ -821,7 +824,7 @@ describe('FileCommandLoader', () => { expect(ShellProcessor).toHaveBeenCalledWith('args_only'); }); it('instantiates ShellProcessor if the trigger is present', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'shell.toml': `prompt = "Run this: ${SHELL_INJECTION_TRIGGER}echo hello}"`, @@ -835,7 +838,7 @@ describe('FileCommandLoader', () => { }); it('does not instantiate ShellProcessor if no triggers ({{args}} or !{}) are present', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'regular.toml': `prompt = "Just a regular prompt"`, @@ -849,7 +852,7 @@ describe('FileCommandLoader', () => { }); it('returns a "submit_prompt" action if shell processing succeeds', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'shell.toml': `prompt = "Run !{echo 'hello'}"`, @@ -876,7 +879,7 @@ describe('FileCommandLoader', () => { }); it('returns a "confirm_shell_commands" action if shell processing requires it', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); const rawInvocation = '/shell rm -rf /'; mock({ [userCommandsDir]: { @@ -910,7 +913,7 @@ describe('FileCommandLoader', () => { }); it('re-throws other errors from the processor', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { 'shell.toml': `prompt = "Run !{something}"`, @@ -935,7 +938,7 @@ describe('FileCommandLoader', () => { ).rejects.toThrow('Something else went wrong'); }); it('assembles the processor pipeline in the correct order (Shell -> Default)', async () => { - const userCommandsDir = getUserCommandsDir(); + const userCommandsDir = Storage.getUserCommandsDir(); mock({ [userCommandsDir]: { // This prompt uses !{} but NOT {{args}}, so both processors should be active. |
