From b675db1324c56ce2f95feecc38b9b2f31c26fae2 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Mon, 16 Feb 2026 14:05:30 -0600 Subject: [PATCH] =?UTF-8?q?test(#411):=20QA-015=20=E2=80=94=20add=20creden?= =?UTF-8?q?tials=20fallback=20test=20+=20fix=20refreshSession=20test=20nam?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add test for non-string error.message fallback in handleCredentialsLogin. Rename misleading refreshSession test to match actual behavior. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/app/(auth)/login/page.test.tsx | 27 +++++++++++++++++++++ apps/web/src/lib/auth/auth-context.test.tsx | 20 ++++----------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/apps/web/src/app/(auth)/login/page.test.tsx b/apps/web/src/app/(auth)/login/page.test.tsx index d76a2fb..4509f15 100644 --- a/apps/web/src/app/(auth)/login/page.test.tsx +++ b/apps/web/src/app/(auth)/login/page.test.tsx @@ -341,6 +341,33 @@ describe("LoginPage", (): void => { expect(mockPush).not.toHaveBeenCalled(); }); + it("should show fallback PDA-friendly message when error.message is not a string", async (): Promise => { + mockFetchConfig(EMAIL_ONLY_CONFIG); + // Return an error object where message is NOT a string (e.g. numeric code, no message field) + mockSignInEmail.mockResolvedValueOnce({ + error: { code: 123 }, + }); + const user = userEvent.setup(); + + render(); + + await waitFor((): void => { + expect(screen.getByLabelText(/email/i)).toBeInTheDocument(); + }); + + await user.type(screen.getByLabelText(/email/i), "test@example.com"); + await user.type(screen.getByLabelText(/password/i), "wrong"); + await user.click(screen.getByRole("button", { name: /continue/i })); + + await waitFor((): void => { + expect( + screen.getByText("Unable to sign in. Please check your credentials and try again.") + ).toBeInTheDocument(); + }); + + expect(mockPush).not.toHaveBeenCalled(); + }); + it("shows parseAuthError message on unexpected sign-in exception", async (): Promise => { mockFetchConfig(EMAIL_ONLY_CONFIG); mockSignInEmail.mockRejectedValueOnce(new TypeError("Failed to fetch")); diff --git a/apps/web/src/lib/auth/auth-context.test.tsx b/apps/web/src/lib/auth/auth-context.test.tsx index d0c88dd..2a0b013 100644 --- a/apps/web/src/lib/auth/auth-context.test.tsx +++ b/apps/web/src/lib/auth/auth-context.test.tsx @@ -411,7 +411,7 @@ describe("AuthContext", (): void => { consoleErrorSpy.mockRestore(); }); - it("should clear authError after successful session refresh", async (): Promise => { + it("should persist authError across re-renders when no new session check occurs", async (): Promise => { const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => { // Intentionally empty }); @@ -429,26 +429,16 @@ describe("AuthContext", (): void => { expect(screen.getByTestId("auth-error")).toHaveTextContent("network"); }); - // Set up successful response for refresh - const mockUser: AuthUser = { - id: "user-1", - email: "test@example.com", - name: "Test User", - }; - vi.mocked(apiGet).mockResolvedValueOnce({ - user: mockUser, - session: { id: "session-1", token: "token123", expiresAt: futureExpiry() }, - }); - - // Trigger a rerender (simulating refreshSession being called) + // Re-render does NOT trigger a new session check, so authError persists rerender( ); - // The initial render will have checked session once, error should still be there - // A real refresh would need to call refreshSession + // authError should still be "network" — re-render alone does not clear it + expect(screen.getByTestId("auth-error")).toHaveTextContent("network"); + consoleErrorSpy.mockRestore(); }); });