fix(desktop): validate adminRouting with type-safe parsing and logged warnings
- Add type guard (typeof parsed === 'object' && 'llm_routing' in parsed) before accessing llm_routing - Replace silent catch with log.warn for parse failures - Add 8 unit tests covering valid/invalid/null/malformed inputs
This commit is contained in:
@@ -357,23 +357,26 @@ export const useConnectionStore = create<ConnectionStore>((set, get) => {
|
||||
try {
|
||||
const raw = localStorage.getItem('zclaw-saas-account');
|
||||
if (raw) {
|
||||
const storedAccount = JSON.parse(raw);
|
||||
// storedAccount is SaaSAccountInfo (saved directly by saveSaaSSession)
|
||||
// 类型安全解析: 仅接受 'relay' | 'local' 两个合法值
|
||||
const adminRouting = storedAccount?.llm_routing;
|
||||
if (adminRouting === 'relay') {
|
||||
// Force SaaS Relay mode — admin override
|
||||
localStorage.setItem('zclaw-connection-mode', 'saas');
|
||||
log.debug('Admin llm_routing=relay: forcing SaaS relay mode');
|
||||
} else if (adminRouting === 'local' && isTauriRuntime()) {
|
||||
// Force local Kernel mode — skip SaaS relay entirely
|
||||
adminForceLocal = true;
|
||||
localStorage.setItem('zclaw-connection-mode', 'tauri');
|
||||
log.debug('Admin llm_routing=local: forcing local Kernel mode');
|
||||
const parsed = JSON.parse(raw);
|
||||
// Type-safe parsing: only accept 'relay' | 'local' as valid values
|
||||
if (parsed && typeof parsed === 'object' && 'llm_routing' in parsed) {
|
||||
const adminRouting = parsed.llm_routing;
|
||||
if (adminRouting === 'relay') {
|
||||
// Force SaaS Relay mode — admin override
|
||||
localStorage.setItem('zclaw-connection-mode', 'saas');
|
||||
log.debug('Admin llm_routing=relay: forcing SaaS relay mode');
|
||||
} else if (adminRouting === 'local' && isTauriRuntime()) {
|
||||
// Force local Kernel mode — skip SaaS relay entirely
|
||||
adminForceLocal = true;
|
||||
localStorage.setItem('zclaw-connection-mode', 'tauri');
|
||||
log.debug('Admin llm_routing=local: forcing local Kernel mode');
|
||||
}
|
||||
// Other values (including undefined/null/invalid) are ignored, fall through to default logic
|
||||
}
|
||||
// 其他值(含 undefined/null/非法值)忽略,走默认逻辑
|
||||
}
|
||||
} catch { /* ignore parse errors, fall through to default logic */ }
|
||||
} catch (e) {
|
||||
log.warn('Failed to parse admin routing from localStorage, using default', e);
|
||||
}
|
||||
|
||||
// === Internal Kernel Mode: Admin forced local ===
|
||||
// If admin forced local mode, skip directly to Tauri Kernel section
|
||||
|
||||
53
tests/desktop/connectionStore.adminRouting.test.ts
Normal file
53
tests/desktop/connectionStore.adminRouting.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
/**
|
||||
* Pure function test: adminRouting parsing logic
|
||||
* Core parsing logic extracted from connectionStore.ts
|
||||
*/
|
||||
function parseAdminRouting(raw: string | null): 'relay' | 'local' | null {
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed && typeof parsed === 'object' && 'llm_routing' in parsed) {
|
||||
const routing = parsed.llm_routing;
|
||||
if (routing === 'relay' || routing === 'local') return routing;
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
describe('parseAdminRouting', () => {
|
||||
it('returns "relay" for valid relay config', () => {
|
||||
expect(parseAdminRouting('{"llm_routing":"relay"}')).toBe('relay');
|
||||
});
|
||||
|
||||
it('returns "local" for valid local config', () => {
|
||||
expect(parseAdminRouting('{"llm_routing":"local"}')).toBe('local');
|
||||
});
|
||||
|
||||
it('returns null for null input', () => {
|
||||
expect(parseAdminRouting(null)).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for empty string', () => {
|
||||
expect(parseAdminRouting('')).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for malformed JSON', () => {
|
||||
expect(parseAdminRouting('{not json}')).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for missing llm_routing field', () => {
|
||||
expect(parseAdminRouting('{"name":"test"}')).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for invalid routing value', () => {
|
||||
expect(parseAdminRouting('{"llm_routing":"invalid"}')).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for number routing value', () => {
|
||||
expect(parseAdminRouting('{"llm_routing":123}')).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user