fix(quota): improve Grok credential quota accuracy and subscription details

- Keep weekly usage separate from monthly spending
- Enrich subscription plans in the background with stale-result guards
- Display prepaid balances and hide empty monthly quota rows
- Add localized labels and regression coverage
This commit is contained in:
lzt404 committed 2026-10-08 18:36:58 +08:00
1 parent 8eab980d72
commit b5189bb436
14 files changed
+217 -18

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
<!doctype html>
<html><head><meta name="viewport" content="width=device-width, initial-scale=1.0" /></head><body><div id="root"></div><script type="module" src="./main.tsx"></script></body></html>
+25
View File
@@ -0,0 +1,25 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { mockIPC } from '@tauri-apps/api/mocks';
import { I18nProvider } from '../../../src/i18n';
import { QuotaPage } from '../../../src/pages/QuotaPage';
import '../../../src/styles/index.css';
// Fictional billing and subscription responses; no upstream calls or real credentials.
const params = new URLSearchParams(location.search);
localStorage.setItem('easy-cli-proxy-api.locale', params.get('locale') || 'zh-CN');
document.documentElement.dataset.theme = params.get('theme') || 'light';
mockIPC(async (command, args) => {
if (command !== 'management_request') throw new Error('Unexpected fixture command');
const request = args.request as { path: string; body?: { url: string } };
if (request.path === '/credentials') return { files: [{ name: 'grok-fictional.json', provider: 'xai', auth_index: 'fixture' }] };
const url = request.body?.url || '';
if (url.endsWith('/settings') || url.includes('/user?')) {
await new Promise((resolve) => setTimeout(resolve, 1500));
return { status_code: 200, body: { subscription_tier_display: 'SuperGrok Heavy' } };
}
return { status_code: 200, body: { config: url.includes('format=credits')
? { currentPeriod: { type: 'weekly', end: '2030-01-08T00:00:00Z' }, prepaidBalance: { val: 250 } }
: { monthlyLimit: 15000, used: 3000, billingPeriodEnd: '2030-02-01T00:00:00Z' } } };
});
createRoot(document.getElementById('root')!).render(<I18nProvider><QuotaPage /></I18nProvider>);
+70
View File
@@ -0,0 +1,70 @@
import { afterEach, expect, it, spyOn } from 'bun:test';
import { managementApi } from '../src/services/managementApi';
import { commitFetchedQuota } from '../src/services/quotaEnrichment';
import { captureQuotaCacheGeneration, getQuotaCacheSnapshot, pruneQuotaCache, updateQuotaCache } from '../src/services/quotaCache';
import { enrichXaiQuotaPlan, loadQuota, quotaKey, type QuotaState } from '../src/services/quotaService';
const file = { name: 'grok-fixture.json', provider: 'xai', auth_index: 'fixture', sub: 'fictional-user' };
const key = quotaKey(file);
const quota: QuotaState = { status: 'success', rows: [{ label: 'Weekly', remainingPercent: 75 }], plan: 'Paid' };
let post: ReturnType<typeof spyOn> | undefined;
afterEach(() => { post?.mockRestore(); updateQuotaCache({}); });
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
it('先提交额度,再后台补套餐;两次请求使用短超时和现有用户头', async () => {
let release!: (value: unknown) => void;
const gate = new Promise((resolve) => { release = resolve; });
post = spyOn(managementApi, 'post').mockImplementation(async (path, body, options) => {
expect(path).toBe('/requests/api-call');
expect(getQuotaCacheSnapshot()[key]).toBe(quota);
expect(options).toEqual({ timeoutMs: 8000 });
expect(body).toMatchObject({ authIndex: 'fixture', method: 'GET', header: { 'x-userid': 'fictional-user' } });
return await gate as never;
});
commitFetchedQuota(captureQuotaCacheGeneration(), file, quota);
expect(getQuotaCacheSnapshot()[key]).toBe(quota);
expect(post).toHaveBeenCalledTimes(2);
release({ status_code: 200, body: { subscription_tier_display: 'SuperGrok Heavy' } });
await flush();
expect(getQuotaCacheSnapshot()[key]).toEqual({ ...quota, plan: 'SuperGrok Heavy' });
});
it('settings 失败仍可使用 user 套餐,两个失败保留原结果', async () => {
post = spyOn(managementApi, 'post').mockImplementation(async (_path, body) => {
const request = body as { url: string };
if (request.url.endsWith('/settings')) throw new Error('offline');
return { status_code: 200, body: JSON.stringify({ subscriptionTier: 'SuperGrok' }) } as never;
});
expect(await enrichXaiQuotaPlan(file, quota)).toEqual({ ...quota, plan: 'SuperGrok' });
post.mockImplementation(async () => ({ status_code: 403, body: 'unavailable' }) as never);
expect(await enrichXaiQuotaPlan(file, quota)).toBe(quota);
post.mockImplementation(async () => ({ status_code: 200, body: {
subscription_tier_display: { invalid: true }, subscriptionTier: false,
} }) as never);
expect(await enrichXaiQuotaPlan(file, quota)).toBe(quota);
});
for (const change of ['refresh', 'remove', 'session'] as const) {
it(`迟到套餐不覆盖 ${change}`, async () => {
let release!: (value: unknown) => void;
const gate = new Promise((resolve) => { release = resolve; });
post = spyOn(managementApi, 'post').mockImplementation(async () => await gate as never);
commitFetchedQuota(captureQuotaCacheGeneration(), file, quota);
if (change === 'refresh') updateQuotaCache({ [key]: { status: 'loading', rows: [] } });
else if (change === 'remove') updateQuotaCache({});
else { pruneQuotaCache(new Set()); updateQuotaCache({ [key]: quota }); }
const expected = getQuotaCacheSnapshot();
release({ status_code: 200, body: { subscription_tier_display: 'SuperGrok Heavy' } });
await flush();
expect(getQuotaCacheSnapshot()).toBe(expected);
});
}
it('套餐接口不阻塞账单结果,并按月额度提供套餐回退', async () => {
post = spyOn(managementApi, 'post').mockImplementation(async (_path, body) => {
expect((body as { url: string }).url).toContain('/billing');
return { status_code: 200, body: { config: { monthlyLimit: 15000, used: 3000 } } } as never;
});
expect(await loadQuota(file)).toMatchObject({ status: 'success', plan: 'SuperGrok' });
expect(post).toHaveBeenCalledTimes(2);
});
+12
View File
@@ -55,6 +55,18 @@ describe('quota card rendering', () => {
expect(full).toContain('width:100%');
expect(full).not.toContain('disabled=""');
});
it('Grok 套餐、未知周额度和预付余额可通过现有卡片完整显示', () => {
const html = render({ status: 'success', plan: 'SuperGrok Heavy', rows: quotaRowsFor('xai', {
weekly: { config: { currentPeriod: { type: 'weekly', end: '2030-01-08T00:00:00Z' }, prepaidBalance: 250 } },
monthly: { config: { monthlyLimit: 10000, used: 2500 } },
}) }, 'xai');
expect(html).toContain('SuperGrok Heavy');
expect(html).toContain('Weekly quota');
expect(html).toContain('Prepaid balance');
expect(html).toContain('$2.50');
expect(html.match(/75% remaining/g)).toHaveLength(1);
expect(html.match(/real-quota-track/g)).toHaveLength(1);
});
it('缓存中的原始重置时间提供动态提示,过期额度不伪造为已恢复', () => {
const html = render({ status: 'success', rows: [{ label: '5h', remainingPercent: 0, resetAtMs: Date.parse('2020-01-01T00:00:00Z') }] });
expect(html).toContain('Reset time reached; refresh to verify');
+1 -1
View File
@@ -184,7 +184,7 @@ describe('xAI quota queries aligned with Management Center', () => {
handler = (request) => success({ config: request.url.includes('format=credits')
? { currentPeriod: { type: 'weekly' } } : { creditUsagePercent: 0 } });
expect(await loadQuota(file)).toMatchObject({
status: 'success', rows: [{ remainingPercent: 100 }],
status: 'success', rows: [{ remainingPercent: null }],
});
expect(calls).toHaveLength(2);
});
+26 -1
View File
@@ -335,7 +335,32 @@ describe('quotaRowsFor', () => {
monthly: { config: { creditUsagePercent: 0, currentPeriod: { type: 'monthly', end: '2030-01-01T00:00:00Z' } } },
});
expect(rows).toHaveLength(1);
expect(rows[0].remainingPercent).toBe(100);
expect(rows[0].remainingPercent).toBeNull();
expect(rows[0].resetAtMs).toBeUndefined();
});
it('xAI 周用量缺失或无效时不会借用月度支出百分比', () => {
for (const creditUsagePercent of [undefined, 'bad']) {
for (const used of [0, 2500]) {
const rows = quotaRowsFor('xai', {
weekly: { config: { currentPeriod: { type: 'weekly', end: '2030-01-08T00:00:00Z' }, creditUsagePercent } },
monthly: { config: { monthlyLimit: 10000, used } },
});
expect(rows[0]).toMatchObject({ label: 'Weekly quota', remainingPercent: null });
expect(rows[1].remainingPercent).toBe(100 - used / 100);
}
}
});
it('xAI 隐藏周额度下的零预算零用量月行,但保留真实支出和月度独立行', () => {
const weekly = { config: { creditUsagePercent: 25 } };
expect(quotaRowsFor('xai', { weekly, monthly: { config: { monthlyLimit: 0, used: 0 } } })).toHaveLength(1);
expect(quotaRowsFor('xai', { weekly, monthly: { config: { monthlyLimit: 0, used: 100 } } })).toHaveLength(2);
expect(quotaRowsFor('xai', { config: { monthlyLimit: 0, used: 0 } })).toHaveLength(1);
});
it('xAI 支持两种预付余额字段,金额不当作百分比', () => {
for (const key of ['prepaidBalance', 'prepaid_balance']) {
expect(quotaRowsFor('xai', { config: { [key]: { val: 250 } } })).toEqual([
{ label: 'Prepaid balance', remainingPercent: null, detail: '$2.50' },
]);
}
});
});