Files
automaker/apps/server/tests/unit/lib/model-resolver.test.ts
Shirone b32eacc913 feat: Enhance model resolution for Cursor models
- Added support for Cursor models in the model resolver, allowing cursor-prefixed models to pass through unchanged.
- Implemented logic to handle bare Cursor model IDs by adding the cursor- prefix.
- Updated logging to provide detailed information on model resolution processes for both Claude and Cursor models.
- Expanded unit tests to cover new Cursor model handling scenarios, ensuring robust validation of model resolution logic.
2025-12-28 01:55:40 +01:00

167 lines
5.7 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
resolveModelString,
getEffectiveModel,
CLAUDE_MODEL_MAP,
CURSOR_MODEL_MAP,
DEFAULT_MODELS,
} from '@automaker/model-resolver';
describe('model-resolver.ts', () => {
let consoleSpy: any;
beforeEach(() => {
consoleSpy = {
log: vi.spyOn(console, 'log').mockImplementation(() => {}),
warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
};
});
afterEach(() => {
consoleSpy.log.mockRestore();
consoleSpy.warn.mockRestore();
});
describe('resolveModelString', () => {
it("should resolve 'haiku' alias to full model string", () => {
const result = resolveModelString('haiku');
expect(result).toBe(CLAUDE_MODEL_MAP.haiku);
});
it("should resolve 'sonnet' alias to full model string", () => {
const result = resolveModelString('sonnet');
expect(result).toBe(CLAUDE_MODEL_MAP.sonnet);
});
it("should resolve 'opus' alias to full model string", () => {
const result = resolveModelString('opus');
expect(result).toBe('claude-opus-4-5-20251101');
expect(consoleSpy.log).toHaveBeenCalledWith(
expect.stringContaining('Resolved Claude model alias: "opus"')
);
});
it('should treat unknown models as falling back to default', () => {
const models = ['o1', 'o1-mini', 'o3', 'gpt-5.2', 'unknown-model'];
models.forEach((model) => {
const result = resolveModelString(model);
// Should fall back to default since these aren't supported
expect(result).toBe(DEFAULT_MODELS.claude);
});
});
it('should pass through full Claude model strings', () => {
const models = [CLAUDE_MODEL_MAP.opus, CLAUDE_MODEL_MAP.sonnet, CLAUDE_MODEL_MAP.haiku];
models.forEach((model) => {
const result = resolveModelString(model);
expect(result).toBe(model);
});
expect(consoleSpy.log).toHaveBeenCalledWith(
expect.stringContaining('Using full Claude model string')
);
});
it('should return default model when modelKey is undefined', () => {
const result = resolveModelString(undefined);
expect(result).toBe(DEFAULT_MODELS.claude);
});
it('should return custom default model when provided', () => {
const customDefault = 'custom-model';
const result = resolveModelString(undefined, customDefault);
expect(result).toBe(customDefault);
});
it('should return default for unknown model key', () => {
const result = resolveModelString('unknown-model');
expect(result).toBe(DEFAULT_MODELS.claude);
expect(consoleSpy.warn).toHaveBeenCalledWith(
expect.stringContaining('Unknown model key "unknown-model"')
);
});
it('should handle empty string', () => {
const result = resolveModelString('');
expect(result).toBe(DEFAULT_MODELS.claude);
});
describe('Cursor models', () => {
it('should pass through cursor-prefixed models unchanged', () => {
const result = resolveModelString('cursor-composer-1');
expect(result).toBe('cursor-composer-1');
expect(consoleSpy.log).toHaveBeenCalledWith(expect.stringContaining('Using Cursor model'));
});
it('should add cursor- prefix to bare Cursor model IDs', () => {
const result = resolveModelString('composer-1');
expect(result).toBe('cursor-composer-1');
});
it('should handle cursor-auto model', () => {
const result = resolveModelString('cursor-auto');
expect(result).toBe('cursor-auto');
});
it('should handle all known Cursor model IDs with prefix', () => {
const cursorModelIds = Object.keys(CURSOR_MODEL_MAP);
cursorModelIds.forEach((modelId) => {
const result = resolveModelString(`cursor-${modelId}`);
expect(result).toBe(`cursor-${modelId}`);
});
});
});
});
describe('getEffectiveModel', () => {
it('should prioritize explicit model over session and default', () => {
const result = getEffectiveModel('opus', 'haiku', 'gpt-5.2');
expect(result).toBe('claude-opus-4-5-20251101');
});
it('should use session model when explicit is not provided', () => {
const result = getEffectiveModel(undefined, 'sonnet', 'gpt-5.2');
expect(result).toBe(CLAUDE_MODEL_MAP.sonnet);
});
it('should use default when neither explicit nor session is provided', () => {
const customDefault = CLAUDE_MODEL_MAP.haiku;
const result = getEffectiveModel(undefined, undefined, customDefault);
expect(result).toBe(customDefault);
});
it('should use Claude default when no arguments provided', () => {
const result = getEffectiveModel();
expect(result).toBe(DEFAULT_MODELS.claude);
});
it('should handle explicit empty strings as undefined', () => {
const result = getEffectiveModel('', 'haiku');
expect(result).toBe(CLAUDE_MODEL_MAP.haiku);
});
});
describe('CLAUDE_MODEL_MAP', () => {
it('should have haiku, sonnet, opus mappings', () => {
expect(CLAUDE_MODEL_MAP).toHaveProperty('haiku');
expect(CLAUDE_MODEL_MAP).toHaveProperty('sonnet');
expect(CLAUDE_MODEL_MAP).toHaveProperty('opus');
});
it('should have valid Claude model strings', () => {
expect(CLAUDE_MODEL_MAP.haiku).toContain('haiku');
expect(CLAUDE_MODEL_MAP.sonnet).toContain('sonnet');
expect(CLAUDE_MODEL_MAP.opus).toContain('opus');
});
});
describe('DEFAULT_MODELS', () => {
it('should have claude default', () => {
expect(DEFAULT_MODELS).toHaveProperty('claude');
});
it('should have valid default model', () => {
expect(DEFAULT_MODELS.claude).toContain('claude');
});
});
});