import { describe, it, expect } from 'vitest'; import { shouldAutoSwapBaseUrl, formatDateTime, sourceLabel, sourceColor, PROVIDER_DEFAULTS, extractErrorMessage, } from './helpers'; describe('AiConfig helpers', () => { describe('shouldAutoSwapBaseUrl', () => { it('swaps to default on first provider selection', () => { const result = shouldAutoSwapBaseUrl('DEEPSEEK', '', null); expect(result.shouldSwap).toBe(true); expect(result.baseUrl).toBe(PROVIDER_DEFAULTS.DEEPSEEK); }); it('swaps when current baseUrl matches previous provider default', () => { const result = shouldAutoSwapBaseUrl('DEEPSEEK', 'https://api.openai.com/v1', 'OPENAI'); expect(result.shouldSwap).toBe(true); expect(result.baseUrl).toBe(PROVIDER_DEFAULTS.DEEPSEEK); }); it('swaps when current baseUrl is empty', () => { const result = shouldAutoSwapBaseUrl('DEEPSEEK', '', 'OPENAI'); expect(result.shouldSwap).toBe(true); }); it('keeps custom baseUrl unchanged', () => { const result = shouldAutoSwapBaseUrl('OPENAI', 'https://custom.api.com/v1', 'DEEPSEEK'); expect(result.shouldSwap).toBe(false); expect(result.baseUrl).toBe('https://custom.api.com/v1'); }); }); describe('formatDateTime', () => { it('returns hyphen for null', () => { expect(formatDateTime(null)).toBe('-'); }); it('returns Chinese locale string for ISO date', () => { expect(formatDateTime('2024-01-15T10:30:00Z')).toContain('2024'); }); }); describe('sourceLabel', () => { it('returns Chinese labels', () => { expect(sourceLabel('database')).toBe('数据库'); expect(sourceLabel('environment')).toBe('环境变量'); expect(sourceLabel('none')).toBe('未配置'); }); }); describe('sourceColor', () => { it('returns correct colors', () => { expect(sourceColor('database')).toBe('green'); expect(sourceColor('environment')).toBe('blue'); expect(sourceColor('none')).toBe('default'); }); }); describe('extractErrorMessage', () => { it('extracts message from server error response (interceptor unwraps to { message })', () => { // The Axios interceptor at api/index.ts does Promise.reject(err.response?.data || err). // For server errors, the rejection value is err.response.data — typically { message: '...' }. const err = { message: 'API出错' }; expect(extractErrorMessage(err)).toBe('API出错'); }); it('falls back to message property', () => { const err = { message: 'Network error' }; expect(extractErrorMessage(err)).toBe('Network error'); }); it('falls back to default on unknown type', () => { expect(extractErrorMessage('unknown string')).toBe('操作失败'); expect(extractErrorMessage(null)).toBe('操作失败'); expect(extractErrorMessage(undefined)).toBe('操作失败'); }); it('sanitizes: newlines replaced with spaces', () => { const err = { message: 'line1\nline2\r\nline3' }; expect(extractErrorMessage(err)).toBe('line1 line2 line3'); }); it('sanitizes: message > 120 chars truncated with ellipsis', () => { const long = 'x'.repeat(200); const err = { message: long }; const result = extractErrorMessage(err); expect(result).toHaveLength(121); // 120 + '…' (1 char) expect(result.endsWith('\u2026')).toBe(true); }); it('sanitizes: empty trimmed message falls back', () => { const err = { message: ' ' }; expect(extractErrorMessage(err)).toBe('操作失败'); }); it('sanitizes: plain object message property sanitized', () => { const err = { message: ' some \n\nerror \r\nmessage ', }; expect(extractErrorMessage(err)).toBe('some error message'); }); }); });