mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +00:00
fix(cliproxy): preserve manual quota pauses
This commit is contained in:
1 parent
62a4d44b58
commit
6ecca9b16f
3 files changed
+70
-1
No files matched your search
@@ -21,6 +21,7 @@ import {
|
|||||||
restoreExpiredQuotaPauses,
|
restoreExpiredQuotaPauses,
|
||||||
} from '../../accounts/account-safety';
|
} from '../../accounts/account-safety';
|
||||||
import { sanitizeEmail } from '../../auth/auth-utils';
|
import { sanitizeEmail } from '../../auth/auth-utils';
|
||||||
|
import { pauseAccount } from '../registry';
|
||||||
|
|
||||||
// Setup test isolation
|
// Setup test isolation
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
@@ -1228,6 +1229,65 @@ describe('Quota Exhaustion Handlers', () => {
|
|||||||
expect(fs.existsSync(path.join(tmpDir, '.ccs', 'cliproxy', 'quota-paused.json'))).toBe(false);
|
expect(fs.existsSync(path.join(tmpDir, '.ccs', 'cliproxy', 'quota-paused.json'))).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not auto-resume when a user manually re-pauses a quota-paused account', async () => {
|
||||||
|
writeRegistry({
|
||||||
|
agy: {
|
||||||
|
default: 'cooldown@gmail.com',
|
||||||
|
accounts: {
|
||||||
|
'cooldown@gmail.com': {
|
||||||
|
email: 'cooldown@gmail.com',
|
||||||
|
tokenFile: 'agy-cooldown.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
writeAuthToken('agy-cooldown.json', {
|
||||||
|
type: 'agy',
|
||||||
|
email: 'cooldown@gmail.com',
|
||||||
|
access_token: 'token',
|
||||||
|
});
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
expect(pauseAccountForQuotaCooldown('agy', 'cooldown@gmail.com', 5, now)).toBe(true);
|
||||||
|
|
||||||
|
const registryPath = path.join(tmpDir, '.ccs', 'cliproxy', 'accounts.json');
|
||||||
|
const quotaPausedPath = path.join(tmpDir, '.ccs', 'cliproxy', 'quota-paused.json');
|
||||||
|
const originalPausedAt = '2026-01-01T00:00:00.000Z';
|
||||||
|
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8')) as {
|
||||||
|
providers: {
|
||||||
|
agy: {
|
||||||
|
accounts: Record<string, { paused?: boolean; pausedAt?: string }>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const quotaPaused = JSON.parse(fs.readFileSync(quotaPausedPath, 'utf8')) as {
|
||||||
|
entries: Array<{ pausedAt: string }>;
|
||||||
|
};
|
||||||
|
registry.providers.agy.accounts['cooldown@gmail.com'].pausedAt = originalPausedAt;
|
||||||
|
quotaPaused.entries[0].pausedAt = originalPausedAt;
|
||||||
|
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2));
|
||||||
|
fs.writeFileSync(quotaPausedPath, JSON.stringify(quotaPaused, null, 2));
|
||||||
|
|
||||||
|
expect(pauseAccount('agy', 'cooldown@gmail.com')).toBe(true);
|
||||||
|
|
||||||
|
const refreshedRegistry = JSON.parse(
|
||||||
|
fs.readFileSync(registryPath, 'utf8')
|
||||||
|
) as typeof registry;
|
||||||
|
expect(refreshedRegistry.providers.agy.accounts['cooldown@gmail.com'].pausedAt).not.toBe(
|
||||||
|
originalPausedAt
|
||||||
|
);
|
||||||
|
|
||||||
|
const resumed = restoreExpiredQuotaPauses(now + 6 * 60 * 1000);
|
||||||
|
const { getAccount } = await import('../account-manager');
|
||||||
|
|
||||||
|
expect(resumed).toBe(0);
|
||||||
|
expect(getAccount('agy', 'cooldown@gmail.com')?.paused).toBe(true);
|
||||||
|
expect(
|
||||||
|
fs.existsSync(path.join(tmpDir, '.ccs', 'cliproxy', 'auth-paused', 'agy-cooldown.json'))
|
||||||
|
).toBe(true);
|
||||||
|
expect(fs.existsSync(quotaPausedPath)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('does not auto-resume quota-paused accounts when pausedAt metadata is missing', async () => {
|
it('does not auto-resume quota-paused accounts when pausedAt metadata is missing', async () => {
|
||||||
writeRegistry({
|
writeRegistry({
|
||||||
agy: {
|
agy: {
|
||||||
|
|||||||
@@ -660,6 +660,10 @@ export function pauseAccount(provider: CLIProxyProvider, accountId: string): boo
|
|||||||
|
|
||||||
const accountMeta = providerAccounts.accounts[accountId];
|
const accountMeta = providerAccounts.accounts[accountId];
|
||||||
if (accountMeta.paused) {
|
if (accountMeta.paused) {
|
||||||
|
// Treat an explicit pause request for an already paused account as a fresh
|
||||||
|
// manual decision. This changes the pause metadata so quota cooldown
|
||||||
|
// restore cannot later mistake the pause for its original auto-pause.
|
||||||
|
accountMeta.pausedAt = new Date().toISOString();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1056,8 +1056,13 @@ export async function handlePauseAccount(args: string[]): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (account.paused) {
|
if (account.paused) {
|
||||||
|
const refreshed = pauseAccount(provider, account.id);
|
||||||
|
const refreshedAccount = refreshed ? findAccountByQuery(provider, account.id) : account;
|
||||||
console.log(warn(`Account already paused: ${formatCliAccountLabel(account)}`));
|
console.log(warn(`Account already paused: ${formatCliAccountLabel(account)}`));
|
||||||
console.log(info(`Paused at: ${account.pausedAt || 'unknown'}`));
|
if (refreshed) {
|
||||||
|
console.log(info('Manual pause refreshed; account will stay out of quota rotation'));
|
||||||
|
}
|
||||||
|
console.log(info(`Paused at: ${refreshedAccount?.pausedAt || account.pausedAt || 'unknown'}`));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user