fix(#411): QA-005 — production logging, error classification, session-expired state

logAuthError now always logs (not dev-only). Replaced isBackendError with
parseAuthError-based classification. signOut uses proper error type.
Session expiry sets explicit session_expired state. Login page logs in prod.
Fixed pre-existing lint violations in auth package (campsite rule).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-16 13:37:49 -06:00
parent 8a572e8525
commit 752e839054
10 changed files with 201 additions and 139 deletions

View File

@@ -34,6 +34,8 @@ describe("CallbackPage", (): void => {
isLoading: false,
isAuthenticated: false,
authError: null,
sessionExpiring: false,
sessionMinutesRemaining: 0,
signOut: vi.fn(),
});
});
@@ -51,6 +53,8 @@ describe("CallbackPage", (): void => {
isLoading: false,
isAuthenticated: false,
authError: null,
sessionExpiring: false,
sessionMinutesRemaining: 0,
signOut: vi.fn(),
});
@@ -141,6 +145,8 @@ describe("CallbackPage", (): void => {
isLoading: false,
isAuthenticated: false,
authError: null,
sessionExpiring: false,
sessionMinutesRemaining: 0,
signOut: vi.fn(),
});

View File

@@ -45,9 +45,8 @@ vi.mock("@/lib/auth/fetch-with-retry", () => ({
}));
// Mock parseAuthError to use the real implementation
vi.mock("@/lib/auth/auth-errors", async () => {
const actual = await vi.importActual<typeof import("@/lib/auth/auth-errors")>("@/lib/auth/auth-errors");
return actual;
vi.mock("@/lib/auth/auth-errors", async (importOriginal) => {
return importOriginal();
});
/* ------------------------------------------------------------------ */
@@ -193,7 +192,9 @@ describe("LoginPage", (): void => {
expect(screen.queryByRole("button", { name: /continue with/i })).not.toBeInTheDocument();
// Should show the unavailability banner (fix #5)
expect(screen.getByText("Some sign-in options may be temporarily unavailable.")).toBeInTheDocument();
expect(
screen.getByText("Some sign-in options may be temporarily unavailable.")
).toBeInTheDocument();
});
it("falls back to email-only on non-ok response", async (): Promise<void> => {

View File

@@ -58,9 +58,7 @@ export default function LoginPage(): ReactElement {
}
} catch (err: unknown) {
if (!cancelled) {
if (process.env.NODE_ENV === "development") {
console.error("[Auth] Failed to load auth config:", err);
}
console.error("[Auth] Failed to load auth config:", err);
setConfig(EMAIL_ONLY_CONFIG);
setUrlError("Some sign-in options may be temporarily unavailable.");
}
@@ -91,9 +89,7 @@ export default function LoginPage(): ReactElement {
setError(null);
signIn.oauth2({ providerId, callbackURL: "/" }).catch((err: unknown) => {
const message = err instanceof Error ? err.message : String(err);
if (process.env.NODE_ENV === "development") {
console.error(`[Auth] OAuth sign-in initiation failed for ${providerId}:`, message);
}
console.error(`[Auth] OAuth sign-in initiation failed for ${providerId}:`, message);
setError("Unable to connect to the sign-in provider. Please try again in a moment.");
setOauthLoading(null);
});
@@ -118,9 +114,7 @@ export default function LoginPage(): ReactElement {
}
} catch (err: unknown) {
const parsed = parseAuthError(err);
if (process.env.NODE_ENV === "development") {
console.error("[Auth] Credentials sign-in failed:", err);
}
console.error("[Auth] Credentials sign-in failed:", err);
setError(parsed.message);
} finally {
setCredentialsLoading(false);