basant307/AI_Governance_Project
048
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7import { describe, it, expect, vi, beforeEach } from 'vitest';8import { settingsCommand } from './settings.js';9import yargs from 'yargs';10 11const mockGetLoadedExtensions = vi.hoisted(() => vi.fn());12const mockGetScopedEnvContents = vi.hoisted(() => vi.fn());13const mockUpdateSetting = vi.hoisted(() => vi.fn());14const mockPromptForSetting = vi.hoisted(() => vi.fn());15const mockWriteStdoutLine = vi.hoisted(() => vi.fn());16 17vi.mock('../../utils/stdioHelpers.js', () => ({18 writeStdoutLine: mockWriteStdoutLine,19 writeStderrLine: vi.fn(),20 clearScreen: vi.fn(),21}));22 23vi.mock('./utils.js', () => ({24 getExtensionManager: vi.fn().mockResolvedValue({25 getLoadedExtensions: mockGetLoadedExtensions,26 }),27}));28 29vi.mock('@qwen-code/qwen-code-core', () => ({30 ExtensionSettingScope: {31 USER: 'user',32 WORKSPACE: 'workspace',33 },34 getScopedEnvContents: mockGetScopedEnvContents,35 promptForSetting: mockPromptForSetting,36 updateSetting: mockUpdateSetting,37}));38 39describe('extensions settings command', () => {40 it('should fail if no subcommand is provided', () => {41 const validationParser = yargs([])42 .command(settingsCommand)43 .fail(false)44 .locale('en');45 expect(() => validationParser.parse('settings')).toThrow(46 'Not enough non-option arguments: got 0, need at least 1',47 );48 });49 50 it('should register set subcommand', () => {51 const parser = yargs([]).command(settingsCommand).fail(false).locale('en');52 expect(() => parser.parse('settings set')).toThrow(53 'Not enough non-option arguments',54 );55 });56 57 it('should register list subcommand', () => {58 const parser = yargs([]).command(settingsCommand).fail(false).locale('en');59 expect(() => parser.parse('settings list')).toThrow(60 'Not enough non-option arguments',61 );62 });63 64 it('should accept set command with name and setting', () => {65 const parser = yargs([]).command(settingsCommand).fail(false).locale('en');66 expect(() =>67 parser.parse('settings set my-extension API_KEY'),68 ).not.toThrow();69 });70 71 it('should accept set command with scope option', () => {72 const parser = yargs([]).command(settingsCommand).fail(false).locale('en');73 expect(() =>74 parser.parse('settings set my-extension API_KEY --scope=workspace'),75 ).not.toThrow();76 });77 78 it('should fail set command with invalid scope', () => {79 const parser = yargs([]).command(settingsCommand).fail(false).locale('en');80 expect(() =>81 parser.parse('settings set my-extension API_KEY --scope=invalid'),82 ).toThrow();83 });84 85 it('should accept list command with name', () => {86 const parser = yargs([]).command(settingsCommand).fail(false).locale('en');87 expect(() => parser.parse('settings list my-extension')).not.toThrow();88 });89});90 91describe('settings set handler', () => {92 beforeEach(() => {93 vi.clearAllMocks();94 });95 96 it('should return early if extension manager is not available', async () => {97 const { getExtensionManager } = await import('./utils.js');98 vi.mocked(getExtensionManager).mockResolvedValueOnce(null as never);99 100 const parser = yargs([]).command(settingsCommand);101 await parser.parseAsync('settings set my-extension API_KEY');102 103 expect(mockUpdateSetting).not.toHaveBeenCalled();104 });105 106 it('should return early if no extensions are loaded', async () => {107 mockGetLoadedExtensions.mockReturnValueOnce([]);108 109 const parser = yargs([]).command(settingsCommand);110 await parser.parseAsync('settings set my-extension API_KEY');111 112 expect(mockUpdateSetting).not.toHaveBeenCalled();113 });114 115 it('should log error if extension is not found', async () => {116 mockGetLoadedExtensions.mockReturnValueOnce([117 { name: 'other-extension', id: 'other-id', config: {} },118 ]);119 120 const parser = yargs([]).command(settingsCommand);121 await parser.parseAsync('settings set my-extension API_KEY');122 123 expect(mockWriteStdoutLine).toHaveBeenCalledWith(124 'Extension "my-extension" not found.',125 );126 expect(mockUpdateSetting).not.toHaveBeenCalled();127 });128 129 it('should call updateSetting with correct arguments for user scope', async () => {130 const mockExtension = {131 name: 'my-extension',132 id: 'ext-id-123',133 config: { name: 'my-extension', settings: [] },134 };135 mockGetLoadedExtensions.mockReturnValueOnce([mockExtension]);136 137 const parser = yargs([]).command(settingsCommand);138 await parser.parseAsync('settings set my-extension API_KEY');139 140 expect(mockUpdateSetting).toHaveBeenCalledWith(141 mockExtension.config,142 mockExtension.id,143 'API_KEY',144 mockPromptForSetting,145 'user',146 );147 });148 149 it('should call updateSetting with workspace scope when specified', async () => {150 const mockExtension = {151 name: 'my-extension',152 id: 'ext-id-123',153 config: { name: 'my-extension', settings: [] },154 };155 mockGetLoadedExtensions.mockReturnValueOnce([mockExtension]);156 157 const parser = yargs([]).command(settingsCommand);158 await parser.parseAsync(159 'settings set my-extension API_KEY --scope=workspace',160 );161 162 expect(mockUpdateSetting).toHaveBeenCalledWith(163 mockExtension.config,164 mockExtension.id,165 'API_KEY',166 mockPromptForSetting,167 'workspace',168 );169 });170});171 172describe('settings list handler', () => {173 beforeEach(() => {174 vi.clearAllMocks();175 });176 177 it('should return early if extension manager is not available', async () => {178 const { getExtensionManager } = await import('./utils.js');179 vi.mocked(getExtensionManager).mockResolvedValueOnce(null as never);180 181 const parser = yargs([]).command(settingsCommand);182 await parser.parseAsync('settings list my-extension');183 184 expect(mockGetScopedEnvContents).not.toHaveBeenCalled();185 });186 187 it('should return early if no extensions are loaded', async () => {188 mockGetLoadedExtensions.mockReturnValueOnce([]);189 190 const parser = yargs([]).command(settingsCommand);191 await parser.parseAsync('settings list my-extension');192 193 expect(mockGetScopedEnvContents).not.toHaveBeenCalled();194 });195 196 it('should log error if extension is not found', async () => {197 mockGetLoadedExtensions.mockReturnValueOnce([198 { name: 'other-extension', id: 'other-id', config: {} },199 ]);200 201 const parser = yargs([]).command(settingsCommand);202 await parser.parseAsync('settings list my-extension');203 204 expect(mockWriteStdoutLine).toHaveBeenCalledWith(205 'Extension "my-extension" not found.',206 );207 });208 209 it('should log message if extension has no settings', async () => {210 const mockExtension = {211 name: 'my-extension',212 id: 'ext-id-123',213 config: { name: 'my-extension' },214 settings: [],215 };216 mockGetLoadedExtensions.mockReturnValueOnce([mockExtension]);217 218 const parser = yargs([]).command(settingsCommand);219 await parser.parseAsync('settings list my-extension');220 221 expect(mockWriteStdoutLine).toHaveBeenCalledWith(222 'Extension "my-extension" has no settings to configure.',223 );224 });225 226 it('should list settings with their values', async () => {227 const mockExtension = {228 name: 'my-extension',229 id: 'ext-id-123',230 config: { name: 'my-extension' },231 settings: [232 {233 name: 'API Key',234 envVar: 'API_KEY',235 description: 'Your API key',236 sensitive: false,237 },238 {239 name: 'Secret Token',240 envVar: 'SECRET_TOKEN',241 description: 'A secret token',242 sensitive: true,243 },244 ],245 };246 mockGetLoadedExtensions.mockReturnValueOnce([mockExtension]);247 mockGetScopedEnvContents248 .mockResolvedValueOnce({ API_KEY: 'my-api-key' }) // user scope249 .mockResolvedValueOnce({}); // workspace scope250 251 const parser = yargs([]).command(settingsCommand);252 await parser.parseAsync('settings list my-extension');253 254 expect(mockWriteStdoutLine).toHaveBeenCalledWith(255 'Settings for "my-extension":',256 );257 expect(mockWriteStdoutLine).toHaveBeenCalledWith('\n- API Key (API_KEY)');258 expect(mockWriteStdoutLine).toHaveBeenCalledWith(259 ' Description: Your API key',260 );261 expect(mockWriteStdoutLine).toHaveBeenCalledWith(262 ' Value: my-api-key (user)',263 );264 });265 266 it('should show workspace scope for workspace-scoped settings', async () => {267 const mockExtension = {268 name: 'my-extension',269 id: 'ext-id-123',270 config: { name: 'my-extension' },271 settings: [272 {273 name: 'API Key',274 envVar: 'API_KEY',275 description: 'Your API key',276 sensitive: false,277 },278 ],279 };280 mockGetLoadedExtensions.mockReturnValueOnce([mockExtension]);281 mockGetScopedEnvContents282 .mockResolvedValueOnce({ API_KEY: 'user-value' }) // user scope283 .mockResolvedValueOnce({ API_KEY: 'workspace-value' }); // workspace scope284 285 const parser = yargs([]).command(settingsCommand);286 await parser.parseAsync('settings list my-extension');287 288 // Workspace should override user, and show (workspace) scope289 expect(mockWriteStdoutLine).toHaveBeenCalledWith(290 ' Value: workspace-value (workspace)',291 );292 });293 294 it('should show [not set] for undefined settings', async () => {295 const mockExtension = {296 name: 'my-extension',297 id: 'ext-id-123',298 config: { name: 'my-extension' },299 settings: [300 {301 name: 'API Key',302 envVar: 'API_KEY',303 description: 'Your API key',304 sensitive: false,305 },306 ],307 };308 mockGetLoadedExtensions.mockReturnValueOnce([mockExtension]);309 mockGetScopedEnvContents310 .mockResolvedValueOnce({}) // user scope311 .mockResolvedValueOnce({}); // workspace scope312 313 const parser = yargs([]).command(settingsCommand);314 await parser.parseAsync('settings list my-extension');315 316 expect(mockWriteStdoutLine).toHaveBeenCalledWith(' Value: [not set]');317 });318 319 it('should show [value stored in keychain] for sensitive settings', async () => {320 const mockExtension = {321 name: 'my-extension',322 id: 'ext-id-123',323 config: { name: 'my-extension' },324 settings: [325 {326 name: 'Secret Token',327 envVar: 'SECRET_TOKEN',328 description: 'A secret token',329 sensitive: true,330 },331 ],332 };333 mockGetLoadedExtensions.mockReturnValueOnce([mockExtension]);334 mockGetScopedEnvContents335 .mockResolvedValueOnce({ SECRET_TOKEN: 'secret-value' }) // user scope336 .mockResolvedValueOnce({}); // workspace scope337 338 const parser = yargs([]).command(settingsCommand);339 await parser.parseAsync('settings list my-extension');340 341 expect(mockWriteStdoutLine).toHaveBeenCalledWith(342 ' Value: [value stored in keychain] (user)',343 );344 });345});346 