Skip to content

fix: gate onlineApi requests on auth state (Rollbar #91)#92

Merged
wizzomafizzo merged 2 commits into
mainfrom
fix/account-deletion-not-signed-in
Apr 17, 2026
Merged

fix: gate onlineApi requests on auth state (Rollbar #91)#92
wizzomafizzo merged 2 commits into
mainfrom
fix/account-deletion-not-signed-in

Conversation

@wizzomafizzo

@wizzomafizzo wizzomafizzo commented Apr 17, 2026

Copy link
Copy Markdown
Member
  • Adds authRequestInterceptor (exported named function) to onlineApi.ts that calls FirebaseAuthentication.getCurrentUser() before every request. If no user is found, it syncs the Zustand store to null and throws NotSignedInError — preventing the Firebase "No user is signed in." error from surfacing as an unhandled Rollbar event.
  • handleDeleteAccount and handleCancelDeletion in settings.online.tsx now catch NotSignedInError explicitly: the delete modal closes, a "no longer signed in" toast is shown, and nothing is logged to Rollbar (expected/recoverable condition).
  • The interceptor guards against redundant store writes on concurrent stale-auth requests via a loggedInUser !== null check.
  • Tests for the interceptor import authRequestInterceptor directly rather than capturing it through the axios mock, eliminating a brittle ESM-caching dependency.

Summary by CodeRabbit

  • New Features

    • If your session expires during account deletion or cancellation, the UI now closes the confirmation modal (when applicable) and shows a clear "not signed in" message prompting re-sign-in.
    • Added an English message for this scenario.
  • Bug Fixes

    • Ensures the app clears stale sign-in state when needed to avoid misleading behavior.
  • Tests

    • Added tests covering authenticated vs unauthenticated flows and the new error handling.

…rash

When a user opens the account deletion modal and their Firebase auth
session expires before confirming, getIdToken() throws "No user is
signed in." which was logged to Rollbar as an unhandled error (Rollbar
#91).

Adds an authRequestInterceptor that pre-checks getCurrentUser() before
every onlineApi request. If no user is found, it syncs the Zustand store
and throws NotSignedInError. The deletion and cancel-deletion handlers
now catch NotSignedInError specifically, close the modal, and show a
"no longer signed in" toast without logging to Rollbar.
@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d7bdb7dc-a67b-4c9f-85f9-ad6dddb4d2c7

📥 Commits

Reviewing files that changed from the base of the PR and between 16be3c9 and b3c0aca.

📒 Files selected for processing (2)
  • src/__tests__/unit/lib/onlineApi.test.ts
  • src/__tests__/unit/routes/settings.online.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tests/unit/routes/settings.online.test.tsx
  • src/tests/unit/lib/onlineApi.test.ts

📝 Walkthrough

Walkthrough

Adds an authentication-state check to the Axios request interceptor that throws a new NotSignedInError when no current Firebase user exists, clears loggedInUser as needed, updates account-deletion/cancellation handlers to handle this error with a localized toast, and extends tests to cover these flows.

Changes

Cohort / File(s) Summary
Request Interceptor & Auth Validation
src/lib/onlineApi.ts
Added NotSignedInError export and authRequestInterceptor(config: InternalAxiosRequestConfig) which checks FirebaseAuthentication.getCurrentUser() before calling getIdToken(), clears useStatusStore().setLoggedInUser(null) when appropriate, and throws NotSignedInError for unauthenticated requests.
Settings Account Management
src/routes/settings.online.tsx
Imported NotSignedInError and added specific handling in handleDeleteAccount and handleCancelDeletion to show translation.online.notSignedInError toast, close/clear confirmation UI where applicable, and avoid logging for this error.
Unit Tests
src/__tests__/unit/lib/onlineApi.test.ts, src/__tests__/unit/routes/settings.online.test.tsx
Expanded mocks for @capacitor-firebase/authentication to include getCurrentUser, added useStatusStore.getState mock, added authRequestInterceptor test suite verifying Authorization header behavior and NotSignedInError flows, and added tests asserting UI/toast behavior when deleteAccount/cancelAccountDeletion throw NotSignedInError.
Translations
src/translations/en-US.json
Added translation.online.notSignedInError → "You're no longer signed in. Please sign in again.".

Sequence Diagram(s)

sequenceDiagram
  participant Axios as Axios (request)
  participant Interceptor as authRequestInterceptor
  participant Firebase as FirebaseAuthentication
  participant Store as useStatusStore

  Axios->>Interceptor: request config
  Interceptor->>Firebase: getCurrentUser()
  alt user exists
    Interceptor->>Firebase: getIdToken()
    Firebase-->>Interceptor: idToken
    Interceptor->>Axios: set Authorization: Bearer <token>
  else no user
    Interceptor->>Store: getState().loggedInUser
    alt loggedInUser != null
      Interceptor->>Store: setLoggedInUser(null)
    end
    Interceptor-->>Axios: throw NotSignedInError
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I checked the token, nose twitching bright,
When users vanish, I set things right,
A toast, a clear, a gentle sigh—
Not signed in? I wave goodbye.
Hop on, auth is tidy tonight. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding authentication state gating to onlineApi requests to resolve a Rollbar error.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/account-deletion-not-signed-in

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

@codecov

codecov Bot commented Apr 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/__tests__/unit/routes/settings.online.test.tsx (1)

1074-1077: Make modal-close assertion async-safe to reduce flakiness.

The absence check can race UI commit; wrap the queryByText assertion in waitFor after the rejection flow.

🧪 Suggested stability tweak
-      expect(
-        screen.queryByText("online.deleteAccountConfirmTitle"),
-      ).not.toBeInTheDocument();
+      await waitFor(() => {
+        expect(
+          screen.queryByText("online.deleteAccountConfirmTitle"),
+        ).not.toBeInTheDocument();
+      });

As per coding guidelines, "**/*.test.tsx: Never use hardcoded delays - use waitFor or findBy* for async operations".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/unit/routes/settings.online.test.tsx` around lines 1074 - 1077,
The assertion that the modal is closed is racing the UI commit; update the test
to use async-safe waitFor checks instead of immediate query assertions: wrap the
check for absence of "online.deleteAccountConfirmTitle" in a waitFor (e.g.,
waitFor(() =>
expect(screen.queryByText("online.deleteAccountConfirmTitle")).not.toBeInTheDocument()))
and similarly assert logger.error is not called inside a waitFor (waitFor(() =>
expect(logger.error).not.toHaveBeenCalled())), referencing the existing use of
screen.queryByText and logger.error in this test.
src/__tests__/unit/lib/onlineApi.test.ts (1)

515-531: Add a test for the already-null store guard branch.

The interceptor intentionally skips setLoggedInUser(null) when loggedInUser is already null; adding that assertion will lock in the anti-churn behavior introduced in src/lib/onlineApi.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/unit/lib/onlineApi.test.ts` around lines 515 - 531, The test
should cover the branch where the store already has loggedInUser === null so the
interceptor does not call setLoggedInUser(null); update the unit test for
authRequestInterceptor to mock useStatusStore.getState to return { loggedInUser:
null, setLoggedInUser: mockSetLoggedInUser } (keep
FirebaseAuthentication.getCurrentUser mocked to return { user: null }), call
authRequestInterceptor and assert that mockSetLoggedInUser was NOT called,
ensuring the early-guard anti-churn behavior in authRequestInterceptor is locked
in.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/unit/lib/onlineApi.test.ts`:
- Around line 485-512: The tests for authRequestInterceptor use unsafe `as any`
casts—update them to use proper types: replace the mocked store state cast with
a typed literal using `satisfies` matching your store state shape (targeting
useStatusStore.getState), type the Axios config with
`InternalAxiosRequestConfig` when calling authRequestInterceptor, and make
Firebase mocks return correctly typed shapes for
FirebaseAuthentication.getCurrentUser and FirebaseAuthentication.getIdToken
(e.g., typed promise results instead of `as any`); ensure the NotSignedInError
assertion remains the same and remove all `as any` usages.

---

Nitpick comments:
In `@src/__tests__/unit/lib/onlineApi.test.ts`:
- Around line 515-531: The test should cover the branch where the store already
has loggedInUser === null so the interceptor does not call
setLoggedInUser(null); update the unit test for authRequestInterceptor to mock
useStatusStore.getState to return { loggedInUser: null, setLoggedInUser:
mockSetLoggedInUser } (keep FirebaseAuthentication.getCurrentUser mocked to
return { user: null }), call authRequestInterceptor and assert that
mockSetLoggedInUser was NOT called, ensuring the early-guard anti-churn behavior
in authRequestInterceptor is locked in.

In `@src/__tests__/unit/routes/settings.online.test.tsx`:
- Around line 1074-1077: The assertion that the modal is closed is racing the UI
commit; update the test to use async-safe waitFor checks instead of immediate
query assertions: wrap the check for absence of
"online.deleteAccountConfirmTitle" in a waitFor (e.g., waitFor(() =>
expect(screen.queryByText("online.deleteAccountConfirmTitle")).not.toBeInTheDocument()))
and similarly assert logger.error is not called inside a waitFor (waitFor(() =>
expect(logger.error).not.toHaveBeenCalled())), referencing the existing use of
screen.queryByText and logger.error in this test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 688b6639-43a2-4884-a307-84121ea9fe8f

📥 Commits

Reviewing files that changed from the base of the PR and between ea392c0 and 16be3c9.

📒 Files selected for processing (5)
  • src/__tests__/unit/lib/onlineApi.test.ts
  • src/__tests__/unit/routes/settings.online.test.tsx
  • src/lib/onlineApi.ts
  • src/routes/settings.online.tsx
  • src/translations/en-US.json

Comment thread src/__tests__/unit/lib/onlineApi.test.ts
- Replace as any casts with proper types: InternalAxiosRequestConfig for
  config args, satisfies GetCurrentUserResult/GetIdTokenResult for Firebase
  mocks, and satisfies PartialStoreState for store mocks
- Add test covering the loggedInUser === null guard (interceptor must not
  call setLoggedInUser when store is already null)
- Wrap post-waitFor assertions in their own waitFor to avoid UI commit races
@wizzomafizzo
wizzomafizzo merged commit 6d0cf58 into main Apr 17, 2026
5 checks passed
@wizzomafizzo
wizzomafizzo deleted the fix/account-deletion-not-signed-in branch April 17, 2026 23:55
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.

1 participant