Files
ccs/tests/unit/utils/hooks/image-analysis-backend-resolver.test.ts
T

236 lines
6.8 KiB
TypeScript

import { describe, expect, it } from 'bun:test';
import {
DEFAULT_IMAGE_ANALYSIS_CONFIG,
type ImageAnalysisConfig,
} from '../../../../src/config/unified-config-types';
import {
canonicalizeImageAnalysisConfig,
resolveImageAnalysisStatus,
} from '../../../../src/utils/hooks/image-analysis-backend-resolver';
describe('image-analysis-backend-resolver', () => {
it('canonicalizes provider aliases in config', () => {
const config = canonicalizeImageAnalysisConfig({
enabled: true,
timeout: 60,
provider_models: {
copilot: 'claude-haiku-4.5',
gemini: 'gemini-2.5-flash',
},
fallback_backend: 'Gemini',
profile_backends: {
orq: 'copilot',
},
});
expect(config.provider_models.ghcp).toBe('claude-haiku-4.5');
expect(config.provider_models.copilot).toBeUndefined();
expect(config.fallback_backend).toBe('gemini');
expect(config.profile_backends?.orq).toBe('ghcp');
});
it('resolves copilot to the ghcp backend without a duplicate provider key', () => {
const status = resolveImageAnalysisStatus(
{
profileName: 'copilot',
profileType: 'copilot',
},
DEFAULT_IMAGE_ANALYSIS_CONFIG
);
expect(status.supported).toBe(true);
expect(status.backendId).toBe('ghcp');
expect(status.model).toBe('claude-haiku-4.5');
expect(status.resolutionSource).toBe('copilot-alias');
});
it('does not route cursor image analysis through the fallback backend by default', () => {
const status = resolveImageAnalysisStatus(
{
profileName: 'cursor',
profileType: 'cursor',
},
DEFAULT_IMAGE_ANALYSIS_CONFIG
);
expect(status.supported).toBe(false);
expect(status.backendId).toBeNull();
expect(status.status).toBe('skipped');
expect(status.resolutionSource).toBe('unresolved');
expect(status.reason).toContain('profile_backends.cursor');
});
it('allows cursor image analysis only when explicitly mapped to a backend', () => {
const config: ImageAnalysisConfig = {
...DEFAULT_IMAGE_ANALYSIS_CONFIG,
profile_backends: {
cursor: 'ghcp',
},
};
const status = resolveImageAnalysisStatus(
{
profileName: 'cursor',
profileType: 'cursor',
},
config
);
expect(status.supported).toBe(true);
expect(status.backendId).toBe('ghcp');
expect(status.status).toBe('mapped');
expect(status.resolutionSource).toBe('profile-backend');
});
it('treats cliproxy cursor as a provider-backed profile rather than a legacy bridge alias', () => {
const status = resolveImageAnalysisStatus(
{
profileName: 'cursor',
profileType: 'cliproxy',
cliproxyProvider: 'cursor',
},
DEFAULT_IMAGE_ANALYSIS_CONFIG
);
expect(status.backendId).toBe('cursor');
expect(status.resolutionSource).toBe('cliproxy-provider');
expect(status.reason).toContain('no image-analysis model configured');
});
it('uses the fallback backend for an unmapped third-party settings profile', () => {
const status = resolveImageAnalysisStatus(
{
profileName: 'glm',
profileType: 'settings',
settings: {
env: {
ANTHROPIC_BASE_URL: 'https://api.z.ai/v1',
ANTHROPIC_AUTH_TOKEN: 'glm-test-key',
},
},
},
DEFAULT_IMAGE_ANALYSIS_CONFIG
);
expect(status.supported).toBe(true);
expect(status.backendId).toBe('gemini');
expect(status.resolutionSource).toBe('fallback-backend');
expect(status.model).toBe('gemini-3-flash-preview');
});
it('keeps direct Anthropic settings profiles on native read unless explicitly mapped', () => {
const status = resolveImageAnalysisStatus(
{
profileName: 'claude-direct',
profileType: 'settings',
settings: {
env: {
ANTHROPIC_API_KEY: 'anthropic-test-key',
},
},
},
DEFAULT_IMAGE_ANALYSIS_CONFIG
);
expect(status.supported).toBe(false);
expect(status.backendId).toBeNull();
expect(status.status).toBe('skipped');
expect(status.shouldPersistHook).toBe(false);
expect(status.reason).toContain('native file access');
});
it('uses explicit profile_backends overrides for custom aliases', () => {
const config: ImageAnalysisConfig = {
...DEFAULT_IMAGE_ANALYSIS_CONFIG,
profile_backends: {
orq: 'copilot',
},
};
const status = resolveImageAnalysisStatus(
{
profileName: 'orq',
profileType: 'settings',
},
config
);
expect(status.supported).toBe(true);
expect(status.status).toBe('mapped');
expect(status.backendId).toBe('ghcp');
expect(status.resolutionSource).toBe('profile-backend');
});
it('lets explicit profile_backends overrides win over cliproxy provider inference', () => {
const config: ImageAnalysisConfig = {
...DEFAULT_IMAGE_ANALYSIS_CONFIG,
profile_backends: {
glmv: 'ghcp',
},
};
const status = resolveImageAnalysisStatus(
{
profileName: 'glmv',
profileType: 'cliproxy',
cliproxyProvider: 'gemini',
},
config
);
expect(status.supported).toBe(true);
expect(status.status).toBe('mapped');
expect(status.backendId).toBe('ghcp');
expect(status.resolutionSource).toBe('profile-backend');
});
it('reports hook-missing when the profile should persist a hook but it is absent', () => {
const status = resolveImageAnalysisStatus(
{
profileName: 'glm',
profileType: 'settings',
settings: {
env: {
ANTHROPIC_BASE_URL: 'https://api.z.ai/v1',
ANTHROPIC_AUTH_TOKEN: 'glm-test-key',
},
},
hookInstalled: false,
sharedHookInstalled: true,
},
DEFAULT_IMAGE_ANALYSIS_CONFIG
);
expect(status.status).toBe('hook-missing');
expect(status.reason).toContain('Profile hook is missing');
});
it('prefers native image reading when the profile settings opt into it', () => {
const status = resolveImageAnalysisStatus(
{
profileName: 'glmv',
profileType: 'settings',
settings: {
env: {
ANTHROPIC_BASE_URL: 'https://api.z.ai/v1',
ANTHROPIC_MODEL: 'glm-4.5v',
ANTHROPIC_AUTH_TOKEN: 'glm-test-key',
},
ccs_image: {
native_read: true,
},
},
},
DEFAULT_IMAGE_ANALYSIS_CONFIG
);
expect(status.backendId).toBeNull();
expect(status.resolutionSource).toBe('native-compatible');
expect(status.nativeReadPreference).toBe(true);
expect(status.profileModel).toBe('glm-4.5v');
expect(status.nativeImageCapable).toBe(true);
expect(status.shouldPersistHook).toBe(false);
expect(status.effectiveRuntimeMode).toBe('native-read');
});
});