Files
gongxue-base/apps/admin/src/pages/AiConfig/helpers.integration.test.ts
wangziqi 375c7ec60b feat: refine admin forms, attendance and finance workflows
Squash merge PR #23.

Included changes:
- complete occupancy check-in required fields/default payload
- improve responsive admin management pages
- fix attendance edge cases and attendance period config
- refine wallet/finance-related workflow handling

Checks:
- npm run typecheck -w apps/admin
- npm run typecheck -w apps/server
2026-07-18 12:54:10 +00:00

108 lines
3.8 KiB
TypeScript

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');
});
});
});