## MSW Infrastructure Enhancement - Add 5 MCP API handlers to tests/msw/handlers.ts: - get_mcp_config: Fetch MCP configuration for app type - import_mcp_from_claude/codex: Mock import operations (returns count: 1) - set_mcp_enabled: Toggle MCP server enabled state - upsert_mcp_server_in_config: Create/update MCP server - delete_mcp_server_in_config: Remove MCP server - Add MCP state management to tests/msw/state.ts: - McpConfigState type with per-app server storage - Default test data (stdio server for Claude, http server for Codex) - CRUD functions: getMcpConfig, setMcpServerEnabled, upsertMcpServer, deleteMcpServer - Immutable state operations with deep cloning ## McpFormModal Component Tests (4 tests) - Test preset application: Verify ID and config JSON auto-fill from preset selection - Test conflict detection: Async validation shows warning when syncing to conflicting ID - Test field sanitization: Verify trim whitespace, split tags, clean URLs before save - Test validation errors: Block submit and show toast error for invalid stdio config (missing command) ## McpPanel Integration Tests (3 tests) - Test toggle enabled state: Click toggle button triggers useMcpActions.toggleEnabled with correct params - Test create server flow: Open form → submit → saveServer called with syncOtherSide option - Test delete server flow: Click delete → confirm dialog → deleteServer called with ID ## Test Utilities - Add createTestQueryClient helper with retry: false for faster test execution ## Test Coverage - Test files: 15 → 17 (+2) - Total tests: 105 → 112 (+6.7%) - All 112 tests passing - Execution time: 3.15s
237 lines
6.0 KiB
TypeScript
237 lines
6.0 KiB
TypeScript
import type { AppType } from "@/lib/api/types";
|
|
import type { McpServer, Provider, Settings } from "@/types";
|
|
|
|
type ProvidersByApp = Record<AppType, Record<string, Provider>>;
|
|
type CurrentProviderState = Record<AppType, string>;
|
|
type McpConfigState = Record<AppType, Record<string, McpServer>>;
|
|
|
|
const createDefaultProviders = (): ProvidersByApp => ({
|
|
claude: {
|
|
"claude-1": {
|
|
id: "claude-1",
|
|
name: "Claude Default",
|
|
settingsConfig: {},
|
|
category: "official",
|
|
sortIndex: 0,
|
|
createdAt: Date.now(),
|
|
},
|
|
"claude-2": {
|
|
id: "claude-2",
|
|
name: "Claude Custom",
|
|
settingsConfig: {},
|
|
category: "custom",
|
|
sortIndex: 1,
|
|
createdAt: Date.now() + 1,
|
|
},
|
|
},
|
|
codex: {
|
|
"codex-1": {
|
|
id: "codex-1",
|
|
name: "Codex Default",
|
|
settingsConfig: {},
|
|
category: "official",
|
|
sortIndex: 0,
|
|
createdAt: Date.now(),
|
|
},
|
|
"codex-2": {
|
|
id: "codex-2",
|
|
name: "Codex Secondary",
|
|
settingsConfig: {},
|
|
category: "custom",
|
|
sortIndex: 1,
|
|
createdAt: Date.now() + 1,
|
|
},
|
|
},
|
|
});
|
|
|
|
const createDefaultCurrent = (): CurrentProviderState => ({
|
|
claude: "claude-1",
|
|
codex: "codex-1",
|
|
});
|
|
|
|
let providers = createDefaultProviders();
|
|
let current = createDefaultCurrent();
|
|
let settingsState: Settings = {
|
|
showInTray: true,
|
|
minimizeToTrayOnClose: true,
|
|
enableClaudePluginIntegration: false,
|
|
claudeConfigDir: "/default/claude",
|
|
codexConfigDir: "/default/codex",
|
|
language: "zh",
|
|
};
|
|
let appConfigDirOverride: string | null = null;
|
|
let mcpConfigs: McpConfigState = {
|
|
claude: {
|
|
sample: {
|
|
id: "sample",
|
|
name: "Sample Claude Server",
|
|
enabled: true,
|
|
server: {
|
|
type: "stdio",
|
|
command: "claude-server",
|
|
},
|
|
},
|
|
},
|
|
codex: {
|
|
httpServer: {
|
|
id: "httpServer",
|
|
name: "HTTP Codex Server",
|
|
enabled: false,
|
|
server: {
|
|
type: "http",
|
|
url: "http://localhost:3000",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const cloneProviders = (value: ProvidersByApp) =>
|
|
JSON.parse(JSON.stringify(value)) as ProvidersByApp;
|
|
|
|
export const resetProviderState = () => {
|
|
providers = createDefaultProviders();
|
|
current = createDefaultCurrent();
|
|
settingsState = {
|
|
showInTray: true,
|
|
minimizeToTrayOnClose: true,
|
|
enableClaudePluginIntegration: false,
|
|
claudeConfigDir: "/default/claude",
|
|
codexConfigDir: "/default/codex",
|
|
language: "zh",
|
|
};
|
|
appConfigDirOverride = null;
|
|
mcpConfigs = {
|
|
claude: {
|
|
sample: {
|
|
id: "sample",
|
|
name: "Sample Claude Server",
|
|
enabled: true,
|
|
server: {
|
|
type: "stdio",
|
|
command: "claude-server",
|
|
},
|
|
},
|
|
},
|
|
codex: {
|
|
httpServer: {
|
|
id: "httpServer",
|
|
name: "HTTP Codex Server",
|
|
enabled: false,
|
|
server: {
|
|
type: "http",
|
|
url: "http://localhost:3000",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
export const getProviders = (appType: AppType) =>
|
|
cloneProviders(providers)[appType] ?? {};
|
|
|
|
export const getCurrentProviderId = (appType: AppType) => current[appType] ?? "";
|
|
|
|
export const setCurrentProviderId = (appType: AppType, providerId: string) => {
|
|
current[appType] = providerId;
|
|
};
|
|
|
|
export const updateProviders = (appType: AppType, data: Record<string, Provider>) => {
|
|
providers[appType] = cloneProviders({ [appType]: data } as ProvidersByApp)[appType];
|
|
};
|
|
|
|
export const setProviders = (appType: AppType, data: Record<string, Provider>) => {
|
|
providers[appType] = JSON.parse(JSON.stringify(data)) as Record<string, Provider>;
|
|
};
|
|
|
|
export const addProvider = (appType: AppType, provider: Provider) => {
|
|
providers[appType] = providers[appType] ?? {};
|
|
providers[appType][provider.id] = provider;
|
|
};
|
|
|
|
export const updateProvider = (appType: AppType, provider: Provider) => {
|
|
if (!providers[appType]) return;
|
|
providers[appType][provider.id] = {
|
|
...providers[appType][provider.id],
|
|
...provider,
|
|
};
|
|
};
|
|
|
|
export const deleteProvider = (appType: AppType, providerId: string) => {
|
|
if (!providers[appType]) return;
|
|
delete providers[appType][providerId];
|
|
if (current[appType] === providerId) {
|
|
const fallback = Object.keys(providers[appType])[0] ?? "";
|
|
current[appType] = fallback;
|
|
}
|
|
};
|
|
|
|
export const updateSortOrder = (
|
|
appType: AppType,
|
|
updates: { id: string; sortIndex: number }[],
|
|
) => {
|
|
if (!providers[appType]) return;
|
|
updates.forEach(({ id, sortIndex }) => {
|
|
const provider = providers[appType][id];
|
|
if (provider) {
|
|
providers[appType][id] = { ...provider, sortIndex };
|
|
}
|
|
});
|
|
};
|
|
|
|
export const listProviders = (appType: AppType) =>
|
|
JSON.parse(JSON.stringify(providers[appType] ?? {})) as Record<string, Provider>;
|
|
|
|
export const getSettings = () => JSON.parse(JSON.stringify(settingsState)) as Settings;
|
|
|
|
export const setSettings = (data: Partial<Settings>) => {
|
|
settingsState = { ...settingsState, ...data };
|
|
};
|
|
|
|
export const getAppConfigDirOverride = () => appConfigDirOverride;
|
|
|
|
export const setAppConfigDirOverrideState = (value: string | null) => {
|
|
appConfigDirOverride = value;
|
|
};
|
|
|
|
export const getMcpConfig = (appType: AppType) => {
|
|
const servers = JSON.parse(
|
|
JSON.stringify(mcpConfigs[appType] ?? {}),
|
|
) as Record<string, McpServer>;
|
|
return {
|
|
configPath: `/mock/${appType}.mcp.json`,
|
|
servers,
|
|
};
|
|
};
|
|
|
|
export const setMcpConfig = (appType: AppType, value: Record<string, McpServer>) => {
|
|
mcpConfigs[appType] = JSON.parse(JSON.stringify(value)) as Record<string, McpServer>;
|
|
};
|
|
|
|
export const setMcpServerEnabled = (
|
|
appType: AppType,
|
|
id: string,
|
|
enabled: boolean,
|
|
) => {
|
|
if (!mcpConfigs[appType]?.[id]) return;
|
|
mcpConfigs[appType][id] = {
|
|
...mcpConfigs[appType][id],
|
|
enabled,
|
|
};
|
|
};
|
|
|
|
export const upsertMcpServer = (
|
|
appType: AppType,
|
|
id: string,
|
|
server: McpServer,
|
|
) => {
|
|
if (!mcpConfigs[appType]) {
|
|
mcpConfigs[appType] = {};
|
|
}
|
|
mcpConfigs[appType][id] = JSON.parse(JSON.stringify(server)) as McpServer;
|
|
};
|
|
|
|
export const deleteMcpServer = (appType: AppType, id: string) => {
|
|
if (!mcpConfigs[appType]) return;
|
|
delete mcpConfigs[appType][id];
|
|
};
|