Browse Source

RHL-020 feat(authMessages): add centralized auth messages and corresponding tests for login alerts and errors

Code_Uwe 1 month ago
parent
commit
b46590609a
2 changed files with 134 additions and 0 deletions
  1. 69 0
      lib/frontend/authMessages.js
  2. 65 0
      lib/frontend/authMessages.test.js

+ 69 - 0
lib/frontend/authMessages.js

@@ -0,0 +1,69 @@
+// ---------------------------------------------------------------------------
+// Folder: lib/frontend
+// File: authMessages.js
+// Relative Path: lib/frontend/authMessages.js
+// ---------------------------------------------------------------------------
+
+import { ApiClientError } from "@/lib/frontend/apiClient";
+import { LOGIN_REASONS } from "@/lib/frontend/authRedirect";
+
+/**
+ * Auth UI messages (RHL-020).
+ *
+ * Why this file exists:
+ * - Keep user-facing auth messages consistent and centralized.
+ * - Avoid string duplication across multiple UI components.
+ * - Make mappings easy to test (pure functions, no UI dependencies).
+ */
+
+/**
+ * Return a user-facing login-page alert message for a known reason.
+ *
+ * @param {string|null} reason
+ * @returns {{ title: string, description: string } | null}
+ */
+export function getLoginReasonAlert(reason) {
+	if (reason === LOGIN_REASONS.EXPIRED) {
+		return {
+			title: "Session expired",
+			description: "Your session has expired. Please log in again.",
+		};
+	}
+
+	if (reason === LOGIN_REASONS.LOGGED_OUT) {
+		return {
+			title: "Logged out",
+			description: "You have been logged out successfully.",
+		};
+	}
+
+	return null;
+}
+
+/**
+ * Map an error to a safe, user-friendly login error message.
+ *
+ * Principles:
+ * - Do not leak internal server details to the UI.
+ * - Use stable error codes (ApiClientError.code) for UX decisions.
+ *
+ * @param {unknown} err
+ * @returns {string}
+ */
+export function getLoginErrorMessage(err) {
+	if (err instanceof ApiClientError) {
+		if (err.code === "AUTH_INVALID_CREDENTIALS") {
+			return "Invalid username or password.";
+		}
+
+		if (err.code === "CLIENT_NETWORK_ERROR") {
+			return "Network error. Please check your connection and try again.";
+		}
+
+		// Generic fallback for other known API client errors
+		return "Login failed. Please try again.";
+	}
+
+	// Unknown error type
+	return "Login failed. Please try again.";
+}

+ 65 - 0
lib/frontend/authMessages.test.js

@@ -0,0 +1,65 @@
+/* @vitest-environment node */
+
+// ---------------------------------------------------------------------------
+// Folder: lib/frontend
+// File: authMessages.test.js
+// Relative Path: lib/frontend/authMessages.test.js
+// ---------------------------------------------------------------------------
+
+import { describe, it, expect } from "vitest";
+import { ApiClientError } from "@/lib/frontend/apiClient";
+import { LOGIN_REASONS } from "@/lib/frontend/authRedirect";
+import { getLoginReasonAlert, getLoginErrorMessage } from "./authMessages.js";
+
+describe("lib/frontend/authMessages", () => {
+	describe("getLoginReasonAlert", () => {
+		it("returns alert copy for reason=expired", () => {
+			expect(getLoginReasonAlert(LOGIN_REASONS.EXPIRED)).toEqual({
+				title: "Session expired",
+				description: "Your session has expired. Please log in again.",
+			});
+		});
+
+		it("returns alert copy for reason=logged-out", () => {
+			expect(getLoginReasonAlert(LOGIN_REASONS.LOGGED_OUT)).toEqual({
+				title: "Logged out",
+				description: "You have been logged out successfully.",
+			});
+		});
+
+		it("returns null for unknown or missing reason", () => {
+			expect(getLoginReasonAlert(null)).toBe(null);
+			expect(getLoginReasonAlert("unknown")).toBe(null);
+		});
+	});
+
+	describe("getLoginErrorMessage", () => {
+		it("maps AUTH_INVALID_CREDENTIALS to a friendly message", () => {
+			const err = new ApiClientError({
+				status: 401,
+				code: "AUTH_INVALID_CREDENTIALS",
+				message: "Invalid credentials",
+			});
+
+			expect(getLoginErrorMessage(err)).toBe("Invalid username or password.");
+		});
+
+		it("maps CLIENT_NETWORK_ERROR to a friendly message", () => {
+			const err = new ApiClientError({
+				status: 0,
+				code: "CLIENT_NETWORK_ERROR",
+				message: "Network error",
+			});
+
+			expect(getLoginErrorMessage(err)).toBe(
+				"Network error. Please check your connection and try again."
+			);
+		});
+
+		it("uses a generic message for other errors", () => {
+			expect(getLoginErrorMessage(new Error("boom"))).toBe(
+				"Login failed. Please try again."
+			);
+		});
+	});
+});