From eab3f06a4013feb7d44931242886c501938d54dc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 30 Oct 2026 17:36:53 +0000 Subject: [PATCH] fix: show signed-in github state when the username is missing The library sidebar required both a token and a login, so a saved session with an empty username showed the signed-out label after returning home. Treat signedIn alone as signed in, and keep @login when the name was stored. Co-authored-by: cosmoli --- .github/workflows/checks.yml | 3 +- AGENTS.md | 3 +- CONTRIBUTING.md | 1 + desktop/src-tauri/src/github.rs | 65 +++++++++++++++++++ web/src/pages/Edit/components/DesktopHome.vue | 10 +-- web/src/utils/githubStatus.js | 15 +++++ web/src/utils/githubStatus.test.cjs | 38 +++++++++++ 7 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 web/src/utils/githubStatus.js create mode 100644 web/src/utils/githubStatus.test.cjs diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f1de1cf6..6decb0a3 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -140,12 +140,13 @@ jobs: working-directory: web run: bun install --frozen-lockfile - # 这三个是无框架的断言脚本,之前只在本地手动跑,CI 从来没执行过。 + # 这些是无框架的断言脚本,之前只在本地手动跑,CI 从来没执行过。 # 放在构建之前:逻辑错误比打包错误更值得先失败。 - name: Test run: | bun web/src/utils/fileLibrary.test.cjs bun web/src/utils/mermaidMindmap.test.cjs + bun web/src/utils/githubStatus.test.cjs bun simple-mind-map/test/xmind-associative-line.test.cjs - name: Build web app diff --git a/AGENTS.md b/AGENTS.md index 308f28bb..716262e3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,7 @@ cargo check --manifest-path desktop/src-tauri/Cargo.toml bun web/src/utils/fileLibrary.test.cjs bun web/src/utils/mermaidMindmap.test.cjs +bun web/src/utils/githubStatus.test.cjs ``` Use Node 20.11 or newer: the build toolchain relies on `import.meta.dirname`. @@ -57,5 +58,5 @@ Updater artifacts are signed with the `TAURI_SIGNING_PRIVATE_KEY` secret; withou ## Pull requests -- One concern per pull request, linked to an issue. CI runs `title`, `format`, `cargo` and `build`; all four must pass before merge. `format` runs on Bun and owns Prettier, and `build` runs the three test scripts before bundling. +- One concern per pull request, linked to an issue. CI runs `title`, `format`, `cargo` and `build`; all four must pass before merge. `format` runs on Bun and owns Prettier, and `build` runs the test scripts before bundling. - PR titles follow Conventional Commits — they become the squash commit subject. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c147b19..41642fad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,6 +34,7 @@ Every issue carries one type label, usually one or two area labels, and `needs t cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --check bun web/src/utils/fileLibrary.test.cjs bun web/src/utils/mermaidMindmap.test.cjs + bun web/src/utils/githubStatus.test.cjs bun simple-mind-map/test/xmind-associative-line.test.cjs cd web && bun install --frozen-lockfile && npm run build ``` diff --git a/desktop/src-tauri/src/github.rs b/desktop/src-tauri/src/github.rs index cd5085d1..1daf1600 100644 --- a/desktop/src-tauri/src/github.rs +++ b/desktop/src-tauri/src/github.rs @@ -2043,6 +2043,9 @@ mod tests { ); } if first.starts_with("GET /user") { + if let Some((status, body)) = USER_RESPONSE.lock().unwrap().clone() { + return (status, body); + } return (200, r#"{"login":"octocat"}"#.to_string()); } if first.starts_with("GET /gists/missing") { @@ -2123,6 +2126,22 @@ mod tests { } static AUTH_TEST: Mutex<()> = Mutex::new(()); + static USER_RESPONSE: Mutex> = Mutex::new(None); + + struct UserResponseGuard; + + impl UserResponseGuard { + fn set(status: u16, body: &str) -> Self { + *USER_RESPONSE.lock().unwrap() = Some((status, body.to_string())); + Self + } + } + + impl Drop for UserResponseGuard { + fn drop(&mut self) { + *USER_RESPONSE.lock().unwrap() = None; + } + } #[test] fn device_flow_stores_token_without_returning_it() { @@ -2155,6 +2174,10 @@ mod tests { let polled = block(auth_poll(&dir.0)).expect("poll"); assert_eq!(polled.status, "success"); assert_eq!(polled.login, "octocat"); + // 重新读状态,相当于侧栏卸载后再挂上:用户名必须还在。 + let saved = status(&dir.0, "").expect("status"); + assert!(saved.signed_in); + assert_eq!(saved.login, "octocat"); let rendered = serde_json::to_string(&polled).unwrap(); assert!(!rendered.contains("gho_test_token")); assert!(!rendered.contains("device-123")); @@ -2176,6 +2199,48 @@ mod tests { assert_eq!(after.status, "cancelled"); } + #[test] + fn device_flow_stays_signed_in_when_login_lookup_fails() { + let _guard = AUTH_TEST.lock().unwrap(); + let _user = UserResponseGuard::set( + 403, + r#"{"message":"Resource not accessible by integration"}"#, + ); + let dir = TempDir::new(); + write_auth_unlocked( + &dir.0, + &AuthFile { + client_id: "Iv1.example".to_string(), + ..AuthFile::default() + }, + ) + .unwrap(); + let mock = Mock::start(); + block(auth_start_with( + &dir.0, + &format!("{}/login/device/code", mock.base), + &format!("{}/login/oauth/access_token", mock.base), + &mock.base, + )) + .expect("device code"); + { + let mut gate = lock_gate().unwrap(); + if let Some(pending) = gate.pending.as_mut() { + pending.last_poll = UNIX_EPOCH; + } + } + let polled = block(auth_poll(&dir.0)).expect("poll"); + assert_eq!(polled.status, "success"); + assert!(polled.login.is_empty()); + let saved = status(&dir.0, "").expect("status"); + assert!(saved.signed_in); + assert!(saved.login.is_empty()); + let raw = fs::read_to_string(auth_path(&dir.0)).unwrap(); + assert!(raw.contains("gho_test_token")); + assert!(!raw.contains("octocat")); + sign_out(&dir.0).unwrap(); + } + #[test] fn push_creates_secret_gist_and_pull_parses_it() { let dir = TempDir::new(); diff --git a/web/src/pages/Edit/components/DesktopHome.vue b/web/src/pages/Edit/components/DesktopHome.vue index 03141e89..8a336722 100644 --- a/web/src/pages/Edit/components/DesktopHome.vue +++ b/web/src/pages/Edit/components/DesktopHome.vue @@ -310,6 +310,7 @@