Skip to content

fix(frontend): fixes library health pinging even when the user is not authenticated#385

Merged
balazs-szucs merged 4 commits into
grimmory-tools:developfrom
NamLe029:fix-lib-health-ping
Apr 7, 2026
Merged

fix(frontend): fixes library health pinging even when the user is not authenticated#385
balazs-szucs merged 4 commits into
grimmory-tools:developfrom
NamLe029:fix-lib-health-ping

Conversation

@NamLe029

@NamLe029 NamLe029 commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes library health pinging even when the user is not authenticated.

Linked Issue: Fixes #378

Changes

  • Split initialize() in LibraryHealthService into two separate methods: initialize() for WebSocket subscription setup, and fetchHealth() for the actual health query
  • Add an authenticatedEffect in AppComponent that calls fetchHealth() reactively, only when the user is authenticated
  • Add isAuthenticated computed signal to AuthService derived from the existing token signal
  • Update tests to reflect the new method split and authenticated/unauthenticated branching behavior

Summary by CodeRabbit

  • Refactor

    • Introduced reactive authentication state and split library health into an explicit one-time fetch and a separate ongoing real-time websocket flow; websocket wiring now starts independently while health fetch runs only when authenticated.
  • Tests

    • Updated and expanded tests/mocks to cover the new auth signal and health behaviors, including websocket startup when unauthenticated and distinctions between fetch vs. websocket calls.

@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Websocket subscription is started once auth initialization completes; a separate reactive effect triggers an explicit HTTP health fetch only when authService.isAuthenticated() is true. LibraryHealthService.initialize() was split into initWebsocket() and fetchHealth().

Changes

Cohort / File(s) Summary
App component & tests
frontend/src/app/app.component.ts, frontend/src/app/app.component.spec.ts
AppComponent now calls libraryHealthService.initWebsocket() after AuthInitializationService.initialized() and adds a reactive effect that calls libraryHealthService.fetchHealth() when authService.isAuthenticated() is true. Tests updated to use an auth object ({ ready, authenticated }) and assert initWebsocket() always runs while fetchHealth() runs only when authenticated.
Library health service & tests
frontend/src/app/features/book/service/library-health.service.ts, frontend/src/app/features/book/service/library-health.service.spec.ts
Replaced initialize() with two methods: initWebsocket() to subscribe to /topic/library-health and update query cache on incoming messages, and fetchHealth() to perform one-time queryClient.fetchQuery(...). Tests adjusted to call/await fetchHealth() and to invoke initWebsocket() for websocket scenarios.
Auth service
frontend/src/app/shared/service/auth.service.ts
Added a computed signal isAuthenticated derived from the existing token signal to expose reactive authentication state used by app effects.

Sequence Diagram(s)

sequenceDiagram
    actor Browser/App
    participant AuthInit as AuthInitializationService
    participant Auth as AuthService
    participant AppComp as AppComponent
    participant LHS as LibraryHealthService
    participant WS as WebSocket (/topic/library-health)
    participant QC as QueryClient

    Browser/App ->> AuthInit: boot
    AuthInit -->> AppComp: initialized() (auth ready)
    AppComp ->> LHS: initWebsocket()
    LHS ->> WS: subscribe()
    WS -->> LHS: message(payload)
    LHS ->> QC: setQueryData(LIBRARY_HEALTH_QUERY_KEY, payload.libraryHealth)

    Note over Auth,AppComp: reactive authenticated effect
    Auth -->> AppComp: isAuthenticated() -> true
    AppComp ->> LHS: fetchHealth()
    LHS ->> QC: fetchQuery(health)
    QC -->> LHS: health response
    LHS ->> QC: setQueryData(LIBRARY_HEALTH_QUERY_KEY, response)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰
I twitch my whiskers, ears alert and bright,
Webs hum softly once auth wakes the light.
If the token glows, I fetch the tale,
Else I only listen to the streamy trail.

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows the conventional commit format with 'fix(frontend):' prefix and clearly describes the main change addressing the linked issue.
Description check ✅ Passed The PR description includes all required sections: Description, Linked Issue, and Changes. It comprehensively documents the modifications and their purpose.
Linked Issues check ✅ Passed The code changes directly address issue #378 by preventing health pinging when unauthenticated through conditional fetchHealth() calls gated by the new isAuthenticated signal.
Out of Scope Changes check ✅ Passed All changes are scoped to addressing the authentication check for library health pinging; no unrelated modifications were introduced.

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

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

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.

@NamLe029 NamLe029 changed the title Fix lib health ping fix(frontend): fixes library health pinging even when the user is not authenticated Apr 5, 2026
@coderabbitai coderabbitai Bot requested a review from balazs-szucs April 5, 2026 03:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/app/features/book/service/library-health.service.ts (1)

35-40: ⚠️ Potential issue | 🟡 Minor

WebSocket subscription is not tracked for cleanup.

The subscription returned by this.rxStompService.watch(...).subscribe(...) is not stored, so it cannot be unsubscribed when the service is destroyed. While root-provided services typically live for the app's lifetime, this pattern can lead to memory leaks if the service is ever recreated or in testing scenarios.

🛡️ Proposed fix to track the subscription
+import {computed, DestroyRef, inject, Injectable} from '@angular/core';
+import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
...

 `@Injectable`({providedIn: 'root'})
 export class LibraryHealthService {
   private readonly url = `${API_CONFIG.BASE_URL}/api/v1/libraries/health`;
   private http = inject(HttpClient);
   private rxStompService = inject(RxStompService);
   private queryClient = inject(QueryClient);
+  private destroyRef = inject(DestroyRef);
   ...

   initialize(): void {
-    this.rxStompService.watch('/topic/library-health').subscribe(msg => {
+    this.rxStompService.watch('/topic/library-health').pipe(
+      takeUntilDestroyed(this.destroyRef)
+    ).subscribe(msg => {
       const payload = JSON.parse(msg.body);
       this.queryClient.setQueryData(LIBRARY_HEALTH_QUERY_KEY, payload.libraryHealth);
     });
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/features/book/service/library-health.service.ts` around
lines 35 - 40, The WebSocket subscription created in initialize() via
this.rxStompService.watch(...).subscribe(...) is not stored for cleanup; capture
the returned Subscription (e.g., a private field like rxStompSubscription:
Subscription) when calling initialize(), import Subscription from 'rxjs', and
then unsubscribe in a destroy lifecycle method (implement ngOnDestroy() or a
public destroy() that calls this.rxStompSubscription.unsubscribe() if set) to
avoid memory leaks; update initialize(), add the private field, import, and the
unsubscribe logic referencing initialize(), rxStompService.watch, and
rxStompSubscription.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/app/features/book/service/library-health.service.ts`:
- Around line 31-33: The test expects initialize() to perform the HTTP fetch but
after refactor initialize() only sets up the websocket subscription and
fetchHealth() does the HTTP call; update the spec so after calling
initializeService() it also calls libraryHealthService.fetchHealth() (or replace
the assertion to only verify websocket subscription behavior) and assert that
httpGetSpy is called by that fetchHealth() invocation; reference the service
methods initialize() and fetchHealth() and the test spies (e.g., httpGetSpy /
initializeService()) when making the change.

---

Outside diff comments:
In `@frontend/src/app/features/book/service/library-health.service.ts`:
- Around line 35-40: The WebSocket subscription created in initialize() via
this.rxStompService.watch(...).subscribe(...) is not stored for cleanup; capture
the returned Subscription (e.g., a private field like rxStompSubscription:
Subscription) when calling initialize(), import Subscription from 'rxjs', and
then unsubscribe in a destroy lifecycle method (implement ngOnDestroy() or a
public destroy() that calls this.rxStompSubscription.unsubscribe() if set) to
avoid memory leaks; update initialize(), add the private field, import, and the
unsubscribe logic referencing initialize(), rxStompService.watch, and
rxStompSubscription.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d23ffec7-ab7c-4680-b0bd-d565f165ac55

📥 Commits

Reviewing files that changed from the base of the PR and between 4e9cf79 and 768153b.

📒 Files selected for processing (4)
  • frontend/src/app/app.component.spec.ts
  • frontend/src/app/app.component.ts
  • frontend/src/app/features/book/service/library-health.service.ts
  • frontend/src/app/shared/service/auth.service.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (4)
frontend/src/**/*.{ts,tsx,html,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation in TypeScript, HTML, and SCSS files

Files:

  • frontend/src/app/app.component.ts
  • frontend/src/app/shared/service/auth.service.ts
  • frontend/src/app/features/book/service/library-health.service.ts
  • frontend/src/app/app.component.spec.ts
frontend/src/app/**/*.component.ts

📄 CodeRabbit inference engine (AGENTS.md)

Keep Angular code on standalone components. Do not add NgModules

Files:

  • frontend/src/app/app.component.ts
frontend/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/src/app/**/*.{ts,tsx}: Prefer inject() over constructor injection
Follow frontend/eslint.config.js: component selectors use app-*, directive selectors use app*, and any is disallowed

Files:

  • frontend/src/app/app.component.ts
  • frontend/src/app/shared/service/auth.service.ts
  • frontend/src/app/features/book/service/library-health.service.ts
  • frontend/src/app/app.component.spec.ts
frontend/src/**/*.{test,spec}.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use Vitest for tests in the frontend

Files:

  • frontend/src/app/app.component.spec.ts
🔇 Additional comments (4)
frontend/src/app/shared/service/auth.service.ts (1)

24-25: LGTM!

Clean addition of isAuthenticated as a computed signal derived from token. This reactive approach integrates well with Angular's signal-based architecture and enables the authenticatedEffect in AppComponent to respond to authentication changes.

frontend/src/app/app.component.ts (1)

60-65: LGTM!

The authenticatedEffect correctly gates the fetchHealth() call behind an authentication check, which directly addresses issue #378 by preventing health endpoint pings when unauthenticated. The reactive effect pattern ensures fetchHealth() is called whenever the user becomes authenticated (including after re-login).

frontend/src/app/app.component.spec.ts (2)

149-155: Good test coverage for the unauthenticated scenario.

This test directly validates the fix for issue #378 by confirming that fetchHealth() is not called when the user is unauthenticated, while initialize() (websocket setup) still runs. This prevents the infinite crash loop described in the issue.


64-67: Clean test configuration refactor.

The auth parameter structure { ready, authenticated } cleanly separates initialization state from authentication state, making tests more expressive and easier to maintain.

Comment thread frontend/src/app/features/book/service/library-health.service.ts
@NamLe029 NamLe029 force-pushed the fix-lib-health-ping branch 2 times, most recently from b9b1c87 to fe20205 Compare April 5, 2026 03:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/app/features/book/service/library-health.service.spec.ts (1)

40-44: Optional: simplify initWebsocket() helper to a direct call.

initWebsocket() is currently wrapped with a spy/await pattern, but the helper can stay synchronous and clearer.

♻️ Suggested simplification
-  async function initWebsocket(): Promise<void> {
-    const initializeSpy = vi.spyOn(service, 'initWebsocket');
-    service.initWebsocket();
-    await initializeSpy.mock.results[0]?.value;
-  }
+  function initWebsocket(): void {
+    service.initWebsocket();
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/features/book/service/library-health.service.spec.ts` around
lines 40 - 44, The test helper initWebsocket currently creates a spy
(initializeSpy = vi.spyOn(service, 'initWebsocket')) and awaits the
spy.mock.results[0]?.value; simplify it by removing the spy and directly
invoking and awaiting the real method: replace the spy/await pattern with a
direct call to service.initWebsocket() (e.g., await service.initWebsocket() if
the method returns a Promise) and remove initializeSpy and any mock.results
usage to keep the helper synchronous/clear.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/src/app/features/book/service/library-health.service.spec.ts`:
- Around line 40-44: The test helper initWebsocket currently creates a spy
(initializeSpy = vi.spyOn(service, 'initWebsocket')) and awaits the
spy.mock.results[0]?.value; simplify it by removing the spy and directly
invoking and awaiting the real method: replace the spy/await pattern with a
direct call to service.initWebsocket() (e.g., await service.initWebsocket() if
the method returns a Promise) and remove initializeSpy and any mock.results
usage to keep the helper synchronous/clear.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0cb2a41f-6852-4aa2-b089-6fcdc02fb3bb

📥 Commits

Reviewing files that changed from the base of the PR and between 768153b and b9b1c87.

📒 Files selected for processing (4)
  • frontend/src/app/app.component.spec.ts
  • frontend/src/app/app.component.ts
  • frontend/src/app/features/book/service/library-health.service.spec.ts
  • frontend/src/app/features/book/service/library-health.service.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/app/app.component.ts
  • frontend/src/app/features/book/service/library-health.service.ts
  • frontend/src/app/app.component.spec.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (3)
frontend/src/**/*.{ts,tsx,html,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation in TypeScript, HTML, and SCSS files

Files:

  • frontend/src/app/features/book/service/library-health.service.spec.ts
frontend/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/src/app/**/*.{ts,tsx}: Prefer inject() over constructor injection
Follow frontend/eslint.config.js: component selectors use app-*, directive selectors use app*, and any is disallowed

Files:

  • frontend/src/app/features/book/service/library-health.service.spec.ts
frontend/src/**/*.{test,spec}.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use Vitest for tests in the frontend

Files:

  • frontend/src/app/features/book/service/library-health.service.spec.ts
🔇 Additional comments (3)
frontend/src/app/features/book/service/library-health.service.spec.ts (3)

34-38: Good synchronization for fetchHealth() assertions.

Waiting on the fetchQuery promise before asserting state is a solid way to avoid timing-related test flakes.


46-77: Nice update of health-path tests to use fetchHealth().

These cases now correctly validate the new split API behavior while preserving expected health/unhealthy outcomes.


79-87: Websocket state-update coverage looks correct after method split.

Calling websocket init before emitting test messages keeps this scenario aligned with runtime behavior.

@NamLe029 NamLe029 force-pushed the fix-lib-health-ping branch from fe20205 to bec7392 Compare April 5, 2026 03:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/app/app.component.ts`:
- Around line 60-65: The authenticatedEffect calls
libraryHealthService.fetchHealth() solely based on
authService.isAuthenticated(), but it must also wait for auth bootstrap to
complete; update authenticatedEffect to gate the call with
authInit.initialized() (same pattern used in syncAuthInitializationEffect) so
that fetchHealth() runs only when both authInit.initialized() is true and
isAuthenticated is true; locate and modify the authenticatedEffect function to
include the initialization check alongside the existing isAuthenticated
condition.
- Around line 55-56: initWebsocket() currently creates an internal subscription
to /topic/library-health but returns void so the component can't track or tear
it down; update LibraryHealthService.initWebsocket() to return the Subscription
object (rxjs.Subscription) instead of void, then in AppComponent where you call
this.libraryHealthService.initWebsocket() capture that return and push it into
the existing subscriptions array (the same array unsubscribed in ngOnDestroy());
this ensures the websocket listener is tracked and will be unsubscribed when the
component is destroyed.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: add42bf1-ef18-4132-9b93-5e34a0879b38

📥 Commits

Reviewing files that changed from the base of the PR and between b9b1c87 and fe20205.

📒 Files selected for processing (4)
  • frontend/src/app/app.component.spec.ts
  • frontend/src/app/app.component.ts
  • frontend/src/app/features/book/service/library-health.service.spec.ts
  • frontend/src/app/features/book/service/library-health.service.ts
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/app/features/book/service/library-health.service.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/app/features/book/service/library-health.service.ts
  • frontend/src/app/app.component.spec.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Test Suite / Backend Tests
🧰 Additional context used
📓 Path-based instructions (3)
frontend/src/**/*.{ts,tsx,html,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation in TypeScript, HTML, and SCSS files

Files:

  • frontend/src/app/app.component.ts
frontend/src/app/**/*.component.ts

📄 CodeRabbit inference engine (AGENTS.md)

Keep Angular code on standalone components. Do not add NgModules

Files:

  • frontend/src/app/app.component.ts
frontend/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/src/app/**/*.{ts,tsx}: Prefer inject() over constructor injection
Follow frontend/eslint.config.js: component selectors use app-*, directive selectors use app*, and any is disallowed

Files:

  • frontend/src/app/app.component.ts

Comment thread frontend/src/app/app.component.ts
Comment thread frontend/src/app/app.component.ts

@zachyale zachyale left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@NamLe029 Let me know if you plan on modifying this PR further to handle subscription teardown for the LibraryHealthService now, or if you plan on making a follow up ticket.

If the PR is feature complete now, I'm happy to approve as-is and merge.

@NamLe029

NamLe029 commented Apr 7, 2026

Copy link
Copy Markdown
Contributor Author

should be done rn. ready to merge

@balazs-szucs balazs-szucs merged commit 5bdae1b into grimmory-tools:develop Apr 7, 2026
13 checks passed
zachyale pushed a commit to zachyale/grimmory that referenced this pull request Apr 17, 2026
… authenticated (grimmory-tools#385)

* fix(frontend): library health pings even not authenticated

* test

* authInitialized guard

* subscription
zachyale pushed a commit to zachyale/grimmory that referenced this pull request Apr 17, 2026
… authenticated (grimmory-tools#385)

* fix(frontend): library health pings even not authenticated

* test

* authInitialized guard

* subscription
zachyale pushed a commit that referenced this pull request Apr 17, 2026
… authenticated (#385)

* fix(frontend): library health pings even not authenticated

* test

* authInitialized guard

* subscription
zachyale pushed a commit that referenced this pull request Apr 22, 2026
… authenticated (#385)

* fix(frontend): library health pings even not authenticated

* test

* authInitialized guard

* subscription
dsmouse pushed a commit to dsmouse/grimmory that referenced this pull request May 11, 2026
… authenticated (grimmory-tools#385)

* fix(frontend): library health pings even not authenticated

* test

* authInitialized guard

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Library Health Check pings even if unauthenticated

3 participants