diff --git a/src-tauri/src/github.rs b/src-tauri/src/github.rs index a4f9172e..cda8fe1d 100644 --- a/src-tauri/src/github.rs +++ b/src-tauri/src/github.rs @@ -2,8 +2,10 @@ use serde::{Deserialize, Serialize}; use std::path::Path; use std::process::Command; -/// GitHub OAuth App client ID. Replace with your registered GitHub App's client_id. -const GITHUB_CLIENT_ID: &str = "Ov23liCuBz7Z5hKk6T8c"; +/// GitHub App client ID for OAuth device flow. +/// To set up: GitHub Settings → Developer settings → GitHub Apps → New GitHub App. +/// Enable "Device authorization flow" under Optional features. Webhook can be disabled. +const GITHUB_CLIENT_ID: &str = "Ov23liwee215tDMs9u4L"; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct GithubRepo { @@ -175,14 +177,23 @@ async fn github_device_flow_start_with_base(base_url: &str) -> Result { ) expect(screen.getByText(/Connect your GitHub account/)).toBeInTheDocument() }) + + it('displays the actual backend error string when device flow start fails', async () => { + mockInvokeFn.mockImplementation(async (cmd: string) => { + if (cmd === 'github_device_flow_start') { + // Tauri invoke rejects with a plain string, not an Error instance + throw 'GitHub device flow not available. Ensure a GitHub App is registered.' + } + return null + }) + + render( + + ) + + fireEvent.click(screen.getByTestId('github-login')) + + await waitFor(() => { + expect(screen.getByTestId('github-error')).toHaveTextContent( + 'GitHub device flow not available. Ensure a GitHub App is registered.' + ) + }) + }) + + it('prevents double-click by disabling button during login flow', async () => { + let resolveStart: ((v: unknown) => void) | null = null + mockInvokeFn.mockImplementation(async (cmd: string) => { + if (cmd === 'github_device_flow_start') { + return new Promise(r => { resolveStart = r }) + } + return null + }) + + render( + + ) + + const loginBtn = screen.getByTestId('github-login') as HTMLButtonElement + fireEvent.click(loginBtn) + + // Button should be disabled while waiting + await waitFor(() => { + expect(loginBtn.disabled).toBe(true) + }) + + // Clean up + resolveStart?.({ + device_code: 'dc', user_code: 'UC-1234', + verification_uri: 'https://github.com/login/device', + expires_in: 900, interval: 5, + }) + }) }) }) diff --git a/src/components/SettingsPanel.tsx b/src/components/SettingsPanel.tsx index fe448585..70409701 100644 --- a/src/components/SettingsPanel.tsx +++ b/src/components/SettingsPanel.tsx @@ -172,11 +172,11 @@ function GitHubSection({ githubUsername, githubToken, onConnected, onDisconnect pollLoop().catch(err => { stopPolling() setOauthStatus('error') - setErrorMessage(err instanceof Error ? err.message : 'Polling failed.') + setErrorMessage(typeof err === 'string' ? err : err instanceof Error ? err.message : 'Polling failed.') }) } catch (err) { setOauthStatus('error') - setErrorMessage(err instanceof Error ? err.message : 'Failed to start login.') + setErrorMessage(typeof err === 'string' ? err : err instanceof Error ? err.message : 'Failed to start login.') } }, [onConnected, stopPolling])