Files
EasyCLIProxyAPI/tests/quotaCache.test.ts
T

76 lines
3.2 KiB
TypeScript

import { describe, expect, it } from 'bun:test';
import {
captureQuotaCacheGeneration,
commitQuotaCacheIfCurrent,
getQuotaCacheSnapshot,
pruneQuotaCache,
pruneQuotaCacheNamespace,
refreshQuotaCacheEntries,
updateQuotaCache,
} from '../src/services/quotaCache';
import type { QuotaState } from '../src/services/quotaService';
describe('额度跨页面缓存', () => {
it('保留仍存在的认证文件额度并清理失效项', () => {
updateQuotaCache({
first: { status: 'success', rows: [], fetchedAt: 1 },
removed: { status: 'error', rows: [], error: 'old' },
});
pruneQuotaCache(new Set(['first']));
expect(getQuotaCacheSnapshot()).toEqual({
first: { status: 'success', rows: [], fetchedAt: 1 },
});
});
it('认证文件集合变化后拒绝过期请求写回', () => {
updateQuotaCache({
stale: { status: 'loading', rows: [] },
retained: { status: 'loading', rows: [] },
});
const generation = captureQuotaCacheGeneration();
pruneQuotaCache(new Set(['retained']));
let committed = false;
expect(commitQuotaCacheIfCurrent(generation, () => {
committed = true;
})).toBe(false);
expect(committed).toBe(false);
expect(getQuotaCacheSnapshot().retained).toEqual({ status: 'idle', rows: [] });
});
it.each(['oauth', 'api'])('keeps the other namespace running when %s is reloaded during refresh all', async (reloaded) => {
updateQuotaCache({});
let finishOAuth!: (result: QuotaState) => void;
let finishApi!: (result: QuotaState) => void;
const refresh = refreshQuotaCacheEntries([
{ key: 'oauth', query: () => new Promise((resolve) => { finishOAuth = resolve; }) },
{ key: 'api-quota::one', query: () => new Promise((resolve) => { finishApi = resolve; }) },
]);
if (reloaded === 'oauth') pruneQuotaCache(new Set(['oauth']));
else pruneQuotaCacheNamespace('api-quota::', new Set(['api-quota::one']));
finishOAuth({ status: 'success', rows: [], plan: 'OAuth' });
finishApi({ status: 'success', rows: [], plan: 'API' });
await refresh;
expect(getQuotaCacheSnapshot().oauth.status).toBe(reloaded === 'oauth' ? 'idle' : 'success');
expect(getQuotaCacheSnapshot()['api-quota::one'].status).toBe(reloaded === 'api' ? 'idle' : 'success');
expect(Object.values(getQuotaCacheSnapshot()).some((quota) => quota.status === 'loading')).toBe(false);
});
it('continues queued API requests after OAuth changes and skips obsolete queued OAuth requests', async () => {
updateQuotaCache({});
let finishFirst!: (result: QuotaState) => void;
const called: string[] = [];
const refresh = refreshQuotaCacheEntries([
{ key: 'oauth-first', query: () => new Promise((resolve) => { finishFirst = resolve; }) },
{ key: 'oauth-next', query: async () => { called.push('OAuth'); return { status: 'success', rows: [] }; } },
{ key: 'api-quota::next', query: async () => { called.push('API'); return { status: 'success', rows: [] }; } },
], 1);
pruneQuotaCache(new Set(['oauth-first', 'oauth-next']));
finishFirst({ status: 'success', rows: [] });
await refresh;
expect(called).toEqual(['API']);
expect(getQuotaCacheSnapshot()['api-quota::next'].status).toBe('success');
});
});