Files
giovannircoandSupra4E8C 69ebed4f3f Show the Grok subscription on the xAI quota card (#451)
* feat(quota): show the Grok subscription on the xAI card

The billing payload has no plan name. The display name comes from
Grok settings and the tier from the user subscription include.

* Refactor provider credential weight normalization tests and update API key structure

- Updated test cases in `providerWeightTransformers.test.ts` to reflect changes in API key structure from 'api-key-entries' to 'keys'.
- Introduced new tests in `providerWorkbenchUi.test.ts` to verify the editing surface of provider workbench.
- Modified `sponsorCustomEndpoint.test.ts` to adapt to the new API key structure.
- Adjusted `visualConfigAntigravitySensitiveWords.test.ts` and `visualConfigConcurrency.test.ts` to accommodate new YAML structure for sensitive words and concurrency settings.
- Enhanced `visualConfigDefaults.test.ts` to ensure proper handling of boolean defaults and YAML structure.
- Updated `visualConfigDevinSensitiveWords.test.ts` to reflect changes in the YAML structure for sensitive words.
- Refined `visualConfigDisableImageGeneration.test.ts` to align with new multimedia settings.
- Added comprehensive tests in `visualConfigV8.test.ts` to validate the v8 visual config contract and ensure scalar read/write path parity.
- Improved validation tests in `visualConfigValidation.test.ts` to handle new routing and requests structure.
- Cleaned up `xaiApiKeyProvider.test.ts` by removing unnecessary API client mocks and focusing on normalization of the xAI API key contract.

* fix(config): prevent v8 configuration mutation regressions

* feat(proxy): add proxyUrl support across various components and API calls

* feat: implement model discovery merging and enhance provider forms

- Add mergeDiscoveredModels function to deduplicate and merge model entries based on incoming data.
- Refactor BaseProviderForm and SponsorProviderForm to utilize mergeDiscoveredModels for applying discovered models.
- Update ModelEntryInput type to include sourceIndex for better tracking of model identities.
- Enhance tests for provider model discovery to ensure correct merging behavior and metadata preservation.
- Modify serialization logic in providers API to handle new model structure and sourceIndex.
- Improve YAML AST handling in visual config tests to ensure accurate condition management.

* feat(cooldown): implement cooldown reset functionality and UI updates

* feat(authFileCard): add disabled state styling and update tests for clarity

* refactor: reorganize logs feature structure and improve log handling

- Deleted the `useLogScroller` hook and its associated logic to streamline log scrolling functionality.
- Moved `LogsPage` component to a new directory structure under `features/logs`.
- Updated API service for logs to enhance error handling and response normalization.
- Introduced new tests for log buffer, log parsing, and log selectors to ensure robust functionality.
- Added localization tests to verify translation consistency across multiple languages.
- Improved log request handling and lifecycle management in the logs controller.
- Enhanced log scrolling tests to validate anchor adjustments and pending log line counts.

* feat(logs): enhance log viewer with line wrapping and improved filter options

- Added a toggle for wrapping long log lines in the LogsPage component.
- Updated the structured filters panel to open in a modal for better accessibility.
- Improved accessibility features with appropriate ARIA attributes.
- Refactored log rendering logic to support new wrapping functionality.
- Updated translations for filter and display settings in multiple languages.
- Enhanced styles for better layout and responsiveness in log viewer.
- Added tests to ensure layout integrity and functionality of new features.

* feat(logs): update styles for improved layout and toolbar controls

* feat(i18n): add legacy backend error messages in Chinese and Traditional Chinese

feat(api): implement config patching logic for managing configuration changes

feat(api): add legacy backend probe for diagnosing v0 management support

fix(auth): handle legacy backend detection during login process

test: add tests for config patching and legacy backend probe functionality

test: implement visual config rebase tests for maintaining draft state

* fix(config): recover confirmed list edits without replaying stale indexes

* fix(config): block conflicting payload replacements after recovery

* fix(logs): respect handled Escape events in fullscreen

* fix(quota): load xAI subscription after quota and preserve remaining amounts

---------

Co-authored-by: Supra4E8C <tan13318527080@163.com>
2026-10-01 01:32:15 +08:00

178 lines
5.6 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
import type { TFunction } from 'i18next';
import { XAI_CONFIG } from '@/features/quota/providers/xai/data';
import { apiCallApi, type ApiCallRequest, type ApiCallResult } from '@/services/api';
import {
XAI_API_CHAT_URL,
XAI_API_ME_URL,
XAI_BILLING_MONTHLY_URL,
XAI_BILLING_WEEKLY_URL,
isPaidXaiAuthFile,
} from '@/utils/quota';
const t = ((key: string) => key) as unknown as TFunction;
const originalApiCallRequest = apiCallApi.request;
const result = (statusCode: number, body: unknown = null): ApiCallResult => ({
statusCode,
header: {},
bodyText: body === null ? '' : JSON.stringify(body),
body,
});
const encodeJwt = (payload: Record<string, unknown>): string => {
const encoded = btoa(JSON.stringify(payload))
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
return `header.${encoded}.signature`;
};
describe('xAI paid OAuth quota fallback', () => {
let requests: ApiCallRequest[];
beforeEach(() => {
requests = [];
});
afterEach(() => {
apiCallApi.request = originalApiCallRequest;
});
test('recognizes paid credentials without trusting individual route hints', () => {
expect(isPaidXaiAuthFile({ name: 'paid-route.json', using_api: true, prefix: 'PAID' })).toBe(
true
);
expect(
isPaidXaiAuthFile({
name: 'tier.json',
metadata: { access_token: encodeJwt({ tier: 1 }) },
})
).toBe(true);
expect(isPaidXaiAuthFile({ name: 'using-api-only.json', using_api: true })).toBe(false);
expect(isPaidXaiAuthFile({ name: 'prefix-only.json', prefix: 'paid' })).toBe(false);
expect(isPaidXaiAuthFile({ name: 'free.json', using_api: false })).toBe(false);
expect(isPaidXaiAuthFile({ name: 'free-default.json', base_url: 'https://api.x.ai/v1' })).toBe(
false
);
});
test('skips free billing endpoints for a recognized paid list entry', async () => {
apiCallApi.request = async (payload) => {
requests.push(payload);
if (payload.url === XAI_API_ME_URL) {
return result(200, { user_id: 'user-1', team_id: 'team-1' });
}
return result(200, { choices: [] });
};
const summary = await XAI_CONFIG.fetchQuota(
{
name: 'paid.json',
type: 'xai',
auth_index: 'xai:1',
using_api: true,
prefix: 'paid',
},
t
);
expect(requests.map((request) => request.url)).toEqual([XAI_API_ME_URL, XAI_API_CHAT_URL]);
const chatRequest = requests.find((request) => request.url === XAI_API_CHAT_URL);
expect(JSON.parse(chatRequest?.data ?? '{}')).toMatchObject({
model: 'grok-4.5',
max_tokens: 1,
stream: false,
});
expect(summary).toMatchObject({
mode: 'paid-health',
source: 'api.x.ai-fallback',
planType: 'paid',
healthStatus: 'chat-ok',
userId: 'user-1',
teamId: 'team-1',
});
});
test('keeps existing billing behavior when only one route hint is present', async () => {
apiCallApi.request = async (payload) => {
requests.push(payload);
if (payload.url === XAI_BILLING_WEEKLY_URL) {
return result(200, {
config: {
currentPeriod: { type: 'weekly' },
creditUsagePercent: 25,
},
});
}
if (payload.url === XAI_BILLING_MONTHLY_URL) {
return result(200, {
config: {
monthlyLimit: { val: 10000 },
used: { val: 2500 },
billingPeriodStart: '2026-07-01T00:00:00Z',
billingPeriodEnd: '2026-08-01T00:00:00Z',
},
});
}
throw new Error(`Unexpected URL: ${payload.url}`);
};
const summary = await XAI_CONFIG.fetchQuota(
{ name: 'free.json', type: 'xai', auth_index: 'xai:3', using_api: true },
t
);
expect(requests.map((request) => request.url).sort()).toEqual(
[XAI_BILLING_WEEKLY_URL, XAI_BILLING_MONTHLY_URL].sort()
);
expect(summary).toMatchObject({
mode: 'billing',
source: 'cli-chat-proxy',
periodType: 'weekly',
usagePercent: 25,
monthlyLimitCents: 10000,
billingPeriodEnd: '2026-08-01T00:00:00Z',
resetAtMs: null,
periodHours: null,
});
expect(summary.periodStart).toBeUndefined();
expect(summary.periodEnd).toBeUndefined();
});
test('falls back to paid health after both free billing probes fail', async () => {
apiCallApi.request = async (payload) => {
requests.push(payload);
if (payload.url === XAI_BILLING_WEEKLY_URL || payload.url === XAI_BILLING_MONTHLY_URL) {
return result(403, { error: 'Access denied' });
}
if (payload.url === XAI_API_ME_URL) return result(200, { user_id: 'paid-user' });
return result(200, { choices: [] });
};
const summary = await XAI_CONFIG.fetchQuota(
{ name: 'unknown.json', type: 'xai', auth_index: 'xai:4' },
t
);
expect(requests.map((request) => request.url)).toEqual([
XAI_BILLING_WEEKLY_URL,
XAI_BILLING_MONTHLY_URL,
XAI_API_ME_URL,
XAI_API_CHAT_URL,
]);
expect(summary).toMatchObject({ mode: 'paid-health', userId: 'paid-user' });
});
test('preserves the billing error when the paid fallback also fails', async () => {
apiCallApi.request = async (payload) => {
if (payload.url === XAI_API_CHAT_URL) return result(401, { error: 'Invalid token' });
return result(403, { error: 'Access denied' });
};
await expect(
XAI_CONFIG.fetchQuota({ name: 'invalid.json', type: 'xai', auth_index: 'xai:5' }, t)
).rejects.toMatchObject({ status: 403 });
});
});