diff --git a/frontend/src/lib/components/AppShell.test.ts b/frontend/src/lib/components/AppShell.test.ts index 3068940f..1439ecff 100644 --- a/frontend/src/lib/components/AppShell.test.ts +++ b/frontend/src/lib/components/AppShell.test.ts @@ -54,13 +54,19 @@ it('opens the user menu, exposing profile and admin links', async () => { expect(screen.getByTestId('appshell-user-menu-admin-item')).toBeTruthy(); }); -it('logs out: clears the session and redirects to /login', async () => { +it('logs out: clears the session and redirects to /login?source=logged_out', async () => { + // The `?source=logged_out` query param is consumed by the login page + // onMount branch to (a) show the "Successfully signed out" banner and + // (b) skip the doomed post-logout /me + /refresh probes. A plain + // `/login` navigation regresses both — the banner disappears and the + // layout re-fires the probes on remount. See AppShell.onLogout for + // the full comment on why the query is necessary here. m(logout).mockResolvedValue(undefined); render(AppShell, { props: { children } }); await fireEvent.click(screen.getByTestId('appshell-user-menu-btn')); await fireEvent.click(await screen.findByTestId('appshell-user-menu-logout-btn')); await waitFor(() => expect(logout).toHaveBeenCalled()); - await waitFor(() => expect(goto).toHaveBeenCalledWith('/login')); + await waitFor(() => expect(goto).toHaveBeenCalledWith('/login?source=logged_out')); expect(session.user).toBeNull(); }); diff --git a/frontend/src/routes/login/page.test.ts b/frontend/src/routes/login/page.test.ts index 6d62bb66..d511f048 100644 --- a/frontend/src/routes/login/page.test.ts +++ b/frontend/src/routes/login/page.test.ts @@ -91,9 +91,18 @@ it('exchanges an oidc code on mount and redirects', async () => { }); it('skips the form when already authenticated', async () => { + // The existing-session probe is gated on `hasSessionHint()` — no + // `oxicloud_csrf` cookie ⇒ probe is skipped and no `fetchMe` fires + // (see login/+page.svelte step 2 for the rationale). Plant the + // cookie the backend would have set on a live session so the + // probe path is exercised end-to-end here. + document.cookie = 'oxicloud_csrf=test-token; Path=/'; m(auth.fetchMe).mockResolvedValue({ id: '1' }); render(LoginPage); await waitFor(() => expect(goto).toHaveBeenCalled()); + // Clean up so the following tests don't inherit the hint and + // unexpectedly probe on their own boot. + document.cookie = 'oxicloud_csrf=; Path=/; Max-Age=0'; }); it('enters setup mode on a fresh install', async () => {