File size: 4,086 Bytes
7421850
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { Command } from './types.js';

const {
  mockExtensionsCommand,
  mockListExtensionsCommand,
  mockExtensionsCommandInstance,
  mockListExtensionsCommandInstance,
} = vi.hoisted(() => {
  const listInstance: Command = {
    name: 'extensions list',
    description: 'Lists all installed extensions.',
    execute: vi.fn(),
  };

  const extInstance: Command = {
    name: 'extensions',
    description: 'Manage extensions.',
    execute: vi.fn(),
    subCommands: [listInstance],
  };

  return {
    mockListExtensionsCommandInstance: listInstance,
    mockExtensionsCommandInstance: extInstance,
    mockExtensionsCommand: vi.fn(() => extInstance),
    mockListExtensionsCommand: vi.fn(() => listInstance),
  };
});

vi.mock('./extensions.js', () => ({
  ExtensionsCommand: mockExtensionsCommand,
  ListExtensionsCommand: mockListExtensionsCommand,
}));

vi.mock('./init.js', () => ({
  InitCommand: vi.fn(() => ({
    name: 'init',
    description: 'Initializes the server.',
    execute: vi.fn(),
  })),
}));

vi.mock('./restore.js', () => ({
  RestoreCommand: vi.fn(() => ({
    name: 'restore',
    description: 'Restores the server.',
    execute: vi.fn(),
  })),
}));

import { commandRegistry } from './command-registry.js';

describe('CommandRegistry', () => {
  beforeEach(async () => {
    vi.clearAllMocks();
    commandRegistry.initialize();
  });

  it('should register ExtensionsCommand on initialization', async () => {
    expect(mockExtensionsCommand).toHaveBeenCalled();
    const command = commandRegistry.get('extensions');
    expect(command).toBe(mockExtensionsCommandInstance);
  }, 20000);

  it('should register sub commands on initialization', async () => {
    const command = commandRegistry.get('extensions list');
    expect(command).toBe(mockListExtensionsCommandInstance);
  });

  it('get() should return undefined for a non-existent command', async () => {
    const command = commandRegistry.get('non-existent');
    expect(command).toBeUndefined();
  });

  it('register() should register a new command', async () => {
    const mockCommand: Command = {
      name: 'test-command',
      description: '',
      execute: vi.fn(),
    };
    commandRegistry.register(mockCommand);
    const command = commandRegistry.get('test-command');
    expect(command).toBe(mockCommand);
  });

  it('register() should register a nested command', async () => {
    const mockSubSubCommand: Command = {
      name: 'test-command-sub-sub',
      description: '',
      execute: vi.fn(),
    };
    const mockSubCommand: Command = {
      name: 'test-command-sub',
      description: '',
      execute: vi.fn(),
      subCommands: [mockSubSubCommand],
    };
    const mockCommand: Command = {
      name: 'test-command',
      description: '',
      execute: vi.fn(),
      subCommands: [mockSubCommand],
    };
    commandRegistry.register(mockCommand);

    const command = commandRegistry.get('test-command');
    const subCommand = commandRegistry.get('test-command-sub');
    const subSubCommand = commandRegistry.get('test-command-sub-sub');

    expect(command).toBe(mockCommand);
    expect(subCommand).toBe(mockSubCommand);
    expect(subSubCommand).toBe(mockSubSubCommand);
  });

  it('register() should not enter an infinite loop with a cyclic command', async () => {
    const { debugLogger } = await import('@google/gemini-cli-core');
    const warnSpy = vi.spyOn(debugLogger, 'warn').mockImplementation(() => {});
    const mockCommand: Command = {
      name: 'cyclic-command',
      description: '',
      subCommands: [],
      execute: vi.fn(),
    };

    mockCommand.subCommands?.push(mockCommand); // Create cycle

    commandRegistry.register(mockCommand);

    expect(commandRegistry.get('cyclic-command')).toBe(mockCommand);
    expect(warnSpy).toHaveBeenCalledWith(
      'Command cyclic-command already registered. Skipping.',
    );
    warnSpy.mockRestore();
  });
});