diff options
Diffstat (limited to 'packages/cli/src/ui/commands/memoryCommand.test.ts')
| -rw-r--r-- | packages/cli/src/ui/commands/memoryCommand.test.ts | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/packages/cli/src/ui/commands/memoryCommand.test.ts b/packages/cli/src/ui/commands/memoryCommand.test.ts new file mode 100644 index 00000000..47d098b1 --- /dev/null +++ b/packages/cli/src/ui/commands/memoryCommand.test.ts @@ -0,0 +1,249 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { vi, describe, it, expect, beforeEach, Mock } from 'vitest'; +import { memoryCommand } from './memoryCommand.js'; +import { type CommandContext, SlashCommand } from './types.js'; +import { createMockCommandContext } from '../../test-utils/mockCommandContext.js'; +import { MessageType } from '../types.js'; +import { getErrorMessage } from '@google/gemini-cli-core'; + +vi.mock('@google/gemini-cli-core', async (importOriginal) => { + const original = + await importOriginal<typeof import('@google/gemini-cli-core')>(); + return { + ...original, + getErrorMessage: vi.fn((error: unknown) => { + if (error instanceof Error) return error.message; + return String(error); + }), + }; +}); + +describe('memoryCommand', () => { + let mockContext: CommandContext; + + const getSubCommand = (name: 'show' | 'add' | 'refresh'): SlashCommand => { + const subCommand = memoryCommand.subCommands?.find( + (cmd) => cmd.name === name, + ); + if (!subCommand) { + throw new Error(`/memory ${name} command not found.`); + } + return subCommand; + }; + + describe('/memory show', () => { + let showCommand: SlashCommand; + let mockGetUserMemory: Mock; + let mockGetGeminiMdFileCount: Mock; + + beforeEach(() => { + showCommand = getSubCommand('show'); + + mockGetUserMemory = vi.fn(); + mockGetGeminiMdFileCount = vi.fn(); + + mockContext = createMockCommandContext({ + services: { + config: { + getUserMemory: mockGetUserMemory, + getGeminiMdFileCount: mockGetGeminiMdFileCount, + }, + }, + }); + }); + + it('should display a message if memory is empty', async () => { + if (!showCommand.action) throw new Error('Command has no action'); + + mockGetUserMemory.mockReturnValue(''); + mockGetGeminiMdFileCount.mockReturnValue(0); + + await showCommand.action(mockContext, ''); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + { + type: MessageType.INFO, + text: 'Memory is currently empty.', + }, + expect.any(Number), + ); + }); + + it('should display the memory content and file count if it exists', async () => { + if (!showCommand.action) throw new Error('Command has no action'); + + const memoryContent = 'This is a test memory.'; + + mockGetUserMemory.mockReturnValue(memoryContent); + mockGetGeminiMdFileCount.mockReturnValue(1); + + await showCommand.action(mockContext, ''); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + { + type: MessageType.INFO, + text: `Current memory content from 1 file(s):\n\n---\n${memoryContent}\n---`, + }, + expect.any(Number), + ); + }); + }); + + describe('/memory add', () => { + let addCommand: SlashCommand; + + beforeEach(() => { + addCommand = getSubCommand('add'); + mockContext = createMockCommandContext(); + }); + + it('should return an error message if no arguments are provided', () => { + if (!addCommand.action) throw new Error('Command has no action'); + + const result = addCommand.action(mockContext, ' '); + expect(result).toEqual({ + type: 'message', + messageType: 'error', + content: 'Usage: /memory add <text to remember>', + }); + + expect(mockContext.ui.addItem).not.toHaveBeenCalled(); + }); + + it('should return a tool action and add an info message when arguments are provided', () => { + if (!addCommand.action) throw new Error('Command has no action'); + + const fact = 'remember this'; + const result = addCommand.action(mockContext, ` ${fact} `); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + { + type: MessageType.INFO, + text: `Attempting to save to memory: "${fact}"`, + }, + expect.any(Number), + ); + + expect(result).toEqual({ + type: 'tool', + toolName: 'save_memory', + toolArgs: { fact }, + }); + }); + }); + + describe('/memory refresh', () => { + let refreshCommand: SlashCommand; + let mockRefreshMemory: Mock; + + beforeEach(() => { + refreshCommand = getSubCommand('refresh'); + mockRefreshMemory = vi.fn(); + mockContext = createMockCommandContext({ + services: { + config: { + refreshMemory: mockRefreshMemory, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + }, + }); + }); + + it('should display success message when memory is refreshed with content', async () => { + if (!refreshCommand.action) throw new Error('Command has no action'); + + const refreshResult = { + memoryContent: 'new memory content', + fileCount: 2, + }; + mockRefreshMemory.mockResolvedValue(refreshResult); + + await refreshCommand.action(mockContext, ''); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + { + type: MessageType.INFO, + text: 'Refreshing memory from source files...', + }, + expect.any(Number), + ); + + expect(mockRefreshMemory).toHaveBeenCalledOnce(); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + { + type: MessageType.INFO, + text: 'Memory refreshed successfully. Loaded 18 characters from 2 file(s).', + }, + expect.any(Number), + ); + }); + + it('should display success message when memory is refreshed with no content', async () => { + if (!refreshCommand.action) throw new Error('Command has no action'); + + const refreshResult = { memoryContent: '', fileCount: 0 }; + mockRefreshMemory.mockResolvedValue(refreshResult); + + await refreshCommand.action(mockContext, ''); + + expect(mockRefreshMemory).toHaveBeenCalledOnce(); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + { + type: MessageType.INFO, + text: 'Memory refreshed successfully. No memory content found.', + }, + expect.any(Number), + ); + }); + + it('should display an error message if refreshing fails', async () => { + if (!refreshCommand.action) throw new Error('Command has no action'); + + const error = new Error('Failed to read memory files.'); + mockRefreshMemory.mockRejectedValue(error); + + await refreshCommand.action(mockContext, ''); + + expect(mockRefreshMemory).toHaveBeenCalledOnce(); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + { + type: MessageType.ERROR, + text: `Error refreshing memory: ${error.message}`, + }, + expect.any(Number), + ); + + expect(getErrorMessage).toHaveBeenCalledWith(error); + }); + + it('should not throw if config service is unavailable', async () => { + if (!refreshCommand.action) throw new Error('Command has no action'); + + const nullConfigContext = createMockCommandContext({ + services: { config: null }, + }); + + await expect( + refreshCommand.action(nullConfigContext, ''), + ).resolves.toBeUndefined(); + + expect(nullConfigContext.ui.addItem).toHaveBeenCalledWith( + { + type: MessageType.INFO, + text: 'Refreshing memory from source files...', + }, + expect.any(Number), + ); + + expect(mockRefreshMemory).not.toHaveBeenCalled(); + }); + }); +}); |
