panels-origin/api/iam_test.ts
Cursor Agent aed2f4531b
Implement client IAM v2: roles per tenant, CRUD matrix, scope, and panel UI
- Add IAM v2 auth with role_id, is_owner, status checks, and API key without tenant_admin bypass
- Enforce CRUD permissions and project/warehouse scope across API routes
- Rewrite usuarios.vue with ribbon, tree, user/role management, and permission matrix
- Add dynamic menu, route guards, usePermissions/useScope composables
- Apply role templates on create; filter project docs by category; hide cost tab without permission
- Add iam_test unit tests; update bootstrap and SaaS tenant admin to use role_id

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 18:16:59 +00:00

72 lines
2.7 KiB
TypeScript

import { assert, assertEquals } from "jsr:@std/assert@1";
import {
ALL_PERMISSION_CODES,
LEGACY_TO_V2,
PROJECT_DOC_CATEGORIES,
projectDocPerm,
RESERVED_ROLE_CODES,
ROLE_TEMPLATES,
} from "./iam_catalog.ts";
import { filterSensitivePermissions } from "./permissions.ts";
import { assertProjectAccess, isOwnerAccountRow } from "./user_scope.ts";
Deno.test("IAM catálogo: permisos CRUD únicos", () => {
const codes = new Set(ALL_PERMISSION_CODES);
assertEquals(codes.size, ALL_PERMISSION_CODES.length);
assert(codes.has("workers.view"));
assert(codes.has("project_docs.tecnico.view"));
assert(codes.has("users.view"));
});
Deno.test("IAM: códigos de rol reservados", () => {
assert(RESERVED_ROLE_CODES.has("tenant_admin"));
assert(RESERVED_ROLE_CODES.has("user"));
assert(!RESERVED_ROLE_CODES.has("supervisor_obra"));
});
Deno.test("IAM: plantilla supervisor sin gastos ni IAM", () => {
const sup = ROLE_TEMPLATES.supervisor_obra.permissions;
assert(sup.includes("projects.view"));
assert(sup.includes("project_docs.tecnico.view"));
assert(!sup.some((p) => p.startsWith("expenses.")));
assert(!sup.some((p) => p.startsWith("users.")));
assert(!sup.some((p) => p.startsWith("settings.")));
});
Deno.test("IAM: plantilla contador con gastos y costos", () => {
const cont = ROLE_TEMPLATES.contador.permissions;
assert(cont.includes("expenses.view"));
assert(cont.includes("cost_control.view"));
assert(!cont.some((p) => p.startsWith("project_docs.")));
});
Deno.test("IAM: permisos sensibles filtrados para no propietario", () => {
const input = ["workers.view", "users.create", "settings.update", "reports.view"];
const filtered = filterSensitivePermissions(input, false);
assertEquals(filtered, ["workers.view", "reports.view"]);
assertEquals(filterSensitivePermissions(input, true), input);
});
Deno.test("IAM: alcance por obra", () => {
const scope = { allProjects: false, projectIds: [10, 20], warehouseCentral: false, warehouseProjects: true };
assert(assertProjectAccess(scope, 10));
assert(!assertProjectAccess(scope, 99));
assert(assertProjectAccess({ ...scope, allProjects: true }, 99));
});
Deno.test("IAM: cuenta propietaria", () => {
assert(isOwnerAccountRow({ is_owner: true }));
assert(isOwnerAccountRow({ role_code: "tenant_admin" }));
assert(!isOwnerAccountRow({ role_code: "supervisor" }));
});
Deno.test("IAM: categorías documento obra", () => {
for (const cat of PROJECT_DOC_CATEGORIES) {
assertEquals(projectDocPerm(cat, "view"), `project_docs.${cat}.view`);
}
});
Deno.test("IAM: mapeo legacy a v2", () => {
assert(LEGACY_TO_V2.manage_workers.includes("workers.view"));
assert(LEGACY_TO_V2.manage_expenses.includes("cost_control.view"));
});