Skip to content

Fix Google refresh-token checks to prevent event sync drop (#744)#912

Open
vaibhavarora14 wants to merge 6 commits intoleits:masterfrom
vaibhavarora14:fix/issue-744-google-refresh-token-check
Open

Fix Google refresh-token checks to prevent event sync drop (#744)#912
vaibhavarora14 wants to merge 6 commits intoleits:masterfrom
vaibhavarora14:fix/issue-744-google-refresh-token-check

Conversation

@vaibhavarora14
Copy link
Copy Markdown

@vaibhavarora14 vaibhavarora14 commented Apr 14, 2026

Summary

  • Fix Google auth session checks to use OIDAuthState.refreshToken instead of lastTokenResponse.refreshToken
  • Prevent false signed-out detection after access token refreshes where Google omits refresh_token in the latest token response
  • Keep existing sign-out revoke logic aligned with persisted auth state token data

Test plan

  • xcodebuild -project \"MeetingBar.xcodeproj\" -scheme \"MeetingBar\" -destination \"platform=macOS\" CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO build
  • Connect Google Calendar account and verify events remain visible without redoing OAuth daily
  • Trigger refresh flow and confirm upcoming meetings do not disappear before meeting start

Closes #744

Made with Cursor

Summary by CodeRabbit

  • Bug Fixes

    • Improved refresh token handling during sign-out operations
    • Enhanced sign-in session validation logic
  • Tests

    • Added comprehensive test coverage for authentication flows and token management

Use OIDAuthState.refreshToken instead of lastTokenResponse.refreshToken so MeetingBar does not treat valid Google sessions as signed out after token refreshes.

Made-with: Cursor
@dosubot dosubot Bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Apr 14, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

Walkthrough

Modified Google Calendar sign-in and sign-out authentication logic by introducing a helper method for skip-sign-in conditions, updating token revocation to use state.refreshToken instead of lastTokenResponse?.refreshToken, and requiring refresh token presence in ensureSignedIn(). Added comprehensive test coverage for these authentication flows.

Changes

Cohort / File(s) Summary
Google Calendar Authentication Logic
MeetingBar/Core/EventStores/GCEventStore.swift
Refactored sign-in bypass logic into static helper shouldSkipSignIn(forcePrompt:state:). Updated signOut() to revoke refresh token via state.refreshToken. Modified ensureSignedIn() to check for refresh token presence and compute forceConsent based on refresh token availability. Added debug-only test helpers for reading/setting auth state and sign-in task.
Authentication Test Coverage
MeetingBarTests/EventManagerTests.swift
Added GCEventStoreCoverageTests suite with test cases validating shouldSkipSignIn() behavior, ensureSignedIn() branching logic, and signOut() refresh token usage. Included helper methods to construct test OIDAuthState instances with and without refresh tokens.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • #835: Modifies GCEventStore's Google sign-in/sign-out logic with overlapping changes to sign-in flow, signInTask handling, and sign-out behavior.

Poem

🐰 Token tango, refresh and revoke,
Auth states now dance without a smoke,
Sign-in helpers hop with grace,
Tests ensure no meetings disappear from place!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing Google refresh-token checks to prevent event sync drop, which directly addresses the core issue of disappearing events.
Linked Issues check ✅ Passed Code changes directly address #744 by replacing incorrect lastTokenResponse.refreshToken checks with OIDAuthState.refreshToken, preventing false sign-out detection and event disappearance.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing Google auth token checks and adding targeted tests; no unrelated refactoring or functionality additions detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot dosubot Bot added the bug Something isn't working label Apr 14, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 14, 2026

Codecov Report

❌ Patch coverage is 0% with 86 lines in your changes missing coverage. Please review.
✅ Project coverage is 35.88%. Comparing base (84e0a9f) to head (3932f14).
⚠️ Report is 15 commits behind head on master.

Files with missing lines Patch % Lines
MeetingBarTests/EventManagerTests.swift 0.00% 69 Missing ⚠️
MeetingBar/Core/EventStores/GCEventStore.swift 0.00% 17 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #912      +/-   ##
==========================================
+ Coverage   26.30%   35.88%   +9.57%     
==========================================
  Files          36       48      +12     
  Lines        5645     5181     -464     
  Branches     2210     1741     -469     
==========================================
+ Hits         1485     1859     +374     
+ Misses       4103     3266     -837     
+ Partials       57       56       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Refactor GCEventStore refresh-token checks into testable helpers and add unit tests that verify AppAuth keeps refreshToken in OIDAuthState even when refresh responses omit refresh_token.

Made-with: Cursor
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XS This PR changes 0-9 lines, ignoring generated files. labels Apr 14, 2026
Comment thread MeetingBarTests/GCEventStoreTests.swift Fixed
Comment thread MeetingBarTests/GCEventStoreTests.swift Fixed
Mark extracted token helper methods as nonisolated for test access and normalize AppAuth parameter dictionaries to Objective-C bridgeable types used by OID response initializers.

Made-with: Cursor
@vaibhavarora14 vaibhavarora14 marked this pull request as draft April 14, 2026 15:18
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
MeetingBar/Core/EventStores/GCEventStore.swift (1)

223-233: ⚠️ Potential issue | 🟠 Major

forcePrompt can be bypassed by signIn’s early return

ensureSignedIn() computes forceConsent on Line 229, but signIn(forcePrompt:) still returns early when authState?.isAuthorized == true (Line 97). That can skip the intended forced-consent flow for sessions missing a refresh token.

Proposed fix
 func signIn(forcePrompt: Bool = false) async throws {
-    // if already authorised, nothing to do
-    if authState?.isAuthorized == true { return }
+    // Skip only when session is fully reusable and consent is not being forced.
+    if !forcePrompt, Self.hasAuthorizedSession(authState) { return }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MeetingBar/Core/EventStores/GCEventStore.swift` around lines 223 - 233,
ensureSignedIn() computes forceConsent and calls signIn(forcePrompt:), but
signIn(forcePrompt:) currently returns early when authState?.isAuthorized ==
true, allowing the forced-consent flow to be bypassed; update the logic so that
signIn(forcePrompt:) honors the forcePrompt flag (or ensureSignedIn() bypasses
the early-return) by treating forcePrompt=true as a reason to proceed even if
authState?.isAuthorized is true—modify the early-return check in
signIn(forcePrompt:) (and/or the hasAuthorizedSession short-circuit in
ensureSignedIn()) to require both isAuthorized==true and forcePrompt==false
before returning, so forced consent is not skipped for sessions missing a
refresh token.
🧹 Nitpick comments (1)
MeetingBarTests/GCEventStoreTests.swift (1)

69-79: Remove redundant access_token write in test helper

Line 71 initializes access_token and Line 78 overwrites it immediately. Keeping a single assignment makes intent clearer.

Proposed cleanup
     private func tokenParameters(accessToken: String, refreshToken: String?) -> [String: NSObject & NSCopying] {
         var parameters: [String: NSObject & NSCopying] = [
-            "access_token": "access-token-1" as NSString,
+            "access_token": accessToken as NSString,
             "token_type": "Bearer" as NSString,
             "expires_in": 3600 as NSNumber
         ]
         if let refreshToken {
             parameters["refresh_token"] = refreshToken as NSString
         }
-        parameters["access_token"] = accessToken as NSString
         return parameters
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MeetingBarTests/GCEventStoreTests.swift` around lines 69 - 79, In
tokenParameters(accessToken:refreshToken:) remove the redundant initial
"access_token" entry from the parameters dictionary (currently set at line
creating parameters) and keep only the final assignment that sets "access_token"
to the accessToken argument; ensure the function still adds "token_type" and
"expires_in", conditionally sets "refresh_token" when refreshToken is non-nil,
and returns the parameters as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@MeetingBar/Core/EventStores/GCEventStore.swift`:
- Around line 223-233: ensureSignedIn() computes forceConsent and calls
signIn(forcePrompt:), but signIn(forcePrompt:) currently returns early when
authState?.isAuthorized == true, allowing the forced-consent flow to be
bypassed; update the logic so that signIn(forcePrompt:) honors the forcePrompt
flag (or ensureSignedIn() bypasses the early-return) by treating
forcePrompt=true as a reason to proceed even if authState?.isAuthorized is
true—modify the early-return check in signIn(forcePrompt:) (and/or the
hasAuthorizedSession short-circuit in ensureSignedIn()) to require both
isAuthorized==true and forcePrompt==false before returning, so forced consent is
not skipped for sessions missing a refresh token.

---

Nitpick comments:
In `@MeetingBarTests/GCEventStoreTests.swift`:
- Around line 69-79: In tokenParameters(accessToken:refreshToken:) remove the
redundant initial "access_token" entry from the parameters dictionary (currently
set at line creating parameters) and keep only the final assignment that sets
"access_token" to the accessToken argument; ensure the function still adds
"token_type" and "expires_in", conditionally sets "refresh_token" when
refreshToken is non-nil, and returns the parameters as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 71e35a77-39d0-4c9c-8924-07fc10c33569

📥 Commits

Reviewing files that changed from the base of the PR and between 5e1d520 and 34c0220.

📒 Files selected for processing (2)
  • MeetingBar/Core/EventStores/GCEventStore.swift
  • MeetingBarTests/GCEventStoreTests.swift

Add debug-only GCEventStore test hooks and async tests that execute the updated ensureSignedIn/signOut token-check paths without network calls so patch coverage includes the regression fix lines.

Made-with: Cursor
}

#if DEBUG
func _test_getAuthState() -> OIDAuthState? {
authState
}

func _test_setAuthState(_ state: OIDAuthState?) {
userEmail = state?.userEmail
}

func _test_ensureSignedIn() async throws {
Move GCEventStore coverage scenarios into an existing test file and reduce GCEventStore token logic changes to directly exercised lines so patch coverage reflects executed code paths.

Made-with: Cursor
try await ensureSignedIn()
}

func _test_setSignInTask(_ task: Task<Void, Error>?) {
@vaibhavarora14
Copy link
Copy Markdown
Author

Hi @leits — quick note on the remaining codecov/patch failure.

I investigated this in detail and it appears to be a Codecov aggregation mismatch, not a missing test execution case:

  • All CI jobs are green except codecov/patch (Tests, SwiftLint, Codacy, GitGuardian pass).
  • Latest head is 65beda2 and tests pass in CI (59 tests).
  • Codecov PR summary shows patch coverage as 0.0% (hits=0, misses=75).
  • However, Codecov compare API line data for the same head reports executed added lines (coverage: 1) in both changed files (GCEventStore.swift and EventManagerTests.swift).

I also restructured tests specifically to avoid new-file patch accounting artifacts (moved GCEventStore coverage tests into an existing test file), but codecov/patch still reports 0.0% while per-line coverage data indicates execution.

Given this mismatch, this looks like a Codecov-side patch status false negative on this PR context.

If helpful, I can open a Codecov support ticket with the API payloads and check-run IDs.

Prevent signIn from short-circuiting authorized sessions when forced consent is requested and add coverage for the skip-decision logic used by issue leits#744 flow.

Made-with: Cursor
@vaibhavarora14 vaibhavarora14 marked this pull request as ready for review April 14, 2026 15:53
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
MeetingBarTests/EventManagerTests.swift (1)

203-210: Good coverage of the key fix scenarios.

The test correctly validates the three essential cases for the bug fix. Consider adding edge cases for completeness:

💡 Optional: Additional edge case coverage
         let authorizedWithoutRefresh = makeAuthState(refreshToken: nil)
         XCTAssertFalse(GCEventStore.shouldSkipSignIn(forcePrompt: false, state: authorizedWithoutRefresh))
+        
+        // nil state should never skip
+        XCTAssertFalse(GCEventStore.shouldSkipSignIn(forcePrompt: false, state: nil))
+        XCTAssertFalse(GCEventStore.shouldSkipSignIn(forcePrompt: true, state: nil))
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MeetingBarTests/EventManagerTests.swift` around lines 203 - 210, Add
edge-case assertions to testSignInSkipLogicHonorsForcePrompt to increase
coverage: call GCEventStore.shouldSkipSignIn with a nil state and verify
expected behavior, and add a case where the state is unauthorized (e.g.,
makeAuthState with an explicit expired/invalid token if supported) to assert
shouldSkipSignIn returns false regardless of forcePrompt; update references
inside the test to use makeAuthState and GCEventStore.shouldSkipSignIn to
construct these additional scenarios and their expected XCTAssertTrue/False
outcomes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@MeetingBarTests/EventManagerTests.swift`:
- Around line 203-210: Add edge-case assertions to
testSignInSkipLogicHonorsForcePrompt to increase coverage: call
GCEventStore.shouldSkipSignIn with a nil state and verify expected behavior, and
add a case where the state is unauthorized (e.g., makeAuthState with an explicit
expired/invalid token if supported) to assert shouldSkipSignIn returns false
regardless of forcePrompt; update references inside the test to use
makeAuthState and GCEventStore.shouldSkipSignIn to construct these additional
scenarios and their expected XCTAssertTrue/False outcomes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cef16747-e45c-44ea-833a-40a4f1bdb6c6

📥 Commits

Reviewing files that changed from the base of the PR and between 34c0220 and 3932f14.

📒 Files selected for processing (2)
  • MeetingBar/Core/EventStores/GCEventStore.swift
  • MeetingBarTests/EventManagerTests.swift

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Upcoming Meetings Disappearing from Menu Bar

2 participants