/** * DeepSeekAdapter.getBalance 解析测试(v0.5.2 回归修复) * * 背景:DeepSeek 官方 /user/balance 实际返回 balance_infos 数组格式, * 此前按扁平字段解析(data.total_balance)→ 恒为 undefined → 界面永远显示 0。 * 本测试固化两种格式(官方数组 + 网关扁平)的解析与 URL 规范化行为。 */ import { describe, it, expect, vi, afterEach } from 'vitest'; vi.mock('electron-log', () => ({ default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }, })); import { DeepSeekAdapter } from '../deepseek.adapter'; const CONFIG = { provider: 'deepseek', baseURL: 'https://api.deepseek.com', apiKey: 'sk-test-key', defaultModel: 'deepseek-v4-pro', }; function makeAdapter(baseURL = CONFIG.baseURL): DeepSeekAdapter { return new DeepSeekAdapter({ ...CONFIG, baseURL }); } const mockFetch = vi.fn(); vi.stubGlobal('fetch', mockFetch); afterEach(() => { mockFetch.mockReset(); }); function okResponse(body: unknown): Response { return { ok: true, status: 200, json: async () => body, } as unknown as Response; } describe('DeepSeekAdapter.getBalance — 响应格式解析(v0.5.2)', () => { it('官方 balance_infos 数组格式正确解析(真实 API 格式,修复点)', async () => { mockFetch.mockResolvedValue( okResponse({ is_available: true, balance_infos: [ { currency: 'CNY', total_balance: '110.55', granted_balance: '10.55', topped_up_balance: '100.00', }, ], }), ); const balance = await makeAdapter().getBalance(); expect(balance).not.toBeNull(); expect(balance!.totalBalance).toBe('110.55'); expect(balance!.grantedBalance).toBe('10.55'); expect(balance!.toppedUpBalance).toBe('100.00'); expect(balance!.currency).toBe('CNY'); }); it('扁平格式回退解析(网关/代理简化响应兼容)', async () => { mockFetch.mockResolvedValue( okResponse({ currency: 'USD', total_balance: '50.00', granted_balance: '0.00', topped_up_balance: '50.00', }), ); const balance = await makeAdapter().getBalance(); expect(balance).not.toBeNull(); expect(balance!.totalBalance).toBe('50.00'); expect(balance!.currency).toBe('USD'); }); it('baseURL 带尾斜杠或 /v1 前缀时规范化为根路径端点', async () => { mockFetch.mockResolvedValue( okResponse({ balance_infos: [{ currency: 'CNY', total_balance: '1.00' }], }), ); await makeAdapter('https://api.deepseek.com/v1/').getBalance(); expect(mockFetch).toHaveBeenCalledWith( 'https://api.deepseek.com/user/balance', expect.objectContaining({ headers: { Authorization: 'Bearer sk-test-key' } }), ); mockFetch.mockClear(); await makeAdapter('https://api.deepseek.com/v1').getBalance(); expect(mockFetch).toHaveBeenCalledWith( 'https://api.deepseek.com/user/balance', expect.anything(), ); }); it('非 2xx 响应返回 null', async () => { mockFetch.mockResolvedValue({ ok: false, status: 401 } as unknown as Response); expect(await makeAdapter().getBalance()).toBeNull(); }); it('请求异常返回 null(网络错误)', async () => { mockFetch.mockRejectedValue(new Error('network down')); expect(await makeAdapter().getBalance()).toBeNull(); }); });