Skip to content

fix: google revocation handling#1484

Open
tyler-dane wants to merge 21 commits intomainfrom
fix/1478-import-bug
Open

fix: google revocation handling#1484
tyler-dane wants to merge 21 commits intomainfrom
fix/1478-import-bug

Conversation

@tyler-dane
Copy link
Contributor

@tyler-dane tyler-dane commented Feb 28, 2026

  • Added handling for Google access revocation in CompassApi, ensuring users remain logged in while displaying a toast notification.
  • Introduced a new utility function to extract error codes from Axios errors, enhancing error management.
  • Updated tests to verify the new behavior, including scenarios for Google revoked access.
  • This enhancement improves user experience by providing clear feedback and managing Google Calendar data effectively.

Closes #1478

tyler-dane and others added 14 commits February 27, 2026 13:40
…mplates

- Removed unnecessary emoji from template names for a cleaner presentation.
- Eliminated the priority dropdown from the feature request template to streamline the submission process.
- Updated the bug report template to remove the emoji, enhancing consistency across issue templates.
- Adjusted the configuration file to reflect these changes, ensuring clarity in the issue submission process.
- Updated SocketProvider tests to utilize `act` for asynchronous callback invocations, ensuring proper handling of state updates during testing.
- Improved test reliability by wrapping callback executions in `act`, aligning with React's testing best practices.
- Introduced a new test case to verify the correct handling of import end events when the awaitingImportResults state changes mid-render.
- Utilized a ref to prevent stale closures, ensuring that the correct state is referenced during socket event processing.
- Enhanced the reliability of the useGcalSync hook by ensuring it properly processes events even when state changes occur asynchronously.
- Deleted the circle.svg file from the public SVG assets as it is no longer needed in the project.
- This cleanup helps streamline the asset management and reduces unnecessary file clutter.
- Update ref synchronously during render instead of in useEffect
  to fully eliminate race condition window
- Rename misleading test name (timeout -> successfully)
- Fix test assertions to validate action creator calls directly
  instead of using unused variable and dispatch-based assertions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Updated state management and selectors to reflect the new naming convention for clarity.
- Adjusted related components and tests to ensure consistency with the updated state name.
- This change enhances code readability and aligns with the overall naming strategy in the project.
- Removed the 1-second delay in the reconnect function, allowing for immediate reconnection after disconnection.
- Updated the corresponding test case to reflect the change in behavior, ensuring it accurately tests the immediate reconnection functionality.
- This change enhances the responsiveness of the socket client and improves the clarity of the test case.
- Removed the use of fake timers in the tests for useGcalSync, simplifying the test setup.
- Updated type definitions for event handler variables to allow for undefined values, enhancing type safety.
- This change improves test clarity and aligns with best practices for handling asynchronous events in React testing.
…tion flow

- Introduced a new test suite for the Google Calendar re-authentication process, validating user experience during import operations.
- Implemented tests to ensure the spinner visibility during import initiation, handling of socket events, and correct state updates upon import completion.
- Enhanced test reliability by utilizing `act` for asynchronous operations and capturing socket event callbacks.
- This addition improves coverage for the re-authentication flow and ensures a seamless user experience during Google Calendar synchronization.
- Updated the Redux action and corresponding function names to improve clarity and consistency in the import state management.
- Adjusted all related components and tests to reflect the new naming convention, ensuring seamless integration across the codebase.
- This change enhances code readability and aligns with the overall naming strategy in the project.
…ng tests

- Updated the error handling for invalid Google tokens to prune user data and notify clients via WebSocket.
- Modified the response to include a specific payload indicating Google access has been revoked.
- Added unit tests to verify the new error handling behavior, ensuring proper response and state management during Google token invalidation.
- This change improves user experience by providing clearer feedback on session status and data management.
- Added the pruneGoogleData method to UserService, which stops Google Calendar sync and removes the Google field from the user document.
- Introduced a new test suite for pruneGoogleData, verifying that user data is correctly pruned and associated events are deleted.
- This enhancement improves data management and user experience by ensuring that outdated Google data is effectively removed.
- Added functionality to handle Google access revocation by pruning user data and notifying clients via WebSocket.
- Updated SyncController to respond with a structured message when access is revoked, improving user feedback.
- Introduced tests to verify the new behavior, ensuring proper handling of revoked access scenarios.
- This enhancement improves data management and user experience by effectively managing Google Calendar access changes.
- Added handling for Google access revocation in CompassApi, ensuring users remain logged in while displaying a toast notification.
- Introduced a new utility function to extract error codes from Axios errors, enhancing error management.
- Updated tests to verify the new behavior, including scenarios for Google revoked access.
- This enhancement improves user experience by providing clear feedback and managing Google Calendar data effectively.
Copilot AI review requested due to automatic review settings February 28, 2026 03:10
@tyler-dane tyler-dane changed the title fix: import bug fix: google revocation handling Feb 28, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the Google Calendar revoked-access experience by pruning Google data server-side, notifying clients via websocket + API error payloads, and ensuring the UI clears Google events and refetches without forcing a logout.

Changes:

  • Add a GOOGLE_REVOKED websocket event + structured API error payload ({ code, message }) and server-side pruning of Google integration data.
  • Update web sync flow to clear Google-origin events, show a persistent toast, and trigger full refetch (including Someday events) on revoke.
  • Rename the “awaiting import results” flag to isImportPending across web code and expand test coverage around the import overlay and socket event race conditions.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/web/src/views/Calendar/hooks/useRefetch.ts Refetch Someday events as part of the Google-revoked full refresh.
packages/web/src/socket/provider/SocketProvider.test.tsx Update import-pending flag usage and wrap socket callbacks in act.
packages/web/src/socket/provider/SocketProvider.interaction.test.tsx Add interaction tests for overlay/spinner behavior across socket import phases.
packages/web/src/socket/hooks/useGcalSync.ts Add revoke handler (toast + purge Google events + trigger refetch) and fix stale-closure race using a ref.
packages/web/src/socket/hooks/useGcalSync.test.ts Expand hook tests for revoke behavior and import flow/race cases.
packages/web/src/socket/client/socket.client.ts Make reconnect() connect immediately after disconnect.
packages/web/src/socket/client/socket.client.test.ts Update reconnect test expectations for immediate connect.
packages/web/src/public/svg/circle.svg Remove unused SVG asset.
packages/web/src/ducks/events/slices/sync.slice.ts Rename awaitingImportResults to isImportPending.
packages/web/src/ducks/events/slices/event.slice.ts Add reducer to remove events by origin (Google/Google-import).
packages/web/src/ducks/events/selectors/sync.selector.ts Rename selector to selectIsImportPending.
packages/web/src/ducks/events/context/sync.context.ts Add GOOGLE_REVOKED sync reason.
packages/web/src/components/SyncEventsOverlay/SyncEventsOverlay.tsx Switch overlay gating to selectIsImportPending.
packages/web/src/components/SyncEventsOverlay/SyncEventsOverlay.test.tsx Update selector usage for renamed import-pending flag.
packages/web/src/common/constants/toast.constants.ts Add a stable toast id constant for Google-revoked toasts.
packages/web/src/common/apis/compass.api.util.ts Add helper to extract code from Axios error responses.
packages/web/src/common/apis/compass.api.util.test.ts Add unit tests for Axios error code extraction helper.
packages/web/src/common/apis/compass.api.ts Intercept GOOGLE_REVOKED API responses: keep session, toast + purge Google events + refetch.
packages/web/src/common/apis/compass.api.test.ts Add test for Google-revoked code path (no sign-out).
packages/web/src/auth/hooks/oauth/useGoogleAuth.ts Rename import-pending dispatches to setIsImportPending.
packages/web/src/auth/hooks/oauth/useGoogleAuth.test.ts Update expected action type names for renamed import-pending action.
packages/web/src/tests/utils/state/store.test.util.ts Update initial test state to use isImportPending.
packages/core/src/types/websocket.types.ts Add GOOGLE_REVOKED to server-to-client socket event types.
packages/core/src/constants/websocket.constants.ts Add GOOGLE_REVOKED websocket constant.
packages/backend/src/user/services/user.service.ts Add pruneGoogleData to stop sync + remove Google data from user doc.
packages/backend/src/user/services/user.service.test.ts Add test coverage for pruneGoogleData.
packages/backend/src/sync/controllers/sync.controller.ts On revoked access: prune Google data, notify websocket, and return structured {code,message} response.
packages/backend/src/sync/controllers/sync.controller.test.ts Add coverage for revoked-access pruning + websocket notification + structured response.
packages/backend/src/servers/websocket/websocket.server.ts Add helper to notify clients of GOOGLE_REVOKED.
packages/backend/src/event/controllers/event.controller.ts Refactor endpoints to use res.promise(...) flow and remove inline try/catch.
packages/backend/src/common/services/gcal/gcal.utils.ts Improve invalid token detection and broaden Google error recognition.
packages/backend/src/common/errors/handlers/error.handler.test.ts Add test for revoked-access response payload from express error handler.
packages/backend/src/common/errors/handlers/error.express.handler.ts On invalid Google token: prune Google data, notify websocket, return 401 with {code,message} (no session revoke).
e2e/utils/oauth-test-utils.ts Rename e2e helper to set isImportPending in Redux.
.github/ISSUE_TEMPLATE/config.yml Remove emoji from contact link names.
.github/ISSUE_TEMPLATE/2-bug-report.yml Remove emoji from issue template name.
.github/ISSUE_TEMPLATE/1-feature-request.yml Remove emoji from issue template name and drop priority dropdown.
Comments suppressed due to low confidence (1)

packages/web/src/socket/hooks/useGcalSync.test.ts:273

  • This test suite calls jest.advanceTimersByTime(...) but never enables fake timers (no jest.useFakeTimers()), which will fail under Jest’s default real-timers mode. Add jest.useFakeTimers()/jest.useRealTimers() in setup for this describe block, or remove the timer advancement if it’s not needed for the assertions.
      // Phase 2: Simulate backend processing time (e.g., 2 seconds)
      jest.advanceTimersByTime(2000);

- Updated the test for handling Google access revocation to ensure proper behavior on 401/410 responses.
- Introduced a new payload structure for the GOOGLE_REVOKED error code and verified that the correct toast notification is displayed.
- Improved test coverage by iterating over multiple status codes to validate consistent error handling and user feedback.
- Added beforeEach and afterEach hooks to manage fake timers in the useGcalSync test suite.
- This change improves test reliability by ensuring proper timer handling during asynchronous operations.
- Updated error handling functions to accept `unknown` type instead of specific error types, enhancing type safety and flexibility.
- Adjusted the `handleExpressError` and `isFullSyncRequired` functions to work with the new type definitions, ensuring consistent error management across the application.
- This change improves code robustness and prepares the error handling system for a wider range of error scenarios.
Copilot AI review requested due to automatic review settings February 28, 2026 03:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.

Comment on lines +119 to +124
expect(toast.error).toHaveBeenCalledWith(
"Google access revoked. Your Google data has been removed.",
expect.objectContaining({
toastId: "google-revoked-api",
autoClose: false,
}),
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The test hard-codes the toastId string ("google-revoked-api"). Since the production code uses the exported GOOGLE_REVOKED_TOAST_ID constant, this makes the test brittle and likely to break on a simple constant rename. Import and assert against GOOGLE_REVOKED_TOAST_ID instead of a string literal.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +57
const handleGoogleRevokedError = () => {
if (!toast.isActive(GOOGLE_REVOKED_TOAST_ID)) {
toast.error("Google access revoked. Your Google data has been removed.", {
toastId: GOOGLE_REVOKED_TOAST_ID,
autoClose: false,
});
}
store.dispatch(
eventsEntitiesSlice.actions.removeEventsByOrigin({
origins: [Origin.GOOGLE, Origin.GOOGLE_IMPORT],
}),
);
store.dispatch(
triggerFetch({ reason: Sync_AsyncStateContextReason.GOOGLE_REVOKED }),
);
};
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

Google-revocation handling (toast + removeEventsByOrigin + triggerFetch) is duplicated here and in useGcalSync's GOOGLE_REVOKED socket handler. This increases the chance of the two paths diverging (message/options/origins) and can also cause the side effects to run twice (API error + websocket event). Consider extracting a shared, idempotent helper (e.g., skip the dispatches when the GOOGLE_REVOKED_TOAST_ID is already active or use a module-level guard) and reuse it from both places.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +53
it("returns 401 with GOOGLE_REVOKED payload when Google token is invalid", async () => {
const userId = "507f1f77bcf86cd799439011";
jest.spyOn(userService, "pruneGoogleData").mockResolvedValue();
jest.spyOn(webSocketServer, "handleGoogleRevoked");
jest.spyOn(errorHandler, "isOperational").mockReturnValue(true);
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

This test spies on webSocketServer.handleGoogleRevoked but doesn't mock its implementation or assert it was called. Because webSocketServer is a singleton with internal connection state, calling the real method can make the test order-dependent/flaky. Mock the implementation (e.g., mockImplementation(() => undefined)) and add an expectation that it was invoked with the userId (and restore the spy after the test).

Copilot uses AI. Check for mistakes.
…ndling

- Introduced the deleteWatchesByUser method in SyncService to remove watch records for a specific user, improving data management.
- Added createGoogleError utility for generating mock Google errors, enhancing test coverage and error handling.
- Updated gcal.utils to include getGoogleErrorStatus for better error status retrieval from Google errors.
- Enhanced tests for SyncService to validate behavior when handling Google errors, ensuring robust error management.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Re-authenticating after session expired flow causes indefinite spinner to load

2 participants