diff --git a/src/commands/config-image-analysis-command.ts b/src/commands/config-image-analysis-command.ts index 0301cfc6..ba9f855d 100644 --- a/src/commands/config-image-analysis-command.ts +++ b/src/commands/config-image-analysis-command.ts @@ -40,6 +40,13 @@ const IMAGE_ANALYSIS_PROVIDER_ALIASES = Object.freeze( ) ); +function isConfiguredImageAnalysisBackend( + backend: string | null, + providerModels: Record +): backend is string { + return Boolean(backend && Object.prototype.hasOwnProperty.call(providerModels, backend)); +} + function parseArgs(args: string[]): ImageAnalysisCommandOptions { const options: ImageAnalysisCommandOptions = { enable: hasAnyFlag(args, ['--enable']), @@ -296,7 +303,7 @@ export async function handleConfigImageAnalysisCommand(args: string[]): Promise< options.setFallback, Object.keys(imageConfig.provider_models) ); - if (!normalizedBackend) { + if (!isConfiguredImageAnalysisBackend(normalizedBackend, imageConfig.provider_models)) { console.error(fail(`Invalid fallback backend: ${options.setFallback}`)); process.exit(1); } @@ -314,7 +321,7 @@ export async function handleConfigImageAnalysisCommand(args: string[]): Promise< console.error(fail('Profile name cannot be empty')); process.exit(1); } - if (!normalizedBackend) { + if (!isConfiguredImageAnalysisBackend(normalizedBackend, imageConfig.provider_models)) { console.error(fail(`Invalid backend: ${options.setProfileBackend.backend}`)); process.exit(1); } diff --git a/tests/unit/commands/config-image-analysis-command.test.ts b/tests/unit/commands/config-image-analysis-command.test.ts index 6a7f9134..3215964e 100644 --- a/tests/unit/commands/config-image-analysis-command.test.ts +++ b/tests/unit/commands/config-image-analysis-command.test.ts @@ -4,7 +4,7 @@ * Unit tests for ccs config image-analysis subcommand. */ -import { describe, it, expect, beforeEach, afterEach, spyOn, mock } from 'bun:test'; +import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; @@ -33,6 +33,13 @@ function createConfigYaml(content: string): void { fs.writeFileSync(path.join(testDir, 'config.yaml'), content, 'utf8'); } +async function loadHandleConfigImageAnalysisCommand() { + const mod = await import( + `../../../src/commands/config-image-analysis-command?test=${Date.now()}-${Math.random()}` + ); + return mod.handleConfigImageAnalysisCommand; +} + describe('config image-analysis command', () => { describe('config file parsing', () => { it('should parse enabled status from config.yaml', () => { @@ -152,6 +159,56 @@ image_analysis: expect(validProviders.includes(provider)).toBe(false); } }); + + it('rejects invalid fallback backends that are not configured', async () => { + const handleConfigImageAnalysisCommand = await loadHandleConfigImageAnalysisCommand(); + const originalProcessExit = process.exit; + + process.exit = ((code?: number) => { + throw new Error(`process.exit(${code ?? 0})`); + }) as typeof process.exit; + + try { + await expect( + handleConfigImageAnalysisCommand(['--set-fallback', 'unknown-provider']) + ).rejects.toThrow('process.exit(1)'); + } finally { + process.exit = originalProcessExit; + } + + const configPath = path.join(testDir, 'config.yaml'); + if (fs.existsSync(configPath)) { + const content = fs.readFileSync(configPath, 'utf8'); + expect(content).not.toContain('fallback_backend: unknown-provider'); + } else { + expect(fs.existsSync(configPath)).toBe(false); + } + }); + + it('rejects invalid profile backend mappings that are not configured', async () => { + const handleConfigImageAnalysisCommand = await loadHandleConfigImageAnalysisCommand(); + const originalProcessExit = process.exit; + + process.exit = ((code?: number) => { + throw new Error(`process.exit(${code ?? 0})`); + }) as typeof process.exit; + + try { + await expect( + handleConfigImageAnalysisCommand(['--set-profile-backend', 'orq', 'unknown-provider']) + ).rejects.toThrow('process.exit(1)'); + } finally { + process.exit = originalProcessExit; + } + + const configPath = path.join(testDir, 'config.yaml'); + if (fs.existsSync(configPath)) { + const content = fs.readFileSync(configPath, 'utf8'); + expect(content).not.toContain('unknown-provider'); + } else { + expect(fs.existsSync(configPath)).toBe(false); + } + }); }); describe('default configuration', () => {