From 46a525aea1685924ee97c0bd9ca643e46fce2df3 Mon Sep 17 00:00:00 2001 From: 777genius Date: Wed, 27 May 2026 21:53:47 +0300 Subject: [PATCH] fix(cli-status): refresh auth after terminal close --- .../components/dashboard/CliStatusBanner.tsx | 18 --- .../settings/sections/CliStatusSection.tsx | 3 - ...visioningProviderRuntimeSettingsDialog.tsx | 4 - .../cli/CliStatusVisibility.test.ts | 141 +++++++++++++++++- 4 files changed, 140 insertions(+), 26 deletions(-) diff --git a/src/renderer/components/dashboard/CliStatusBanner.tsx b/src/renderer/components/dashboard/CliStatusBanner.tsx index b5ee70a6..63361c61 100644 --- a/src/renderer/components/dashboard/CliStatusBanner.tsx +++ b/src/renderer/components/dashboard/CliStatusBanner.tsx @@ -1759,9 +1759,6 @@ export const CliStatusBanner = (): React.JSX.Element | null => { setProviderTerminal(null); recheckAuthState(); }} - onExit={() => { - recheckAuthState(); - }} autoCloseOnSuccessMs={3000} successMessage={ providerTerminal.action === 'login' @@ -2367,21 +2364,6 @@ export const CliStatusBanner = (): React.JSX.Element | null => { } })(); }} - onExit={() => { - setIsVerifyingAuth(true); - void (async () => { - try { - await invalidateCliStatus(); - if (multimodelEnabled) { - await bootstrapCliStatus({ multimodelEnabled: true }); - } else { - await fetchCliStatus(); - } - } finally { - setIsVerifyingAuth(false); - } - })(); - }} autoCloseOnSuccessMs={4000} successMessage={t('cliStatus.labels.loginComplete')} failureMessage={t('cliStatus.labels.loginFailed')} diff --git a/src/renderer/components/settings/sections/CliStatusSection.tsx b/src/renderer/components/settings/sections/CliStatusSection.tsx index ce5c61b4..3e16b9fb 100644 --- a/src/renderer/components/settings/sections/CliStatusSection.tsx +++ b/src/renderer/components/settings/sections/CliStatusSection.tsx @@ -862,9 +862,6 @@ export const CliStatusSection = (): React.JSX.Element | null => { setProviderTerminal(null); recheckStatus(); }} - onExit={() => { - recheckStatus(); - }} autoCloseOnSuccessMs={3000} successMessage={ providerTerminal.action === 'login' diff --git a/src/renderer/components/team/dialogs/ProvisioningProviderRuntimeSettingsDialog.tsx b/src/renderer/components/team/dialogs/ProvisioningProviderRuntimeSettingsDialog.tsx index 300abf4e..9db050f9 100644 --- a/src/renderer/components/team/dialogs/ProvisioningProviderRuntimeSettingsDialog.tsx +++ b/src/renderer/components/team/dialogs/ProvisioningProviderRuntimeSettingsDialog.tsx @@ -180,10 +180,6 @@ export const ProvisioningProviderRuntimeSettingsDialog = ({ onProviderRuntimeChanged?.(providerTerminal.providerId); refreshRuntimeAfterTerminal(); }} - onExit={() => { - onProviderRuntimeChanged?.(providerTerminal.providerId); - refreshRuntimeAfterTerminal(); - }} autoCloseOnSuccessMs={3000} successMessage={ providerTerminal.action === 'login' ? 'Authentication updated' : 'Provider logged out' diff --git a/test/renderer/components/cli/CliStatusVisibility.test.ts b/test/renderer/components/cli/CliStatusVisibility.test.ts index b6168105..9dcf82cb 100644 --- a/test/renderer/components/cli/CliStatusVisibility.test.ts +++ b/test/renderer/components/cli/CliStatusVisibility.test.ts @@ -70,6 +70,10 @@ let providerRuntimeSettingsDialogProps: { open?: boolean; initialProviderId?: string; } | null = null; +let terminalModalProps: { + onClose?: () => void; + onExit?: (exitCode: number) => void; +} | null = null; const codexAccountHookState = { snapshot: null as CodexAccountSnapshotDto | null, loading: false, @@ -160,7 +164,23 @@ vi.mock('@renderer/components/terminal/TerminalLogPanel', () => ({ })); vi.mock('@renderer/components/terminal/TerminalModal', () => ({ - TerminalModal: () => React.createElement('div', { 'data-testid': 'terminal-modal' }, 'terminal'), + TerminalModal: (props: { onClose?: () => void; onExit?: (exitCode: number) => void }) => { + terminalModalProps = props; + return React.createElement( + 'div', + { 'data-testid': 'terminal-modal' }, + React.createElement( + 'button', + { 'data-testid': 'terminal-exit', onClick: () => props.onExit?.(0) }, + 'exit' + ), + React.createElement( + 'button', + { 'data-testid': 'terminal-close', onClick: () => props.onClose?.() }, + 'close' + ) + ); + }, })); vi.mock('@renderer/store', () => { @@ -345,6 +365,7 @@ describe('CLI status visibility during completed install state', () => { beforeEach(() => { providerRuntimeSettingsDialogProps = null; + terminalModalProps = null; codexAccountHookState.snapshot = null; codexAccountHookState.loading = false; codexAccountHookState.rateLimitsLoading = false; @@ -518,6 +539,50 @@ describe('CLI status visibility during completed install state', () => { }); }); + it('waits until the runtime login modal closes before refreshing auth status', async () => { + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); + storeState.cliStatus = createInstalledCliStatus({ + authLoggedIn: false, + }); + + const host = document.createElement('div'); + document.body.appendChild(host); + const root = createRoot(host); + + await act(async () => { + root.render(React.createElement(CliStatusBanner)); + await flushLazyImports(); + }); + + const loginButton = Array.from(host.querySelectorAll('button')).find( + (button) => button.textContent?.trim() === 'Login' + ); + + await act(async () => { + loginButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + await flushLazyImports(); + }); + + expect(host.querySelector('[data-testid="terminal-modal"]')).not.toBeNull(); + expect(terminalModalProps?.onExit).toBeUndefined(); + + storeState.invalidateCliStatus.mockClear(); + storeState.bootstrapCliStatus.mockClear(); + + await act(async () => { + terminalModalProps?.onClose?.(); + await flushLazyImports(); + }); + + expect(storeState.invalidateCliStatus).toHaveBeenCalledTimes(1); + expect(storeState.bootstrapCliStatus).toHaveBeenCalledTimes(1); + + await act(async () => { + root.unmount(); + await flushLazyImports(); + }); + }); + it('loads the installer terminal log only while installation is active', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); storeState.cliInstallerState = 'installing'; @@ -1427,6 +1492,80 @@ describe('CLI status visibility during completed install state', () => { }); }); + it('waits until the provider login modal closes before refreshing provider auth status', async () => { + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); + storeState.cliInstallerState = 'idle'; + storeState.cliStatus = createInstalledCliStatus({ + flavor: 'agent_teams_orchestrator', + authLoggedIn: false, + providers: [ + { + providerId: 'anthropic', + displayName: 'Anthropic', + supported: true, + authenticated: false, + authMethod: null, + verificationState: 'verified', + statusMessage: 'Not connected', + models: [], + canLoginFromUi: true, + capabilities: { + teamLaunch: true, + oneShot: true, + }, + connection: { + supportsOAuth: true, + supportsApiKey: true, + configurableAuthModes: ['auto', 'oauth', 'api_key'], + configuredAuthMode: 'auto', + apiKeyConfigured: false, + apiKeySource: null, + apiKeySourceLabel: null, + }, + backend: null, + }, + ], + }); + + const host = document.createElement('div'); + document.body.appendChild(host); + const root = createRoot(host); + + await act(async () => { + root.render(React.createElement(CliStatusBanner)); + await flushLazyImports(); + }); + + const connectButton = Array.from(host.querySelectorAll('button')).find((button) => + button.textContent?.includes('Connect Anthropic') + ); + expect(connectButton).not.toBeUndefined(); + + await act(async () => { + connectButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + await flushLazyImports(); + }); + + expect(host.querySelector('[data-testid="terminal-modal"]')).not.toBeNull(); + expect(terminalModalProps?.onExit).toBeUndefined(); + + storeState.invalidateCliStatus.mockClear(); + storeState.bootstrapCliStatus.mockClear(); + + await act(async () => { + terminalModalProps?.onClose?.(); + await flushLazyImports(); + }); + + expect(storeState.invalidateCliStatus).toHaveBeenCalledTimes(1); + expect(storeState.bootstrapCliStatus).toHaveBeenCalledTimes(1); + + await act(async () => { + root.unmount(); + await flushLazyImports(); + }); + }); + it('shows subscription limit placeholders while an Anthropic subscription provider is checking', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); storeState.cliInstallerState = 'idle';