basant307/AI_Governance_Project
045
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7vi.mock('../ui/commands/aboutCommand.js', async () => {8 const { CommandKind } = await import('../ui/commands/types.js');9 return {10 aboutCommand: {11 name: 'status',12 altNames: ['about'],13 description: 'About the CLI',14 kind: CommandKind.BUILT_IN,15 },16 };17});18 19vi.mock('../ui/commands/approvalModeCommand.js', () => ({20 approvalModeCommand: {21 name: 'approval-mode',22 description: 'Approval mode command',23 kind: 'built-in',24 },25}));26vi.mock('../ui/commands/cdCommand.js', () => ({27 cdCommand: {28 name: 'cd',29 description: 'Change directory command',30 kind: 'built-in',31 },32}));33 34vi.mock('../ui/commands/ideCommand.js', async () => {35 const { CommandKind } = await import('../ui/commands/types.js');36 return {37 ideCommand: vi.fn().mockResolvedValue({38 name: 'ide',39 description: 'IDE command',40 kind: CommandKind.BUILT_IN,41 }),42 };43});44vi.mock('../ui/commands/restoreCommand.js', () => ({45 restoreCommand: vi.fn(),46}));47vi.mock('../ui/commands/trustCommand.js', async () => {48 const { CommandKind } = await import('../ui/commands/types.js');49 return {50 trustCommand: {51 name: 'trust',52 description: 'Trust command',53 kind: CommandKind.BUILT_IN,54 },55 };56});57vi.mock('../ui/commands/permissionsCommand.js', async () => {58 const { CommandKind } = await import('../ui/commands/types.js');59 return {60 permissionsCommand: {61 name: 'permissions',62 description: 'Manage permission rules',63 kind: CommandKind.BUILT_IN,64 },65 };66});67 68vi.mock('../ui/commands/hooksCommand.js', async () => {69 const { CommandKind } = await import('../ui/commands/types.js');70 return {71 hooksCommand: {72 name: 'hooks',73 description: 'Hooks command',74 kind: CommandKind.BUILT_IN,75 },76 };77});78 79import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';80import { BuiltinCommandLoader } from './BuiltinCommandLoader.js';81import type { Config } from '@qwen-code/qwen-code-core';82import { CommandKind } from '../ui/commands/types.js';83 84import { restoreCommand } from '../ui/commands/restoreCommand.js';85 86vi.mock('../ui/commands/authCommand.js', () => ({ authCommand: {} }));87vi.mock('../ui/commands/bugCommand.js', () => ({ bugCommand: {} }));88vi.mock('../ui/commands/clearCommand.js', () => ({ clearCommand: {} }));89vi.mock('../ui/commands/compressCommand.js', () => ({ compressCommand: {} }));90vi.mock('../ui/commands/docsCommand.js', () => ({ docsCommand: {} }));91vi.mock('../ui/commands/exportCommand.js', () => ({ exportCommand: {} }));92vi.mock('../ui/commands/editorCommand.js', () => ({ editorCommand: {} }));93vi.mock('../ui/commands/extensionsCommand.js', () => ({94 extensionsCommand: {},95}));96vi.mock('../ui/commands/helpCommand.js', () => ({ helpCommand: {} }));97vi.mock('../ui/commands/memoryCommand.js', () => ({ memoryCommand: {} }));98vi.mock('../ui/commands/insightCommand.js', () => ({ insightCommand: {} }));99vi.mock('../ui/commands/modelCommand.js', () => ({100 modelCommand: { name: 'model' },101}));102vi.mock('../ui/commands/quitCommand.js', () => ({103 quitCommand: {},104}));105vi.mock('../ui/commands/statsCommand.js', () => ({ statsCommand: {} }));106vi.mock('../ui/commands/themeCommand.js', () => ({ themeCommand: {} }));107vi.mock('../ui/commands/toolsCommand.js', () => ({ toolsCommand: {} }));108vi.mock('../ui/commands/mcpCommand.js', () => ({109 mcpCommand: {110 name: 'mcp',111 description: 'MCP command',112 kind: 'BUILT_IN',113 },114}));115vi.mock('../ui/commands/modelCommand.js', () => ({116 modelCommand: {117 name: 'model',118 description: 'Model command',119 kind: 'BUILT_IN',120 },121}));122 123describe('BuiltinCommandLoader', () => {124 let mockConfig: Config;125 126 const restoreCommandMock = restoreCommand as Mock;127 128 beforeEach(() => {129 vi.clearAllMocks();130 mockConfig = {131 getFolderTrust: vi.fn().mockReturnValue(true),132 getUseModelRouter: () => false,133 getDisableAllHooks: vi.fn().mockReturnValue(false),134 isManagedMemoryAvailable: vi.fn().mockReturnValue(true),135 isLspEnabled: vi.fn().mockReturnValue(false),136 isWorkflowsEnabled: vi.fn().mockReturnValue(false),137 } as unknown as Config;138 139 restoreCommandMock.mockReturnValue({140 name: 'restore',141 description: 'Restore command',142 kind: CommandKind.BUILT_IN,143 });144 });145 146 it('should correctly pass the config object to restore command factory', async () => {147 const loader = new BuiltinCommandLoader(mockConfig);148 await loader.loadCommands(new AbortController().signal);149 150 // ideCommand is now a constant, no longer needs config151 expect(restoreCommandMock).toHaveBeenCalledTimes(1);152 expect(restoreCommandMock).toHaveBeenCalledWith(mockConfig);153 });154 155 it('should filter out null command definitions returned by factories', async () => {156 // ideCommand is now a constant SlashCommand157 const loader = new BuiltinCommandLoader(mockConfig);158 const commands = await loader.loadCommands(new AbortController().signal);159 160 // The 'ide' command should be present.161 const ideCmd = commands.find((c) => c.name === 'ide');162 expect(ideCmd).toBeDefined();163 164 // Other commands should still be present.165 const statusCmd = commands.find((c) => c.name === 'status');166 expect(statusCmd).toBeDefined();167 });168 169 it('should handle a null config gracefully when calling factories', async () => {170 const loader = new BuiltinCommandLoader(null);171 await loader.loadCommands(new AbortController().signal);172 // ideCommand is now a constant, no longer needs config173 expect(restoreCommandMock).toHaveBeenCalledTimes(1);174 expect(restoreCommandMock).toHaveBeenCalledWith(null);175 });176 177 it('should return a list of all loaded commands', async () => {178 const loader = new BuiltinCommandLoader(mockConfig);179 const commands = await loader.loadCommands(new AbortController().signal);180 181 const statusCmd = commands.find((c) => c.name === 'status');182 expect(statusCmd).toBeDefined();183 expect(statusCmd?.kind).toBe(CommandKind.BUILT_IN);184 185 const approvalModeCmd = commands.find((c) => c.name === 'approval-mode');186 expect(approvalModeCmd).toBeDefined();187 expect(approvalModeCmd?.kind).toBe(CommandKind.BUILT_IN);188 189 const cdCmd = commands.find((c) => c.name === 'cd');190 expect(cdCmd).toBeDefined();191 expect(cdCmd?.kind).toBe(CommandKind.BUILT_IN);192 193 const ideCmd = commands.find((c) => c.name === 'ide');194 expect(ideCmd).toBeDefined();195 196 const importConfigCmd = commands.find((c) => c.name === 'import-config');197 expect(importConfigCmd).toBeDefined();198 199 const mcpCmd = commands.find((c) => c.name === 'mcp');200 expect(mcpCmd).toBeDefined();201 202 const modelCmd = commands.find((c) => c.name === 'model');203 expect(modelCmd).toBeDefined();204 });205 206 it('should include trust command when folder trust is enabled', async () => {207 const loader = new BuiltinCommandLoader(mockConfig);208 const commands = await loader.loadCommands(new AbortController().signal);209 const trustCmd = commands.find((c) => c.name === 'trust');210 expect(trustCmd).toBeDefined();211 });212 213 it('should exclude trust command when folder trust is disabled', async () => {214 (mockConfig.getFolderTrust as Mock).mockReturnValue(false);215 const loader = new BuiltinCommandLoader(mockConfig);216 const commands = await loader.loadCommands(new AbortController().signal);217 const trustCmd = commands.find((c) => c.name === 'trust');218 expect(trustCmd).toBeUndefined();219 });220 221 it('should always include modelCommand', async () => {222 const loader = new BuiltinCommandLoader(mockConfig);223 const commands = await loader.loadCommands(new AbortController().signal);224 const modelCmd = commands.find((c) => c.name === 'model');225 expect(modelCmd).toBeDefined();226 expect(modelCmd?.name).toBe('model');227 });228 229 it('should always register the /fork command', async () => {230 const loader = new BuiltinCommandLoader(mockConfig);231 const commands = await loader.loadCommands(new AbortController().signal);232 const forkCmd = commands.find((c) => c.name === 'fork');233 expect(forkCmd).toBeDefined();234 expect(forkCmd?.kind).toBe(CommandKind.BUILT_IN);235 });236 237 it('should include lsp command only when LSP is enabled', async () => {238 const disabledLoader = new BuiltinCommandLoader(mockConfig);239 const disabledCommands = await disabledLoader.loadCommands(240 new AbortController().signal,241 );242 expect(disabledCommands.find((c) => c.name === 'lsp')).toBeUndefined();243 244 (mockConfig.isLspEnabled as Mock).mockReturnValue(true);245 const enabledLoader = new BuiltinCommandLoader(mockConfig);246 const enabledCommands = await enabledLoader.loadCommands(247 new AbortController().signal,248 );249 250 expect(enabledCommands.find((c) => c.name === 'lsp')).toBeDefined();251 });252 253 it('should still load all other commands when ideCommand() throws', async () => {254 // Simulate ideCommand() failure (e.g., platform-specific process detection fails)255 const { ideCommand: ideCommandMock } = await import(256 '../ui/commands/ideCommand.js'257 );258 (ideCommandMock as Mock).mockRejectedValueOnce(259 new Error('PowerShell not available'),260 );261 262 const loader = new BuiltinCommandLoader(mockConfig);263 const commands = await loader.loadCommands(new AbortController().signal);264 265 // IDE command should NOT be present266 const ideCmd = commands.find((c) => c.name === 'ide');267 expect(ideCmd).toBeUndefined();268 269 // But all other built-in commands should still be loaded270 const modelCmd = commands.find((c) => c.name === 'model');271 expect(modelCmd).toBeDefined();272 273 const statusCmd = commands.find((c) => c.name === 'status');274 expect(statusCmd).toBeDefined();275 276 const mcpCmd = commands.find((c) => c.name === 'mcp');277 expect(mcpCmd).toBeDefined();278 });279 280 it('should always include hooks command regardless of disableAllHooks', async () => {281 // When disableAllHooks is false282 const loader1 = new BuiltinCommandLoader(mockConfig);283 const commands1 = await loader1.loadCommands(new AbortController().signal);284 const hooksCmd1 = commands1.find((c) => c.name === 'hooks');285 expect(hooksCmd1).toBeDefined();286 287 // When disableAllHooks is true - hooks command should still be available288 // (it will show a disabled state page in the UI instead of hiding the command)289 (mockConfig.getDisableAllHooks as Mock).mockReturnValue(true);290 const loader2 = new BuiltinCommandLoader(mockConfig);291 const commands2 = await loader2.loadCommands(new AbortController().signal);292 const hooksCmd2 = commands2.find((c) => c.name === 'hooks');293 expect(hooksCmd2).toBeDefined();294 });295});296 