mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
* feat: configure prettier with pre-commit hook integration Add prettier, lint-staged, and prettier-plugin-tailwindcss at the repo root with husky pre-commit hook integration. Moves husky from gitnexus/ to root package.json for reliable hook installation. - Root package.json with prepare/format/format:check scripts - .prettierrc with endOfLine:lf and tailwindStylesheet for TW v4 - .prettierignore excluding fixtures, vendor, generated, *.d.ts, *.md - .gitattributes enforcing LF line endings for Windows consistency - Pre-commit hook uses direct node_modules/.bin/ paths (no npx) * style: apply prettier formatting to entire codebase One-time bulk format. No logic changes. Use .git-blame-ignore-revs to skip this commit in git blame. * chore: add .git-blame-ignore-revs for prettier format commit * perf: pre-commit hook runs only tests related to staged files Use vitest --related to scope test execution to tests that import the changed files, instead of running the full suite on every commit. * perf: remove vitest from pre-commit hook, keep in CI only Pre-commit now runs lint-staged + tsc only. Tests run in CI (ci-tests.yml) where they belong — keeps commits fast. * ci: add prettier format check to quality workflow PRs will now fail if code isn't formatted with prettier.
155 lines
5.1 KiB
TypeScript
155 lines
5.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
loadSettings,
|
|
saveSettings,
|
|
setActiveProvider,
|
|
getActiveProviderConfig,
|
|
isProviderConfigured,
|
|
clearSettings,
|
|
getProviderDisplayName,
|
|
getAvailableModels,
|
|
} from '../../src/core/llm/settings-service';
|
|
|
|
describe('loadSettings', () => {
|
|
it('returns defaults when nothing is stored', () => {
|
|
const settings = loadSettings();
|
|
expect(settings.activeProvider).toBeDefined();
|
|
expect(settings.openai).toBeDefined();
|
|
expect(settings.ollama).toBeDefined();
|
|
});
|
|
|
|
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('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 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');
|
|
});
|
|
});
|
|
|
|
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');
|
|
});
|
|
|
|
it('returns empty array for unknown provider', () => {
|
|
expect(getAvailableModels('unknown' as any)).toEqual([]);
|
|
});
|
|
});
|