import { describe, it, expect } from "vitest"; import { parseAuthError, getErrorMessage } from "./auth-errors"; import type { AuthErrorCode, ParsedAuthError } from "./auth-errors"; /** Words that must never appear in PDA-friendly messages. */ const FORBIDDEN_WORDS = [ "overdue", "urgent", "must", "critical", "required", "error", "failed", "failure", ]; describe("parseAuthError", (): void => { it("should classify TypeError('Failed to fetch') as network_error", (): void => { const result: ParsedAuthError = parseAuthError(new TypeError("Failed to fetch")); expect(result.code).toBe("network_error"); expect(result.retryable).toBe(true); }); it("should classify TypeError with 'fetch' anywhere in message as network_error", (): void => { const result: ParsedAuthError = parseAuthError(new TypeError("Could not fetch resource")); expect(result.code).toBe("network_error"); }); it("should classify Error('Unauthorized') as invalid_credentials", (): void => { const result: ParsedAuthError = parseAuthError(new Error("Unauthorized")); expect(result.code).toBe("invalid_credentials"); expect(result.retryable).toBe(false); }); it("should classify Error('Forbidden') as invalid_credentials", (): void => { const result: ParsedAuthError = parseAuthError(new Error("Forbidden")); expect(result.code).toBe("invalid_credentials"); }); it("should classify HTTP 401 response as invalid_credentials", (): void => { const result: ParsedAuthError = parseAuthError({ status: 401 }); expect(result.code).toBe("invalid_credentials"); expect(result.retryable).toBe(false); }); it("should classify HTTP 403 response as invalid_credentials", (): void => { const result: ParsedAuthError = parseAuthError({ status: 403 }); expect(result.code).toBe("invalid_credentials"); }); it("should classify HTTP 429 response as rate_limited", (): void => { const result: ParsedAuthError = parseAuthError({ status: 429 }); expect(result.code).toBe("rate_limited"); expect(result.retryable).toBe(false); }); it("should classify HTTP 500 response as server_error", (): void => { const result: ParsedAuthError = parseAuthError({ status: 500 }); expect(result.code).toBe("server_error"); expect(result.retryable).toBe(true); }); it("should classify HTTP 502 response as server_error", (): void => { const result: ParsedAuthError = parseAuthError({ status: 502 }); expect(result.code).toBe("server_error"); }); it("should classify HTTP 503 response as server_error", (): void => { const result: ParsedAuthError = parseAuthError({ status: 503 }); expect(result.code).toBe("server_error"); }); it("should classify string 'access_denied' as access_denied", (): void => { const result: ParsedAuthError = parseAuthError("access_denied"); expect(result.code).toBe("access_denied"); expect(result.retryable).toBe(false); }); it("should classify string 'session_expired' as session_expired", (): void => { const result: ParsedAuthError = parseAuthError("session_expired"); expect(result.code).toBe("session_expired"); expect(result.retryable).toBe(false); }); it("should classify string 'rate_limited' as rate_limited", (): void => { const result: ParsedAuthError = parseAuthError("rate_limited"); expect(result.code).toBe("rate_limited"); }); it("should classify string 'server_error' as server_error", (): void => { const result: ParsedAuthError = parseAuthError("server_error"); expect(result.code).toBe("server_error"); expect(result.retryable).toBe(true); }); it("should classify string 'network_error' as network_error", (): void => { const result: ParsedAuthError = parseAuthError("network_error"); expect(result.code).toBe("network_error"); expect(result.retryable).toBe(true); }); it("should classify unknown string as unknown", (): void => { const result: ParsedAuthError = parseAuthError("something_weird"); expect(result.code).toBe("unknown"); expect(result.retryable).toBe(false); }); it("should classify null as unknown", (): void => { const result: ParsedAuthError = parseAuthError(null); expect(result.code).toBe("unknown"); }); it("should classify undefined as unknown", (): void => { const result: ParsedAuthError = parseAuthError(undefined); expect(result.code).toBe("unknown"); }); it("should classify a number as unknown", (): void => { const result: ParsedAuthError = parseAuthError(42); expect(result.code).toBe("unknown"); }); it("should classify an empty object as unknown", (): void => { const result: ParsedAuthError = parseAuthError({}); expect(result.code).toBe("unknown"); }); it("should classify Error('Internal Server Error') as server_error", (): void => { const result: ParsedAuthError = parseAuthError(new Error("Internal Server Error")); expect(result.code).toBe("server_error"); expect(result.retryable).toBe(true); }); it("should classify Error('Service Unavailable') as server_error", (): void => { const result: ParsedAuthError = parseAuthError(new Error("Service Unavailable")); expect(result.code).toBe("server_error"); }); it("should classify Error('Too many requests') as rate_limited", (): void => { const result: ParsedAuthError = parseAuthError(new Error("Too many requests")); expect(result.code).toBe("rate_limited"); }); it("should classify Error('Session expired') as session_expired", (): void => { const result: ParsedAuthError = parseAuthError(new Error("Session expired")); expect(result.code).toBe("session_expired"); }); it("should classify Error('Network issue') as network_error", (): void => { const result: ParsedAuthError = parseAuthError(new Error("Network issue")); expect(result.code).toBe("network_error"); }); it("should classify Error with unknown message as unknown", (): void => { const result: ParsedAuthError = parseAuthError(new Error("Something completely different")); expect(result.code).toBe("unknown"); }); }); describe("parseAuthError retryable flag", (): void => { it("should mark network_error as retryable", (): void => { expect(parseAuthError(new TypeError("Failed to fetch")).retryable).toBe(true); }); it("should mark server_error as retryable", (): void => { expect(parseAuthError({ status: 500 }).retryable).toBe(true); }); it("should mark invalid_credentials as not retryable", (): void => { expect(parseAuthError(new Error("Unauthorized")).retryable).toBe(false); }); it("should mark access_denied as not retryable", (): void => { expect(parseAuthError("access_denied").retryable).toBe(false); }); it("should mark rate_limited as not retryable", (): void => { expect(parseAuthError("rate_limited").retryable).toBe(false); }); it("should mark session_expired as not retryable", (): void => { expect(parseAuthError("session_expired").retryable).toBe(false); }); it("should mark unknown as not retryable", (): void => { expect(parseAuthError(null).retryable).toBe(false); }); }); describe("getErrorMessage", (): void => { const allCodes: AuthErrorCode[] = [ "access_denied", "invalid_credentials", "server_error", "network_error", "rate_limited", "session_expired", "unknown", ]; it("should return the correct message for access_denied", (): void => { expect(getErrorMessage("access_denied")).toBe( "Authentication paused. Please try again when ready." ); }); it("should return the correct message for invalid_credentials", (): void => { expect(getErrorMessage("invalid_credentials")).toBe( "The email and password combination wasn't recognized." ); }); it("should return the correct message for server_error", (): void => { expect(getErrorMessage("server_error")).toBe( "The service is taking a break. Please try again in a moment." ); }); it("should return the correct message for network_error", (): void => { expect(getErrorMessage("network_error")).toBe( "Unable to connect. Check your network and try again." ); }); it("should return the correct message for rate_limited", (): void => { expect(getErrorMessage("rate_limited")).toBe( "You've tried a few times. Take a moment and try again shortly." ); }); it("should return the correct message for session_expired", (): void => { expect(getErrorMessage("session_expired")).toBe( "Your session ended. Please sign in again when ready." ); }); it("should return the correct message for unknown", (): void => { expect(getErrorMessage("unknown")).toBe( "Authentication didn't complete. Please try again when ready." ); }); it("should return a non-empty string for every error code", (): void => { for (const code of allCodes) { const message = getErrorMessage(code); expect(message).toBeTruthy(); expect(message.length).toBeGreaterThan(0); } }); }); describe("PDA-friendly language compliance", (): void => { const allCodes: AuthErrorCode[] = [ "access_denied", "invalid_credentials", "server_error", "network_error", "rate_limited", "session_expired", "unknown", ]; it("should not contain any forbidden words in any message", (): void => { for (const code of allCodes) { const message = getErrorMessage(code).toLowerCase(); for (const forbidden of FORBIDDEN_WORDS) { expect(message).not.toContain(forbidden); } } }); it("should not contain forbidden words in parseAuthError output messages", (): void => { const testInputs: unknown[] = [ new TypeError("Failed to fetch"), new Error("Unauthorized"), new Error("Internal Server Error"), { status: 429 }, { status: 500 }, "access_denied", "session_expired", null, undefined, 42, ]; for (const input of testInputs) { const result = parseAuthError(input); const message = result.message.toLowerCase(); for (const forbidden of FORBIDDEN_WORDS) { expect(message).not.toContain(forbidden); } } }); });