Skip to main content
Glama
gemini-profile.integration.test.ts13.2 kB
/** * @fileoverview Integration Tests for GeminiProfile * Tests actual filesystem operations for adding and removing slash commands. */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; import { GeminiProfile } from '../../src/slash-commands/profiles/gemini-profile.js'; import { staticCommand, dynamicCommand } from '../../src/slash-commands/factories.js'; describe('GeminiProfile Integration Tests', () => { let tempDir: string; let profile: GeminiProfile; beforeEach(() => { // Create temporary directory for each test tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-profile-test-')); profile = new GeminiProfile(); }); afterEach(() => { // Clean up temporary directory after each test if (fs.existsSync(tempDir)) { fs.rmSync(tempDir, { recursive: true, force: true }); } }); describe('addSlashCommands()', () => { it('should create the .gemini/commands directory', () => { // Arrange const commands = [ staticCommand({ name: 'help', description: 'Show help', content: '# Help Content' }) ]; // Act const result = profile.addSlashCommands(tempDir, commands); // Assert expect(result.success).toBe(true); const commandsDir = path.join(tempDir, '.gemini/commands'); expect(fs.existsSync(commandsDir)).toBe(true); expect(fs.statSync(commandsDir).isDirectory()).toBe(true); }); it('should write files with Python-style format (description="...", prompt = """...""")', () => { // Arrange const commands = [ staticCommand({ name: 'test', description: 'Test description', content: '# Test Content' }) ]; // Act const result = profile.addSlashCommands(tempDir, commands); // Assert expect(result.success).toBe(true); const filePath = path.join(tempDir, '.gemini/commands/tm/test.toml'); expect(fs.existsSync(filePath)).toBe(true); const fileContent = fs.readFileSync(filePath, 'utf-8'); expect(fileContent).toBe( 'description="Test description"\nprompt = """\n# Test Content\n"""\n' ); }); it('should return success result with correct count', () => { // Arrange const commands = [ staticCommand({ name: 'help', description: 'Show help', content: '# Help' }), staticCommand({ name: 'deploy', description: 'Deploy app', content: '# Deploy' }), dynamicCommand( 'review', 'Review PR', '<pr-number>', '# Review\n\nPR: $ARGUMENTS' ) ]; // Act const result = profile.addSlashCommands(tempDir, commands); // Assert expect(result.success).toBe(true); expect(result.count).toBe(3); expect(result.files).toHaveLength(3); expect(result.files).toContain('help.toml'); expect(result.files).toContain('deploy.toml'); expect(result.files).toContain('review.toml'); expect(result.directory).toBe(path.join(tempDir, '.gemini/commands/tm')); }); it('should properly escape double quotes in description', () => { // Arrange const commands = [ staticCommand({ name: 'test', description: 'Test "quoted" description', content: '# Test Content' }) ]; // Act const result = profile.addSlashCommands(tempDir, commands); // Assert expect(result.success).toBe(true); const filePath = path.join(tempDir, '.gemini/commands/tm/test.toml'); const fileContent = fs.readFileSync(filePath, 'utf-8'); expect(fileContent).toContain( 'description="Test \\"quoted\\" description"' ); }); it('should handle multiple commands with different types', () => { // Arrange const commands = [ staticCommand({ name: 'static-cmd', description: 'Static command', content: '# Static Content\n\nThis is static.' }), dynamicCommand( 'dynamic-cmd', 'Dynamic command', '<arg>', '# Dynamic Content\n\nArgument: $ARGUMENTS' ) ]; // Act const result = profile.addSlashCommands(tempDir, commands); // Assert expect(result.success).toBe(true); expect(result.count).toBe(2); // Verify static command const staticFilePath = path.join( tempDir, '.gemini/commands/tm/static-cmd.toml' ); const staticContent = fs.readFileSync(staticFilePath, 'utf-8'); expect(staticContent).toContain('description="Static command"'); expect(staticContent).toContain('# Static Content'); expect(staticContent).not.toContain('$ARGUMENTS'); // Verify dynamic command const dynamicFilePath = path.join( tempDir, '.gemini/commands/tm/dynamic-cmd.toml' ); const dynamicContent = fs.readFileSync(dynamicFilePath, 'utf-8'); expect(dynamicContent).toContain('description="Dynamic command"'); expect(dynamicContent).toContain('Argument: $ARGUMENTS'); }); it('should create directory recursively if parent directories do not exist', () => { // Arrange const commands = [ staticCommand({ name: 'test', description: 'Test', content: '# Test' }) ]; // Act const result = profile.addSlashCommands(tempDir, commands); // Assert expect(result.success).toBe(true); const commandsDir = path.join(tempDir, '.gemini/commands/tm'); expect(fs.existsSync(commandsDir)).toBe(true); }); it('should work when directory already exists', () => { // Arrange const commandsDir = path.join(tempDir, '.gemini/commands/tm'); fs.mkdirSync(commandsDir, { recursive: true }); const commands = [ staticCommand({ name: 'test', description: 'Test', content: '# Test' }) ]; // Act const result = profile.addSlashCommands(tempDir, commands); // Assert expect(result.success).toBe(true); expect(result.count).toBe(1); }); }); describe('removeSlashCommands()', () => { it('should remove only TaskMaster commands (preserves user files)', () => { // Arrange const commandsDir = path.join(tempDir, '.gemini/commands/tm'); fs.mkdirSync(commandsDir, { recursive: true }); // Add TaskMaster commands const tmCommands = [ staticCommand({ name: 'help', description: 'Show help', content: '# Help' }), staticCommand({ name: 'deploy', description: 'Deploy', content: '# Deploy' }) ]; profile.addSlashCommands(tempDir, tmCommands); // Add user's custom command const userFilePath = path.join(commandsDir, 'my-custom-command.toml'); fs.writeFileSync( userFilePath, 'description="My custom command"\nprompt = """\n# Custom\n"""\n' ); // Act const result = profile.removeSlashCommands(tempDir, tmCommands); // Assert expect(result.success).toBe(true); expect(result.count).toBe(2); expect(result.files).toContain('help.toml'); expect(result.files).toContain('deploy.toml'); // Verify TaskMaster commands are removed expect(fs.existsSync(path.join(commandsDir, 'help.toml'))).toBe(false); expect(fs.existsSync(path.join(commandsDir, 'deploy.toml'))).toBe(false); // Verify user's custom command is preserved expect(fs.existsSync(userFilePath)).toBe(true); }); it('should remove empty directory after cleanup', () => { // Arrange const commands = [ staticCommand({ name: 'test', description: 'Test', content: '# Test' }) ]; // Add commands first profile.addSlashCommands(tempDir, commands); const commandsDir = path.join(tempDir, '.gemini/commands/tm'); expect(fs.existsSync(commandsDir)).toBe(true); // Act const result = profile.removeSlashCommands(tempDir, commands); // Assert expect(result.success).toBe(true); expect(result.count).toBe(1); // Directory should be removed since it's empty expect(fs.existsSync(commandsDir)).toBe(false); }); it('should not remove directory if user files remain (removeEmptyDir=true)', () => { // Arrange const commandsDir = path.join(tempDir, '.gemini/commands/tm'); fs.mkdirSync(commandsDir, { recursive: true }); // Add TaskMaster command const tmCommands = [ staticCommand({ name: 'help', description: 'Help', content: '# Help' }) ]; profile.addSlashCommands(tempDir, tmCommands); // Add user's custom command const userFilePath = path.join(commandsDir, 'my-command.toml'); fs.writeFileSync( userFilePath, 'description="User"\nprompt = """\n# User\n"""\n' ); // Act const result = profile.removeSlashCommands(tempDir, tmCommands); // Assert expect(result.success).toBe(true); expect(result.count).toBe(1); // Directory should still exist because user file remains expect(fs.existsSync(commandsDir)).toBe(true); expect(fs.existsSync(userFilePath)).toBe(true); }); it('should not remove directory if removeEmptyDir=false', () => { // Arrange const commands = [ staticCommand({ name: 'test', description: 'Test', content: '# Test' }) ]; // Add command first profile.addSlashCommands(tempDir, commands); const commandsDir = path.join(tempDir, '.gemini/commands/tm'); // Act const result = profile.removeSlashCommands(tempDir, commands, false); // Assert expect(result.success).toBe(true); expect(result.count).toBe(1); // Directory should still exist because removeEmptyDir=false expect(fs.existsSync(commandsDir)).toBe(true); // Verify directory is empty const remainingFiles = fs.readdirSync(commandsDir); expect(remainingFiles).toHaveLength(0); }); it('should return success with count 0 if directory does not exist', () => { // Arrange const commands = [ staticCommand({ name: 'test', description: 'Test', content: '# Test' }) ]; // Act (directory doesn't exist) const result = profile.removeSlashCommands(tempDir, commands); // Assert expect(result.success).toBe(true); expect(result.count).toBe(0); expect(result.files).toHaveLength(0); }); it('should handle removing subset of commands', () => { // Arrange const commandsDir = path.join(tempDir, '.gemini/commands/tm'); fs.mkdirSync(commandsDir, { recursive: true }); const allCommands = [ staticCommand({ name: 'help', description: 'Help', content: '# Help' }), staticCommand({ name: 'deploy', description: 'Deploy', content: '# Deploy' }), staticCommand({ name: 'test', description: 'Test', content: '# Test' }) ]; // Add all commands profile.addSlashCommands(tempDir, allCommands); // Remove only 'help' and 'test' const commandsToRemove = [allCommands[0], allCommands[2]]; // Act const result = profile.removeSlashCommands(tempDir, commandsToRemove); // Assert expect(result.success).toBe(true); expect(result.count).toBe(2); expect(result.files).toContain('help.toml'); expect(result.files).toContain('test.toml'); // Verify removed commands expect(fs.existsSync(path.join(commandsDir, 'help.toml'))).toBe(false); expect(fs.existsSync(path.join(commandsDir, 'test.toml'))).toBe(false); // Verify 'deploy' remains expect(fs.existsSync(path.join(commandsDir, 'deploy.toml'))).toBe(true); }); it('should match commands case-insensitively', () => { // Arrange const commandsDir = path.join(tempDir, '.gemini/commands/tm'); fs.mkdirSync(commandsDir, { recursive: true }); // Create file with uppercase in name const upperFilePath = path.join(commandsDir, 'HELP.toml'); fs.writeFileSync( upperFilePath, 'description="Help"\nprompt = """\n# Help\n"""\n' ); const commands = [ staticCommand({ name: 'help', description: 'Help', content: '# Help' }) ]; // Act const result = profile.removeSlashCommands(tempDir, commands); // Assert expect(result.success).toBe(true); expect(result.count).toBe(1); expect(fs.existsSync(upperFilePath)).toBe(false); }); }); describe('Full workflow: add then remove', () => { it('should successfully add and then remove commands', () => { // Arrange const commands = [ staticCommand({ name: 'help', description: 'Show help', content: '# Help Content' }), dynamicCommand( 'review', 'Review PR', '<pr-number>', '# Review\n\nPR: $ARGUMENTS' ) ]; const commandsDir = path.join(tempDir, '.gemini/commands'); const tmDir = path.join(commandsDir, 'tm'); // Act - Add commands const addResult = profile.addSlashCommands(tempDir, commands); // Assert - Add worked expect(addResult.success).toBe(true); expect(addResult.count).toBe(2); expect(fs.existsSync(tmDir)).toBe(true); expect(fs.existsSync(path.join(tmDir, 'help.toml'))).toBe(true); expect(fs.existsSync(path.join(tmDir, 'review.toml'))).toBe(true); // Act - Remove commands const removeResult = profile.removeSlashCommands(tempDir, commands); // Assert - Remove worked expect(removeResult.success).toBe(true); expect(removeResult.count).toBe(2); // The tm subdirectory should be removed expect(fs.existsSync(tmDir)).toBe(false); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/eyaltoledano/claude-task-master'

If you have feedback or need assistance with the MCP directory API, please join our Discord server