Initial import: open-design source for helix-mind.ai distribution
Some checks failed
ci / Validate workspace (push) Successful in 12m32s
landing-page-ci / Validate landing page (push) Successful in 9m41s
landing-page-deploy / Deploy landing page (push) Failing after 5m23s
github-metrics / Generate repository metrics SVG (push) Failing after 2m6s
refresh-contributors-wall / Refresh contributors wall cache bust (push) Failing after 12s
Some checks failed
ci / Validate workspace (push) Successful in 12m32s
landing-page-ci / Validate landing page (push) Successful in 9m41s
landing-page-deploy / Deploy landing page (push) Failing after 5m23s
github-metrics / Generate repository metrics SVG (push) Failing after 2m6s
refresh-contributors-wall / Refresh contributors wall cache bust (push) Failing after 12s
This repository contains the open-design daemon CLI source code, built and packaged at https://helix-mind.ai/cli/open-design/latest.tgz for use by the HelixMind /design slash command. Licenses: Apache-2.0 (root) + MIT (skills/*)
This commit is contained in:
225
apps/web/tests/components/SettingsDialog.test.ts
Normal file
225
apps/web/tests/components/SettingsDialog.test.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
agentRefreshOptionsForConfig,
|
||||
isValidApiBaseUrl,
|
||||
switchApiProtocolConfig,
|
||||
updateAgentCliEnvValue,
|
||||
updateCurrentApiProtocolConfig,
|
||||
} from '../../src/components/SettingsDialog';
|
||||
import type { AppConfig } from '../../src/types';
|
||||
|
||||
const baseConfig: AppConfig = {
|
||||
mode: 'api',
|
||||
apiKey: 'sk-test',
|
||||
apiProtocol: 'anthropic',
|
||||
baseUrl: 'https://api.anthropic.com',
|
||||
model: 'claude-sonnet-4-5',
|
||||
apiProviderBaseUrl: 'https://api.anthropic.com',
|
||||
agentId: null,
|
||||
skillId: null,
|
||||
designSystemId: null,
|
||||
};
|
||||
|
||||
describe('SettingsDialog API protocol switching', () => {
|
||||
it('stores the current custom protocol config while preserving custom endpoint details', () => {
|
||||
const config: AppConfig = {
|
||||
...baseConfig,
|
||||
apiKey: 'anthropic-key',
|
||||
apiProviderBaseUrl: null,
|
||||
baseUrl: 'https://my-proxy.example.com',
|
||||
model: 'my-model',
|
||||
};
|
||||
|
||||
const next = switchApiProtocolConfig(config, 'openai');
|
||||
|
||||
expect(next).toMatchObject({
|
||||
mode: 'api',
|
||||
apiProtocol: 'openai',
|
||||
apiKey: '',
|
||||
baseUrl: 'https://my-proxy.example.com',
|
||||
model: 'my-model',
|
||||
apiProviderBaseUrl: null,
|
||||
});
|
||||
expect(next.apiProtocolConfigs?.anthropic).toMatchObject({
|
||||
apiKey: 'anthropic-key',
|
||||
baseUrl: 'https://my-proxy.example.com',
|
||||
model: 'my-model',
|
||||
apiProviderBaseUrl: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('restores each protocol draft instead of leaking shared field values', () => {
|
||||
const openai = switchApiProtocolConfig(baseConfig, 'openai');
|
||||
const openaiEdited = updateCurrentApiProtocolConfig(openai, {
|
||||
apiKey: 'openai-key',
|
||||
baseUrl: 'https://openai-proxy.example.com',
|
||||
model: 'openai-model',
|
||||
apiProviderBaseUrl: null,
|
||||
});
|
||||
const google = switchApiProtocolConfig(openaiEdited, 'google');
|
||||
const googleEdited = updateCurrentApiProtocolConfig(google, {
|
||||
apiKey: 'google-key',
|
||||
baseUrl: 'https://google-proxy.example.com',
|
||||
model: 'google-model',
|
||||
apiProviderBaseUrl: null,
|
||||
});
|
||||
|
||||
const restoredOpenai = switchApiProtocolConfig(googleEdited, 'openai');
|
||||
|
||||
expect(restoredOpenai).toMatchObject({
|
||||
mode: 'api',
|
||||
apiProtocol: 'openai',
|
||||
apiKey: 'openai-key',
|
||||
baseUrl: 'https://openai-proxy.example.com',
|
||||
model: 'openai-model',
|
||||
apiProviderBaseUrl: null,
|
||||
});
|
||||
expect(restoredOpenai.apiProtocolConfigs?.google).toMatchObject({
|
||||
apiKey: 'google-key',
|
||||
baseUrl: 'https://google-proxy.example.com',
|
||||
model: 'google-model',
|
||||
apiProviderBaseUrl: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the new protocol default on first visit', () => {
|
||||
expect(switchApiProtocolConfig(baseConfig, 'openai')).toMatchObject({
|
||||
mode: 'api',
|
||||
apiProtocol: 'openai',
|
||||
apiKey: '',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
model: 'gpt-4o',
|
||||
apiProviderBaseUrl: 'https://api.openai.com/v1',
|
||||
});
|
||||
});
|
||||
|
||||
it('auto-fills Google defaults when switching from a selected known provider', () => {
|
||||
expect(switchApiProtocolConfig(baseConfig, 'google')).toMatchObject({
|
||||
mode: 'api',
|
||||
apiProtocol: 'google',
|
||||
apiKey: '',
|
||||
baseUrl: 'https://generativelanguage.googleapis.com',
|
||||
model: 'gemini-2.0-flash',
|
||||
apiProviderBaseUrl: 'https://generativelanguage.googleapis.com',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps Azure API version in the Azure draft only', () => {
|
||||
const config: AppConfig = {
|
||||
...baseConfig,
|
||||
apiProtocol: 'azure',
|
||||
apiKey: 'azure-key',
|
||||
model: 'deployment-one',
|
||||
apiVersion: '2024-10-21',
|
||||
};
|
||||
|
||||
const next = switchApiProtocolConfig(config, 'openai');
|
||||
|
||||
expect(next).toMatchObject({
|
||||
apiProtocol: 'openai',
|
||||
apiKey: '',
|
||||
apiVersion: '',
|
||||
});
|
||||
expect(next.apiProtocolConfigs?.azure).toMatchObject({
|
||||
apiKey: 'azure-key',
|
||||
model: 'deployment-one',
|
||||
apiVersion: '2024-10-21',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SettingsDialog API Base URL validation', () => {
|
||||
it('accepts public http/https URLs and loopback local providers', () => {
|
||||
expect(isValidApiBaseUrl('https://api.openai.com/v1')).toBe(true);
|
||||
expect(isValidApiBaseUrl('http://localhost:11434/v1')).toBe(true);
|
||||
expect(isValidApiBaseUrl('http://127.0.0.1:11434/v1')).toBe(true);
|
||||
expect(isValidApiBaseUrl('http://[::1]:11434/v1')).toBe(true);
|
||||
expect(isValidApiBaseUrl(' https://resource.openai.azure.com ')).toBe(true);
|
||||
|
||||
expect(isValidApiBaseUrl('ddddd')).toBe(false);
|
||||
expect(isValidApiBaseUrl('api.openai.com/v1')).toBe(false);
|
||||
expect(isValidApiBaseUrl('ftp://api.example.com')).toBe(false);
|
||||
expect(isValidApiBaseUrl('http:api.example.com')).toBe(false);
|
||||
expect(isValidApiBaseUrl('https://')).toBe(false);
|
||||
expect(isValidApiBaseUrl('http://10.0.0.5:11434/v1')).toBe(false);
|
||||
expect(isValidApiBaseUrl('http://169.254.1.5:11434/v1')).toBe(false);
|
||||
expect(isValidApiBaseUrl('http://172.16.0.5:11434/v1')).toBe(false);
|
||||
expect(isValidApiBaseUrl('http://192.168.1.5:11434/v1')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SettingsDialog agent CLI env settings', () => {
|
||||
it('updates supported per-agent CLI env values without dropping sibling agents', () => {
|
||||
const config: AppConfig = {
|
||||
...baseConfig,
|
||||
mode: 'daemon',
|
||||
agentCliEnv: {
|
||||
codex: { CODEX_HOME: '~/.codex-alt' },
|
||||
},
|
||||
};
|
||||
|
||||
const next = updateAgentCliEnvValue(
|
||||
config,
|
||||
'claude',
|
||||
'CLAUDE_CONFIG_DIR',
|
||||
' ~/.claude-2 ',
|
||||
);
|
||||
|
||||
expect(next.agentCliEnv).toEqual({
|
||||
claude: { CLAUDE_CONFIG_DIR: '~/.claude-2' },
|
||||
codex: { CODEX_HOME: '~/.codex-alt' },
|
||||
});
|
||||
});
|
||||
|
||||
it('removes empty per-agent CLI env entries', () => {
|
||||
const config: AppConfig = {
|
||||
...baseConfig,
|
||||
mode: 'daemon',
|
||||
agentCliEnv: {
|
||||
claude: { CLAUDE_CONFIG_DIR: '~/.claude-2' },
|
||||
codex: { CODEX_HOME: '~/.codex-alt' },
|
||||
},
|
||||
};
|
||||
|
||||
const next = updateAgentCliEnvValue(
|
||||
config,
|
||||
'claude',
|
||||
'CLAUDE_CONFIG_DIR',
|
||||
'',
|
||||
);
|
||||
|
||||
expect(next.agentCliEnv).toEqual({
|
||||
codex: { CODEX_HOME: '~/.codex-alt' },
|
||||
});
|
||||
});
|
||||
|
||||
it('passes pending CLI env prefs through agent rescan options', () => {
|
||||
const config: AppConfig = {
|
||||
...baseConfig,
|
||||
mode: 'daemon',
|
||||
agentCliEnv: {
|
||||
claude: { CLAUDE_CONFIG_DIR: '~/.claude-pending' },
|
||||
},
|
||||
};
|
||||
|
||||
expect(agentRefreshOptionsForConfig(config)).toEqual({
|
||||
throwOnError: true,
|
||||
agentCliEnv: {
|
||||
claude: { CLAUDE_CONFIG_DIR: '~/.claude-pending' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('passes an empty CLI env object through agent rescan after fields are cleared', () => {
|
||||
const config: AppConfig = {
|
||||
...baseConfig,
|
||||
mode: 'daemon',
|
||||
agentCliEnv: {},
|
||||
};
|
||||
|
||||
expect(agentRefreshOptionsForConfig(config)).toEqual({
|
||||
throwOnError: true,
|
||||
agentCliEnv: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user