mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 10:15:40 +00:00
fix: cap native quota profile refresh fan-out (#1627)
* fix: cap native quota profile refresh fan-out * fix: preserve native quota live cache state
This commit is contained in:
1 parent
f2cd076c08
commit
0f9a87619c
2 files changed
+168
-78
No files matched your search
@@ -710,12 +710,72 @@ function codexProfileMatchesPausedAccount(
|
|||||||
/**
|
/**
|
||||||
* Tag a row with whether it is the surface's default profile (drives the
|
* Tag a row with whether it is the surface's default profile (drives the
|
||||||
* "active" badge only). The row's own `paused` flag is authoritative for
|
* "active" badge only). The row's own `paused` flag is authoritative for
|
||||||
* dimming: it is already set by the collector to reflect LIVENESS — parked
|
* dimming: live/default rows arrive with paused:false, while non-default
|
||||||
* (no on-disk creds / unsupported) rows arrive with paused:true; profiles with
|
* profiles are served cache-only and dimmed to avoid live-refresh fan-out.
|
||||||
* a usable token arrive with paused:false regardless of default status. We must
|
|
||||||
* NOT override `paused` from `isDefault`, or a valid non-default subscription
|
|
||||||
* (e.g. an isolated `ccsx` profile) would render dimmed despite live quota.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
function buildParkedClaudeProfileRow(profile: string, now: number): BarSummaryRow {
|
||||||
|
return {
|
||||||
|
account_id: `${SURFACE_CLAUDE}:${profile}`,
|
||||||
|
provider: CLAUDE_NATIVE_PROVIDER,
|
||||||
|
surface: SURFACE_CLAUDE,
|
||||||
|
profile,
|
||||||
|
is_subscription: true,
|
||||||
|
displayName: profile,
|
||||||
|
tier: null,
|
||||||
|
paused: true,
|
||||||
|
quota_percentage: null,
|
||||||
|
quotaStatus: 'unsupported',
|
||||||
|
next_reset: null,
|
||||||
|
is_default: false,
|
||||||
|
last_activity_at: null,
|
||||||
|
today_cost: null,
|
||||||
|
health: 'ok',
|
||||||
|
cached: false,
|
||||||
|
fetchedAt: new Date(now).toISOString(),
|
||||||
|
needsReauth: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildParkedCodexProfileRow(profile: string, now: number): BarSummaryRow {
|
||||||
|
return {
|
||||||
|
account_id: `${SURFACE_CODEX}:${profile}`,
|
||||||
|
provider: CODEX_NATIVE_PROVIDER,
|
||||||
|
surface: SURFACE_CODEX,
|
||||||
|
profile,
|
||||||
|
is_subscription: true,
|
||||||
|
displayName: profile,
|
||||||
|
tier: null,
|
||||||
|
paused: true,
|
||||||
|
quota_percentage: null,
|
||||||
|
quotaStatus: 'unsupported',
|
||||||
|
next_reset: null,
|
||||||
|
is_default: false,
|
||||||
|
last_activity_at: null,
|
||||||
|
today_cost: null,
|
||||||
|
health: 'ok',
|
||||||
|
cached: false,
|
||||||
|
fetchedAt: new Date(now).toISOString(),
|
||||||
|
needsReauth: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectCachedOrParkedProfileRow(
|
||||||
|
map: Map<string, ProviderState>,
|
||||||
|
profile: string,
|
||||||
|
now: number,
|
||||||
|
buildParkedRow: (profile: string, now: number) => BarSummaryRow
|
||||||
|
): BarSummaryRow {
|
||||||
|
const state = getState(map, profile);
|
||||||
|
if (state.cachedRow) {
|
||||||
|
return { ...state.cachedRow, cached: true, paused: true };
|
||||||
|
}
|
||||||
|
const parkedRow = buildParkedRow(profile, now);
|
||||||
|
state.cachedRow = parkedRow;
|
||||||
|
state.cachedAt = now;
|
||||||
|
return parkedRow;
|
||||||
|
}
|
||||||
|
|
||||||
function markDefault(row: BarSummaryRow, isDefault: boolean): BarSummaryRow {
|
function markDefault(row: BarSummaryRow, isDefault: boolean): BarSummaryRow {
|
||||||
return { ...row, is_default: isDefault };
|
return { ...row, is_default: isDefault };
|
||||||
}
|
}
|
||||||
@@ -734,10 +794,11 @@ function markDefaultAndSyncCache(
|
|||||||
isDefault: boolean
|
isDefault: boolean
|
||||||
): BarSummaryRow {
|
): BarSummaryRow {
|
||||||
const state = map.get(profile);
|
const state = map.get(profile);
|
||||||
|
const marked = markDefault(row, isDefault);
|
||||||
if (state?.cachedRow) {
|
if (state?.cachedRow) {
|
||||||
state.cachedRow = { ...state.cachedRow, is_default: isDefault };
|
state.cachedRow = { ...state.cachedRow, is_default: isDefault };
|
||||||
}
|
}
|
||||||
return markDefault(row, isDefault);
|
return marked;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function collectClaudeRowForProfile(
|
async function collectClaudeRowForProfile(
|
||||||
@@ -789,26 +850,7 @@ async function collectClaudeRowForProfile(
|
|||||||
// user has not logged in via 'ccs auth' for this machine or the credentials
|
// user has not logged in via 'ccs auth' for this machine or the credentials
|
||||||
// are stored only in keychain (which we deliberately do not access here).
|
// are stored only in keychain (which we deliberately do not access here).
|
||||||
if (!creds) {
|
if (!creds) {
|
||||||
const parkedRow: BarSummaryRow = {
|
const parkedRow = buildParkedClaudeProfileRow(profile, now);
|
||||||
account_id: `${SURFACE_CLAUDE}:${profile}`,
|
|
||||||
provider: CLAUDE_NATIVE_PROVIDER,
|
|
||||||
surface: SURFACE_CLAUDE,
|
|
||||||
profile,
|
|
||||||
is_subscription: true,
|
|
||||||
displayName: profile,
|
|
||||||
tier: null,
|
|
||||||
paused: true,
|
|
||||||
quota_percentage: null,
|
|
||||||
quotaStatus: 'unsupported',
|
|
||||||
next_reset: null,
|
|
||||||
is_default: false,
|
|
||||||
last_activity_at: null,
|
|
||||||
today_cost: null,
|
|
||||||
health: 'ok',
|
|
||||||
cached: false,
|
|
||||||
fetchedAt: new Date(now).toISOString(),
|
|
||||||
needsReauth: true,
|
|
||||||
};
|
|
||||||
// Cache the parked row so repeated calls don't re-stat the fs.
|
// Cache the parked row so repeated calls don't re-stat the fs.
|
||||||
state.cachedRow = parkedRow;
|
state.cachedRow = parkedRow;
|
||||||
state.cachedAt = now;
|
state.cachedAt = now;
|
||||||
@@ -1062,26 +1104,7 @@ async function collectCodexRowForProfile(
|
|||||||
// profile.
|
// profile.
|
||||||
const stale = serveCached(state);
|
const stale = serveCached(state);
|
||||||
if (stale) return stale;
|
if (stale) return stale;
|
||||||
const parkedRow: BarSummaryRow = {
|
const parkedRow = buildParkedCodexProfileRow(profile, now);
|
||||||
account_id: `${SURFACE_CODEX}:${profile}`,
|
|
||||||
provider: CODEX_NATIVE_PROVIDER,
|
|
||||||
surface: SURFACE_CODEX,
|
|
||||||
profile,
|
|
||||||
is_subscription: true,
|
|
||||||
displayName: profile,
|
|
||||||
tier: null,
|
|
||||||
paused: true,
|
|
||||||
quota_percentage: null,
|
|
||||||
quotaStatus: 'unsupported',
|
|
||||||
next_reset: null,
|
|
||||||
is_default: false,
|
|
||||||
last_activity_at: null,
|
|
||||||
today_cost: null,
|
|
||||||
health: 'ok',
|
|
||||||
cached: false,
|
|
||||||
fetchedAt: new Date(now).toISOString(),
|
|
||||||
needsReauth: true,
|
|
||||||
};
|
|
||||||
state.cachedRow = parkedRow;
|
state.cachedRow = parkedRow;
|
||||||
state.cachedAt = now;
|
state.cachedAt = now;
|
||||||
return parkedRow;
|
return parkedRow;
|
||||||
@@ -1545,30 +1568,62 @@ async function getNativeAccountRowsMultiProfile(
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
const tasks: Promise<BarSummaryRow | null>[] = [];
|
const tasks: Promise<BarSummaryRow | null>[] = [];
|
||||||
|
const results: (BarSummaryRow | null)[] = [];
|
||||||
|
const now = (deps.now ?? Date.now)();
|
||||||
|
|
||||||
// Forced refresh applies to every profile: parked profiles (no creds) short-
|
// Preserve the safety budget: only the active/default profile for each surface
|
||||||
// circuit to a parked row with zero network, so forcing them is free, while
|
// may perform a live refresh. Non-default profiles are cache-only (or parked)
|
||||||
// every profile that has a usable token gets live quota — not just the
|
// so one /summary request can trigger at most one Claude and one Codex live
|
||||||
// default. Per-profile TTL + breaker still protect each account.
|
// upstream call regardless of configured profile count.
|
||||||
for (const p of claudeProfiles) {
|
for (const p of claudeProfiles) {
|
||||||
const isDefault = p === claudeDefault;
|
const isDefault = p === claudeDefault;
|
||||||
|
if (!isDefault) {
|
||||||
|
results.push(
|
||||||
|
markDefaultAndSyncCache(
|
||||||
|
claudeProfileStates,
|
||||||
|
p,
|
||||||
|
collectCachedOrParkedProfileRow(claudeProfileStates, p, now, buildParkedClaudeProfileRow),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
tasks.push(
|
tasks.push(
|
||||||
collectClaudeRowForProfile(p, deps, force)
|
collectClaudeRowForProfile(
|
||||||
.then((r) => (r ? markDefaultAndSyncCache(claudeProfileStates, p, r, isDefault) : null))
|
p,
|
||||||
|
deps,
|
||||||
|
force || claudeProfileStates.get(p)?.cachedRow?.quotaStatus === 'unsupported'
|
||||||
|
)
|
||||||
|
.then((r) => (r ? markDefaultAndSyncCache(claudeProfileStates, p, r, true) : null))
|
||||||
.catch(() => null)
|
.catch(() => null)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const p of codexProfiles) {
|
for (const p of codexProfiles) {
|
||||||
const isDefault = p === codexDefault;
|
const isDefault = p === codexDefault;
|
||||||
|
if (!isDefault) {
|
||||||
|
results.push(
|
||||||
|
markDefaultAndSyncCache(
|
||||||
|
codexProfileStates,
|
||||||
|
p,
|
||||||
|
collectCachedOrParkedProfileRow(codexProfileStates, p, now, buildParkedCodexProfileRow),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
tasks.push(
|
tasks.push(
|
||||||
collectCodexRowForProfile(p, deps, force)
|
collectCodexRowForProfile(
|
||||||
.then((r) => (r ? markDefaultAndSyncCache(codexProfileStates, p, r, isDefault) : null))
|
p,
|
||||||
|
deps,
|
||||||
|
force || codexProfileStates.get(p)?.cachedRow?.quotaStatus === 'unsupported'
|
||||||
|
)
|
||||||
|
.then((r) => (r ? markDefaultAndSyncCache(codexProfileStates, p, r, true) : null))
|
||||||
.catch(() => null)
|
.catch(() => null)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await Promise.all(tasks);
|
results.push(...(await Promise.all(tasks)));
|
||||||
const rows = results.filter((r): r is BarSummaryRow => r !== null);
|
const rows = results.filter((r): r is BarSummaryRow => r !== null);
|
||||||
|
|
||||||
// Sort by (surface, profile) for stable ordering.
|
// Sort by (surface, profile) for stable ordering.
|
||||||
|
|||||||
@@ -989,7 +989,7 @@ describe('getCachedNativeAccountRows (instant, no-fetch fallback)', () => {
|
|||||||
*
|
*
|
||||||
* - claudeProfiles: profile names for the Claude surface (ccs)
|
* - claudeProfiles: profile names for the Claude surface (ccs)
|
||||||
* - codexProfiles: profile names for the Codex surface (ccsx)
|
* - codexProfiles: profile names for the Codex surface (ccsx)
|
||||||
* - claudeDefault / codexDefault: the active profile per surface (paused:false)
|
* - claudeDefault / codexDefault: the only live-polled profile per surface (paused:false)
|
||||||
* - credsForProfile: map from profile name to credentials (null = parked)
|
* - credsForProfile: map from profile name to credentials (null = parked)
|
||||||
* - claudeFetch: network fetcher for Claude (all profiles share one implementation)
|
* - claudeFetch: network fetcher for Claude (all profiles share one implementation)
|
||||||
* - codexNativeAuth: map from profile name to {accessToken, accountId}
|
* - codexNativeAuth: map from profile name to {accessToken, accountId}
|
||||||
@@ -1113,13 +1113,13 @@ describe('multi-profile: account_id and wire fields', () => {
|
|||||||
expect(ck?.is_subscription).toBe(true);
|
expect(ck?.is_subscription).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('paused reflects liveness (creds present), NOT default-ness; is_default marks the default independently', async () => {
|
it('only default profiles are live-polled; is_default marks the default independently', async () => {
|
||||||
const clock = { now: 1_000_000 };
|
const clock = { now: 1_000_000 };
|
||||||
const deps = makeMultiProfileDeps({
|
const deps = makeMultiProfileDeps({
|
||||||
clock,
|
clock,
|
||||||
// Claude: work = default + creds (live); ck = non-default + NO creds (parked).
|
// Claude: work = default + creds (live); ck = non-default + NO creds (parked).
|
||||||
claudeProfiles: ['work', 'ck'],
|
claudeProfiles: ['work', 'ck'],
|
||||||
// Codex: personal = default + creds (live); ck = NON-default + creds (live).
|
// Codex: personal = default + creds (live); ck = NON-default + creds (cache-only).
|
||||||
codexProfiles: ['personal', 'ck'],
|
codexProfiles: ['personal', 'ck'],
|
||||||
claudeDefault: 'work',
|
claudeDefault: 'work',
|
||||||
codexDefault: 'personal',
|
codexDefault: 'personal',
|
||||||
@@ -1146,11 +1146,11 @@ describe('multi-profile: account_id and wire fields', () => {
|
|||||||
expect(codexPersonal?.paused).toBe(false);
|
expect(codexPersonal?.paused).toBe(false);
|
||||||
expect(codexPersonal?.is_default).toBe(true);
|
expect(codexPersonal?.is_default).toBe(true);
|
||||||
|
|
||||||
// Codex ck: NON-default but HAS creds -> LIVE, NOT dimmed. This is the key
|
// Codex ck: NON-default with creds is cache-only/parked. Only the default
|
||||||
// correctness guarantee: a valid isolated subscription is never dimmed just
|
// profile is live-polled so a dashboard request cannot fan out to every
|
||||||
// because it is not the surface default.
|
// configured subscription account.
|
||||||
const codexCk = rows.find((r) => r.surface === 'ccsx' && r.profile === 'ck');
|
const codexCk = rows.find((r) => r.surface === 'ccsx' && r.profile === 'ck');
|
||||||
expect(codexCk?.paused).toBe(false);
|
expect(codexCk?.paused).toBe(true);
|
||||||
expect(codexCk?.is_default).toBe(false);
|
expect(codexCk?.is_default).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1170,6 +1170,8 @@ describe('multi-profile: account_id and wire fields', () => {
|
|||||||
|
|
||||||
const rows = await getNativeAccountRows(deps);
|
const rows = await getNativeAccountRows(deps);
|
||||||
expect(rows.length).toBe(claudeProfiles.length + codexProfiles.length);
|
expect(rows.length).toBe(claudeProfiles.length + codexProfiles.length);
|
||||||
|
expect(deps.claudeFetchCount()).toBe(1);
|
||||||
|
expect(deps.codexNetworkCount()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rows are sorted by (surface, profile)', async () => {
|
it('rows are sorted by (surface, profile)', async () => {
|
||||||
@@ -1264,7 +1266,7 @@ describe('multi-profile: per-profile circuit breaker isolation', () => {
|
|||||||
claudeDefault: 'work',
|
claudeDefault: 'work',
|
||||||
credsForProfile: () => maxCreds(),
|
credsForProfile: () => maxCreds(),
|
||||||
claudeFetch: async (_token, accountId) => {
|
claudeFetch: async (_token, accountId) => {
|
||||||
// 'work' (ccs:work) always 429s; 'ck' always succeeds
|
// 'work' (ccs:work) always 429s; 'ck' remains cache-only
|
||||||
if (accountId?.includes('work') && workFails) {
|
if (accountId?.includes('work') && workFails) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -1294,8 +1296,8 @@ describe('multi-profile: per-profile circuit breaker isolation', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// After the three 429s on 'work', check that 'ck' still succeeds.
|
// After the three 429s on 'work', check that 'ck' remains cache-only.
|
||||||
// We reset state to have a clean run where 'ck' has no prior breaker history.
|
// We reset state to have a clean run with no prior breaker history.
|
||||||
resetNativeQuotaState();
|
resetNativeQuotaState();
|
||||||
clock.now += MAX_COOLDOWN_JUMP_MP;
|
clock.now += MAX_COOLDOWN_JUMP_MP;
|
||||||
workFails = false;
|
workFails = false;
|
||||||
@@ -1304,8 +1306,8 @@ describe('multi-profile: per-profile circuit breaker isolation', () => {
|
|||||||
const ckRow = rows.find((r) => r.profile === 'ck');
|
const ckRow = rows.find((r) => r.profile === 'ck');
|
||||||
const workRow = rows.find((r) => r.profile === 'work');
|
const workRow = rows.find((r) => r.profile === 'work');
|
||||||
|
|
||||||
// 'ck' should succeed — its breaker was never tripped.
|
// 'ck' stays cache-only, independent of work's breaker.
|
||||||
expect(ckRow?.quotaStatus).toBe('ok');
|
expect(ckRow?.quotaStatus).toBe('unsupported');
|
||||||
// 'work' is also fine after reset (no breaker state).
|
// 'work' is also fine after reset (no breaker state).
|
||||||
expect(workRow?.quotaStatus).toBe('ok');
|
expect(workRow?.quotaStatus).toBe('ok');
|
||||||
});
|
});
|
||||||
@@ -1339,10 +1341,10 @@ describe('multi-profile: per-profile circuit breaker isolation', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// First call: 'work' gets a 429, 'ck' succeeds.
|
// First call: 'work' gets a 429, 'ck' remains cache-only.
|
||||||
const rows1 = await getNativeAccountRows(deps);
|
const rows1 = await getNativeAccountRows(deps);
|
||||||
const ck1 = rows1.find((r) => r.profile === 'ck');
|
const ck1 = rows1.find((r) => r.profile === 'ck');
|
||||||
expect(ck1?.quotaStatus).toBe('ok');
|
expect(ck1?.quotaStatus).toBe('unsupported');
|
||||||
expect(workCall429Count).toBeGreaterThanOrEqual(1);
|
expect(workCall429Count).toBeGreaterThanOrEqual(1);
|
||||||
|
|
||||||
// Skip past cooldown for 'work' only; 'ck' is within TTL.
|
// Skip past cooldown for 'work' only; 'ck' is within TTL.
|
||||||
@@ -1351,8 +1353,8 @@ describe('multi-profile: per-profile circuit breaker isolation', () => {
|
|||||||
// Second call past 'work' cooldown: work tries again (429 again); ck cached.
|
// Second call past 'work' cooldown: work tries again (429 again); ck cached.
|
||||||
const rows2 = await getNativeAccountRows(deps);
|
const rows2 = await getNativeAccountRows(deps);
|
||||||
const ck2 = rows2.find((r) => r.profile === 'ck');
|
const ck2 = rows2.find((r) => r.profile === 'ck');
|
||||||
// 'ck' still has a good cached row.
|
// 'ck' remains cache-only and is not affected by work's breaker.
|
||||||
expect(ck2?.quotaStatus).toBe('ok');
|
expect(ck2?.quotaStatus).toBe('unsupported');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1449,11 +1451,11 @@ describe('review focus areas: reauth caching + codex local fallback', () => {
|
|||||||
const r1 = first.find((r) => r.profile === 'ck');
|
const r1 = first.find((r) => r.profile === 'ck');
|
||||||
expect(r1?.needsReauth).toBe(true);
|
expect(r1?.needsReauth).toBe(true);
|
||||||
expect(r1?.paused).toBe(true);
|
expect(r1?.paused).toBe(true);
|
||||||
expect(deps.codexNetworkCount()).toBe(1);
|
expect(deps.codexNetworkCount()).toBe(0);
|
||||||
|
|
||||||
const second = await getNativeAccountRows(deps, { force: true });
|
const second = await getNativeAccountRows(deps, { force: true });
|
||||||
expect(second.find((r) => r.profile === 'ck')?.cached).toBe(true);
|
expect(second.find((r) => r.profile === 'ck')?.cached).toBe(true);
|
||||||
expect(deps.codexNetworkCount()).toBe(1);
|
expect(deps.codexNetworkCount()).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Codex named profile without on-disk auth is parked, never filled from global local data', async () => {
|
it('Codex named profile without on-disk auth is parked, never filled from global local data', async () => {
|
||||||
@@ -1563,7 +1565,7 @@ describe('review focus areas: reauth caching + codex local fallback', () => {
|
|||||||
expect(deps.claudeFetchCount()).toBe(1); // re-checked -> fetched
|
expect(deps.claudeFetchCount()).toBe(1); // re-checked -> fetched
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Codex named profile with valid auth but sparse payload stays active, not parked', async () => {
|
it('Codex named profile with valid auth but sparse payload stays parked when not default', async () => {
|
||||||
resetNativeQuotaState();
|
resetNativeQuotaState();
|
||||||
const clock = { now: 7_000_000 };
|
const clock = { now: 7_000_000 };
|
||||||
const deps = makeMultiProfileDeps({
|
const deps = makeMultiProfileDeps({
|
||||||
@@ -1579,9 +1581,42 @@ describe('review focus areas: reauth caching + codex local fallback', () => {
|
|||||||
const rows = await getNativeAccountRows(deps);
|
const rows = await getNativeAccountRows(deps);
|
||||||
const ck = rows.find((r) => r.profile === 'ck');
|
const ck = rows.find((r) => r.profile === 'ck');
|
||||||
expect(ck).toBeDefined();
|
expect(ck).toBeDefined();
|
||||||
expect(ck?.paused).toBe(false); // active subscription, not parked
|
expect(ck?.paused).toBe(true); // cache-only, parked until selected as default
|
||||||
expect(ck?.needsReauth).toBe(false);
|
expect(ck?.needsReauth).toBe(true);
|
||||||
expect(ck?.quotaStatus).toBe('ok');
|
expect(ck?.quotaStatus).toBe('unsupported');
|
||||||
expect(ck?.quota_percentage).toBeNull(); // no windows, but still active
|
expect(ck?.quota_percentage).toBeNull(); // no windows while parked
|
||||||
|
});
|
||||||
|
|
||||||
|
it('non-default cache-only rows do not overwrite the canonical live cache', async () => {
|
||||||
|
resetNativeQuotaState();
|
||||||
|
const clock = { now: 8_000_000 };
|
||||||
|
let codexDefault = 'ck';
|
||||||
|
const deps = makeMultiProfileDeps({
|
||||||
|
clock,
|
||||||
|
claudeProfiles: [],
|
||||||
|
codexProfiles: ['default', 'ck'],
|
||||||
|
codexDefault,
|
||||||
|
codexNativeAuth: (p) => ({ accessToken: `t-${p}`, accountId: `id-${p}` }),
|
||||||
|
codexNetworkFetch: async () => codexSuccessQuota(),
|
||||||
|
});
|
||||||
|
deps.defaultCodexProfile = () => codexDefault;
|
||||||
|
|
||||||
|
const first = await getNativeAccountRows(deps);
|
||||||
|
expect(first.find((r) => r.profile === 'ck')?.paused).toBe(false);
|
||||||
|
expect(deps.codexNetworkCount()).toBe(1);
|
||||||
|
|
||||||
|
codexDefault = 'default';
|
||||||
|
const second = await getNativeAccountRows(deps);
|
||||||
|
const ckAsNonDefault = second.find((r) => r.profile === 'ck');
|
||||||
|
expect(ckAsNonDefault?.cached).toBe(true);
|
||||||
|
expect(ckAsNonDefault?.paused).toBe(true);
|
||||||
|
expect(deps.codexNetworkCount()).toBe(2);
|
||||||
|
|
||||||
|
codexDefault = 'ck';
|
||||||
|
const third = await getNativeAccountRows(deps);
|
||||||
|
const ckDefaultAgain = third.find((r) => r.profile === 'ck');
|
||||||
|
expect(ckDefaultAgain?.cached).toBe(true);
|
||||||
|
expect(ckDefaultAgain?.paused).toBe(false);
|
||||||
|
expect(deps.codexNetworkCount()).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user