GitNexus/gitnexus-web/test/unit/settings-service.test.ts
Octopus 0fa547ccdc
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Skill copy sync / shipped skills drift guard (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
feat: refresh MiniMax model and endpoint configuration (#2780)
2026-08-11 18:11:47 +00:00

246 lines
8.1 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
loadSettings,
saveSettings,
setActiveProvider,
getActiveProviderConfig,
isProviderConfigured,
clearSettings,
getProviderDisplayName,
getAvailableModels,
getProviderCapabilities,
} from '../../src/core/llm/settings-service';
import {
getMiniMaxModelCapabilities,
MINIMAX_ANTHROPIC_BASE_URLS,
MINIMAX_MODEL_IDS,
} from '../../src/core/llm/types';
import { createChatModel } from '../../src/core/llm/agent';
describe('loadSettings', () => {
it('returns defaults when nothing is stored', () => {
const settings = loadSettings();
expect(settings.activeProvider).toBeDefined();
expect(settings.openai).toBeDefined();
expect(settings.ollama).toBeDefined();
expect(settings.minimax).toMatchObject({
model: MINIMAX_MODEL_IDS[0],
baseUrl: MINIMAX_ANTHROPIC_BASE_URLS.global_en,
thinkingMode: 'adaptive',
});
});
it('merges stored values with defaults', () => {
sessionStorage.setItem(
'gitnexus-llm-settings',
JSON.stringify({
activeProvider: 'ollama',
ollama: { model: 'qwen3-coder:30b' },
}),
);
const settings = loadSettings();
expect(settings.activeProvider).toBe('ollama');
expect(settings.ollama.model).toBe('qwen3-coder:30b');
// Should still have other provider defaults
expect(settings.openai).toBeDefined();
});
it('migrates unsupported legacy MiniMax models to the current default', () => {
sessionStorage.setItem(
'gitnexus-llm-settings',
JSON.stringify({
activeProvider: 'minimax',
minimax: {
apiKey: 'minimax-test-key',
model: 'MiniMax-M2.5',
temperature: 0.1,
},
}),
);
const settings = loadSettings();
expect(settings.minimax).toMatchObject({
model: MINIMAX_MODEL_IDS[0],
thinkingMode: 'adaptive',
});
const model = createChatModel(getActiveProviderConfig()!) as any;
expect(model.model).toBe(MINIMAX_MODEL_IDS[0]);
expect(model.thinking).toEqual({ type: 'adaptive' });
});
it('returns defaults on corrupted JSON', () => {
sessionStorage.setItem('gitnexus-llm-settings', 'not-json{{{');
const settings = loadSettings();
expect(settings.activeProvider).toBeDefined();
});
it('migrates legacy localStorage to sessionStorage', () => {
localStorage.setItem(
'gitnexus-llm-settings',
JSON.stringify({
activeProvider: 'ollama',
ollama: { model: 'migrated-model' },
}),
);
const settings = loadSettings();
expect(settings.ollama.model).toBe('migrated-model');
expect(sessionStorage.getItem('gitnexus-llm-settings')).not.toBeNull();
expect(localStorage.getItem('gitnexus-llm-settings')).toBeNull();
});
});
describe('saveSettings / clearSettings', () => {
it('persists settings to sessionStorage', () => {
const settings = loadSettings();
settings.activeProvider = 'anthropic';
saveSettings(settings);
expect(loadSettings().activeProvider).toBe('anthropic');
});
it('clearSettings removes settings from both storages', () => {
saveSettings({ ...loadSettings(), activeProvider: 'anthropic' });
expect(sessionStorage.getItem('gitnexus-llm-settings')).not.toBeNull();
clearSettings();
expect(sessionStorage.getItem('gitnexus-llm-settings')).toBeNull();
expect(localStorage.getItem('gitnexus-llm-settings')).toBeNull();
});
});
describe('setActiveProvider', () => {
it('changes the active provider and persists', () => {
setActiveProvider('gemini');
expect(loadSettings().activeProvider).toBe('gemini');
});
});
describe('getActiveProviderConfig', () => {
it('returns null for unconfigured providers requiring API keys', () => {
setActiveProvider('openai');
expect(getActiveProviderConfig()).toBeNull();
});
it('returns config for ollama without API key', () => {
setActiveProvider('ollama');
const config = getActiveProviderConfig();
expect(config).not.toBeNull();
expect(config!.provider).toBe('ollama');
});
it('returns config for openai when API key is set', () => {
const settings = loadSettings();
settings.activeProvider = 'openai';
settings.openai = { ...settings.openai, apiKey: 'sk-test-123' };
saveSettings(settings);
const config = getActiveProviderConfig();
expect(config).not.toBeNull();
expect(config!.provider).toBe('openai');
});
it('returns config for deepseek when API key is set', () => {
const settings = loadSettings();
settings.activeProvider = 'deepseek';
settings.deepseek = { ...settings.deepseek, apiKey: 'sk-deepseek-123' };
saveSettings(settings);
const config = getActiveProviderConfig();
expect(config).not.toBeNull();
expect(config!.provider).toBe('deepseek');
});
it('returns the regional endpoint and thinking mode for MiniMax', () => {
const settings = loadSettings();
settings.activeProvider = 'minimax';
settings.minimax = {
...settings.minimax,
apiKey: 'minimax-test-key',
model: MINIMAX_MODEL_IDS[0],
baseUrl: MINIMAX_ANTHROPIC_BASE_URLS.cn_zh,
thinkingMode: 'disabled',
};
saveSettings(settings);
expect(getActiveProviderConfig()).toMatchObject({
provider: 'minimax',
model: MINIMAX_MODEL_IDS[0],
baseUrl: MINIMAX_ANTHROPIC_BASE_URLS.cn_zh,
thinkingMode: 'disabled',
});
});
it('returns null for openrouter with empty API key', () => {
const settings = loadSettings();
settings.activeProvider = 'openrouter';
settings.openrouter = { ...settings.openrouter, apiKey: ' ' };
saveSettings(settings);
expect(getActiveProviderConfig()).toBeNull();
});
});
describe('isProviderConfigured', () => {
it('returns false when provider requires API key and none is set', () => {
// Manually build a clean openai config with no API key
saveSettings({
...loadSettings(),
activeProvider: 'openai',
openai: { apiKey: '', model: 'gpt-4o', temperature: 0.1 },
});
expect(isProviderConfigured()).toBe(false);
});
it('returns true for ollama (no key required)', () => {
setActiveProvider('ollama');
expect(isProviderConfigured()).toBe(true);
});
});
describe('getProviderDisplayName', () => {
it('returns human-readable names', () => {
expect(getProviderDisplayName('openai')).toBe('OpenAI');
expect(getProviderDisplayName('azure-openai')).toBe('Azure OpenAI');
expect(getProviderDisplayName('gemini')).toBe('Google Gemini');
expect(getProviderDisplayName('anthropic')).toBe('Anthropic');
expect(getProviderDisplayName('ollama')).toBe('Ollama (Local)');
expect(getProviderDisplayName('openrouter')).toBe('OpenRouter');
expect(getProviderDisplayName('deepseek')).toBe('DeepSeek');
});
});
describe('getAvailableModels', () => {
it('returns models for known providers', () => {
expect(getAvailableModels('openai').length).toBeGreaterThan(0);
expect(getAvailableModels('ollama').length).toBeGreaterThan(0);
expect(getAvailableModels('anthropic')).toContain('claude-sonnet-4-20250514');
expect(getAvailableModels('deepseek')).toContain('deepseek-v4-flash');
expect(getAvailableModels('minimax')).toEqual([...MINIMAX_MODEL_IDS]);
});
it('describes MiniMax model input and thinking capabilities', () => {
expect(getMiniMaxModelCapabilities(MINIMAX_MODEL_IDS[0])).toEqual({
contextWindow: 1_000_000,
inputModalities: ['text', 'image', 'video'],
thinkingModes: ['adaptive', 'disabled'],
});
expect(getMiniMaxModelCapabilities(MINIMAX_MODEL_IDS[1])).toEqual({
contextWindow: 204_800,
inputModalities: ['text'],
thinkingModes: ['always_on'],
});
});
it('returns empty array for unknown provider', () => {
expect(getAvailableModels('unknown' as any)).toEqual([]);
});
});
describe('getProviderCapabilities', () => {
it('enables transcript replay only for providers that require it', () => {
expect(getProviderCapabilities('deepseek').preserveAssistantTranscript).toBe(true);
expect(getProviderCapabilities('openai').preserveAssistantTranscript).toBe(false);
expect(getProviderCapabilities('anthropic').preserveAssistantTranscript).toBe(false);
});
});