Browse Source

RHL-021 feat(rbac): implement branch UI decision logic and corresponding tests

Code_Uwe 1 tháng trước cách đây
mục cha
commit
4ed43e83d7

+ 42 - 0
lib/frontend/rbac/branchUiDecision.js

@@ -0,0 +1,42 @@
+import {
+	getBranchAccess,
+	BRANCH_ACCESS,
+} from "@/lib/frontend/rbac/branchAccess";
+
+export const BRANCH_UI_DECISION = Object.freeze({
+	ALLOWED: "allowed",
+	FORBIDDEN: "forbidden",
+	NOT_FOUND: "not-found",
+});
+
+/**
+ * Decide which UI outcome should be shown for a branch route.
+ *
+ * Notes:
+ * - This function assumes the user is authenticated (handled by AuthProvider).
+ * - For admin/dev we can optionally validate branch existence using `allowedBranches`
+ *   from GET /api/branches. If the list is not available, we fail open.
+ *
+ * @param {{
+ *   user: { userId: string, role: string, branchId: string|null },
+ *   branch: string,
+ *   allowedBranches?: string[]|null
+ * }} input
+ * @returns {"allowed"|"forbidden"|"not-found"}
+ */
+export function decideBranchUi({ user, branch, allowedBranches = null }) {
+	const access = getBranchAccess(user, branch);
+
+	if (access === BRANCH_ACCESS.FORBIDDEN) {
+		return BRANCH_UI_DECISION.FORBIDDEN;
+	}
+
+	// Only admin/dev may validate existence across branches.
+	if (user.role === "admin" || user.role === "dev") {
+		if (Array.isArray(allowedBranches) && !allowedBranches.includes(branch)) {
+			return BRANCH_UI_DECISION.NOT_FOUND;
+		}
+	}
+
+	return BRANCH_UI_DECISION.ALLOWED;
+}

+ 42 - 0
lib/frontend/rbac/branchUiDecision.test.js

@@ -0,0 +1,42 @@
+/* @vitest-environment node */
+
+import { describe, it, expect } from "vitest";
+import { decideBranchUi, BRANCH_UI_DECISION } from "./branchUiDecision.js";
+
+describe("lib/frontend/rbac/branchUiDecision", () => {
+	it("returns FORBIDDEN for branch users accessing other branches", () => {
+		const user = { userId: "u1", role: "branch", branchId: "NL01" };
+
+		expect(decideBranchUi({ user, branch: "NL02" })).toBe(
+			BRANCH_UI_DECISION.FORBIDDEN
+		);
+	});
+
+	it("returns ALLOWED for branch users accessing their own branch", () => {
+		const user = { userId: "u1", role: "branch", branchId: "NL01" };
+
+		expect(decideBranchUi({ user, branch: "NL01" })).toBe(
+			BRANCH_UI_DECISION.ALLOWED
+		);
+	});
+
+	it("returns NOT_FOUND for admin/dev when branch is not in allowedBranches", () => {
+		const admin = { userId: "u2", role: "admin", branchId: null };
+
+		expect(
+			decideBranchUi({
+				user: admin,
+				branch: "NL200",
+				allowedBranches: ["NL01", "NL02"],
+			})
+		).toBe(BRANCH_UI_DECISION.NOT_FOUND);
+	});
+
+	it("fails open (ALLOWED) for admin/dev when allowedBranches is not available", () => {
+		const admin = { userId: "u2", role: "admin", branchId: null };
+
+		expect(decideBranchUi({ user: admin, branch: "NL200" })).toBe(
+			BRANCH_UI_DECISION.ALLOWED
+		);
+	});
+});