agent-ecosystem/test/features/codex-account/renderer/useCodexAccountSnapshot.test.ts
777genius e9cebe64ff feat: improve provider status startup hydration
Keep connected provider details visible while refreshes are in flight, restore reusable provider status UI, and separate fast startup summaries from heavier provider hydration. Replace the fixed 30s startup wait with an idle-aware scheduler with a 30s safety cap and cover the Electron timer binding crash.
2026-05-24 00:23:04 +03:00

517 lines
15 KiB
TypeScript

import React, { act } from 'react';
import { createRoot } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useCodexAccountSnapshot } from '../../../../src/features/codex-account/renderer/hooks/useCodexAccountSnapshot';
import type { CodexAccountSnapshotDto } from '@features/codex-account/contracts';
const apiMocks = vi.hoisted(() => ({
getCodexAccountSnapshot: vi.fn(),
refreshCodexAccountSnapshot: vi.fn(),
startCodexChatgptLogin: vi.fn(),
cancelCodexChatgptLogin: vi.fn(),
logoutCodexAccount: vi.fn(),
onCodexAccountSnapshotChanged: vi.fn(() => () => undefined),
}));
type IdleCallbackForTest = (deadline: {
didTimeout: boolean;
timeRemaining: () => number;
}) => void;
vi.mock('@renderer/api', () => ({
api: apiMocks,
isElectronMode: () => true,
}));
function createSnapshot(): CodexAccountSnapshotDto {
return {
preferredAuthMode: 'chatgpt',
effectiveAuthMode: 'chatgpt',
launchAllowed: true,
launchIssueMessage: null,
launchReadinessState: 'ready_chatgpt',
appServerState: 'healthy',
appServerStatusMessage: null,
managedAccount: {
type: 'chatgpt',
email: 'belief@example.com',
planType: 'pro',
},
apiKey: {
available: false,
source: null,
sourceLabel: null,
},
requiresOpenaiAuth: false,
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: {
limitId: 'codex',
limitName: null,
primary: {
usedPercent: 77,
windowDurationMins: 300,
resetsAt: 1_776_678_034,
},
secondary: null,
credits: {
hasCredits: false,
unlimited: false,
balance: '0',
},
planType: 'pro',
},
updatedAt: new Date().toISOString(),
};
}
function createDeferred<T>() {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<T>((nextResolve, nextReject) => {
resolve = nextResolve;
reject = nextReject;
});
return { promise, resolve, reject };
}
describe('useCodexAccountSnapshot', () => {
beforeEach(() => {
vi.clearAllMocks();
(
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
vi.useRealTimers();
});
afterEach(() => {
document.body.innerHTML = '';
Reflect.deleteProperty(window, 'requestIdleCallback');
Reflect.deleteProperty(window, 'cancelIdleCallback');
});
it('loads the initial Codex snapshot through refresh when rate limits are requested', async () => {
const snapshot = createSnapshot();
const refreshDeferred = createDeferred<CodexAccountSnapshotDto>();
apiMocks.refreshCodexAccountSnapshot.mockReturnValue(refreshDeferred.promise);
apiMocks.getCodexAccountSnapshot.mockResolvedValue(snapshot);
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
function Harness(): React.ReactElement {
const state = useCodexAccountSnapshot({
enabled: true,
includeRateLimits: true,
});
return React.createElement('div', null, state.snapshot?.managedAccount?.email ?? 'empty');
}
await act(async () => {
root.render(React.createElement(Harness));
refreshDeferred.resolve(snapshot);
await refreshDeferred.promise;
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledWith({
includeRateLimits: true,
});
expect(apiMocks.getCodexAccountSnapshot).not.toHaveBeenCalled();
expect(host.textContent).toContain('belief@example.com');
act(() => {
root.unmount();
});
});
it('can defer the initial Codex snapshot without starting interval refreshes first', async () => {
vi.useFakeTimers();
const snapshot = createSnapshot();
apiMocks.refreshCodexAccountSnapshot.mockResolvedValue(snapshot);
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
function Harness(): React.ReactElement {
const state = useCodexAccountSnapshot({
enabled: true,
includeRateLimits: true,
initialRefreshDelayMs: 30_000,
});
return React.createElement(
'div',
{ 'data-loading': state.loading ? 'true' : 'false' },
state.snapshot?.managedAccount?.email ?? 'empty'
);
}
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).not.toHaveBeenCalled();
expect(host.firstElementChild?.getAttribute('data-loading')).toBe('false');
await act(async () => {
vi.advanceTimersByTime(20_000);
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).not.toHaveBeenCalled();
await act(async () => {
vi.advanceTimersByTime(10_000);
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledTimes(1);
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledWith({
includeRateLimits: true,
});
expect(host.textContent).toContain('belief@example.com');
act(() => {
root.unmount();
});
});
it('uses idle scheduling for deferred initial Codex snapshots when a max delay is provided', async () => {
vi.useFakeTimers();
const snapshot = createSnapshot();
apiMocks.refreshCodexAccountSnapshot.mockResolvedValue(snapshot);
let idleCallback: IdleCallbackForTest = () => undefined;
const requestIdleCallback = vi.fn((callback, options?: { timeout?: number }) => {
idleCallback = callback;
expect(options).toEqual({ timeout: 28_000 });
return 7;
});
const cancelIdleCallback = vi.fn();
Object.defineProperty(window, 'requestIdleCallback', {
configurable: true,
value: requestIdleCallback,
});
Object.defineProperty(window, 'cancelIdleCallback', {
configurable: true,
value: cancelIdleCallback,
});
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
function Harness(): React.ReactElement {
const state = useCodexAccountSnapshot({
enabled: true,
includeRateLimits: true,
initialRefreshDelayMs: 2_000,
initialRefreshMaxDelayMs: 30_000,
});
return React.createElement('div', null, state.snapshot?.managedAccount?.email ?? 'empty');
}
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
await act(async () => {
vi.advanceTimersByTime(1_999);
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).not.toHaveBeenCalled();
expect(requestIdleCallback).not.toHaveBeenCalled();
await act(async () => {
vi.advanceTimersByTime(1);
await Promise.resolve();
});
expect(requestIdleCallback).toHaveBeenCalledTimes(1);
expect(apiMocks.refreshCodexAccountSnapshot).not.toHaveBeenCalled();
await act(async () => {
idleCallback({ didTimeout: false, timeRemaining: () => 10 });
await Promise.resolve();
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledTimes(1);
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledWith({
includeRateLimits: true,
});
expect(host.textContent).toContain('belief@example.com');
act(() => {
root.unmount();
});
expect(cancelIdleCallback).not.toHaveBeenCalled();
});
it('clears a deferred initial Codex snapshot timer on unmount', async () => {
vi.useFakeTimers();
apiMocks.refreshCodexAccountSnapshot.mockResolvedValue(createSnapshot());
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
function Harness(): React.ReactElement {
useCodexAccountSnapshot({
enabled: true,
includeRateLimits: true,
initialRefreshDelayMs: 30_000,
});
return React.createElement('div', null, 'mounted');
}
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
act(() => {
root.unmount();
});
await act(async () => {
vi.advanceTimersByTime(30_000);
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).not.toHaveBeenCalled();
});
it('keeps retrying after a deferred initial Codex snapshot fails transiently', async () => {
vi.useFakeTimers();
const snapshot = createSnapshot();
apiMocks.refreshCodexAccountSnapshot
.mockRejectedValueOnce(new Error('temporary Codex outage'))
.mockResolvedValueOnce(snapshot);
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
function Harness(): React.ReactElement {
const state = useCodexAccountSnapshot({
enabled: true,
includeRateLimits: true,
initialRefreshDelayMs: 30_000,
});
return React.createElement(
'div',
null,
state.error ?? state.snapshot?.managedAccount?.email ?? 'empty'
);
}
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
await act(async () => {
vi.advanceTimersByTime(30_000);
await Promise.resolve();
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledTimes(1);
expect(host.textContent).toContain('temporary Codex outage');
await act(async () => {
vi.advanceTimersByTime(10_000);
await Promise.resolve();
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledTimes(2);
expect(host.textContent).toContain('belief@example.com');
act(() => {
root.unmount();
});
});
it('does not run the deferred initial snapshot after a manual refresh already loaded one', async () => {
vi.useFakeTimers();
Object.defineProperty(document, 'visibilityState', {
configurable: true,
get: () => 'hidden' satisfies DocumentVisibilityState,
});
const snapshot = createSnapshot();
apiMocks.refreshCodexAccountSnapshot.mockResolvedValue(snapshot);
let refreshNow!: () => Promise<void>;
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
function Harness(): React.ReactElement {
const state = useCodexAccountSnapshot({
enabled: true,
includeRateLimits: true,
initialRefreshDelayMs: 30_000,
});
refreshNow = () => state.refresh({ includeRateLimits: true });
return React.createElement('div', null, state.snapshot?.managedAccount?.email ?? 'empty');
}
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
await act(async () => {
await refreshNow();
});
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledTimes(1);
await act(async () => {
vi.advanceTimersByTime(30_000);
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledTimes(1);
act(() => {
root.unmount();
});
});
it('refreshes rate-limit snapshots more often while visible without flipping loading state during background polls', async () => {
vi.useFakeTimers();
const visibilityState: DocumentVisibilityState = 'visible';
Object.defineProperty(document, 'visibilityState', {
configurable: true,
get: () => visibilityState,
});
const snapshot = createSnapshot();
apiMocks.refreshCodexAccountSnapshot.mockResolvedValue(snapshot);
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
function Harness(): React.ReactElement {
const state = useCodexAccountSnapshot({
enabled: true,
includeRateLimits: true,
});
return React.createElement(
'div',
{ 'data-loading': state.loading ? 'true' : 'false' },
state.snapshot?.managedAccount?.email ?? 'empty'
);
}
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
expect(host.firstElementChild?.getAttribute('data-loading')).toBe('false');
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledTimes(1);
apiMocks.refreshCodexAccountSnapshot.mockClear();
await act(async () => {
vi.advanceTimersByTime(10_000);
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledTimes(1);
expect(host.firstElementChild?.getAttribute('data-loading')).toBe('false');
act(() => {
root.unmount();
});
});
it('slows background refreshes while hidden and refreshes immediately when the tab becomes visible again after staleness', async () => {
vi.useFakeTimers();
let visibilityState: DocumentVisibilityState = 'visible';
Object.defineProperty(document, 'visibilityState', {
configurable: true,
get: () => visibilityState,
});
const snapshot = createSnapshot();
apiMocks.refreshCodexAccountSnapshot.mockResolvedValue(snapshot);
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
function Harness(): React.ReactElement {
useCodexAccountSnapshot({
enabled: true,
includeRateLimits: true,
});
return React.createElement('div', null, 'hook-mounted');
}
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
apiMocks.refreshCodexAccountSnapshot.mockClear();
await act(async () => {
visibilityState = 'hidden';
document.dispatchEvent(new Event('visibilitychange'));
await Promise.resolve();
});
await act(async () => {
vi.advanceTimersByTime(10_000);
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).not.toHaveBeenCalled();
await act(async () => {
vi.advanceTimersByTime(50_000);
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledTimes(1);
apiMocks.refreshCodexAccountSnapshot.mockClear();
await act(async () => {
vi.advanceTimersByTime(10_000);
visibilityState = 'visible';
document.dispatchEvent(new Event('visibilitychange'));
await Promise.resolve();
});
expect(apiMocks.refreshCodexAccountSnapshot).toHaveBeenCalledTimes(1);
act(() => {
root.unmount();
});
});
});