Files
calendar/packages/features/pbac/services/__tests__/permission.service.test.ts
sean-brydonGitHubEunjae Leecubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
9dca13305f feat: pbac core ui (#21471)
* migration plus feature flag

* show navigation route + inital roles migration

* add a check permission use case to take in the feature flags for a team

* bulk update script

* inital frontend work for displaying roles

* move to a more "anemic domain models" approach

* update test to match new DDD strutcture

* fix tests

* update transaction call back types to include trx

* align fe types after transaction to DDD

* move away from usecases to a more domain tailored approach

* get permissions per resource and map them to domain permission string

* update permision logic

* correctly get the logic for *.* permissions on owner

* wip sheet logic for ssr

* role list

* use nuqs for sheet parsing on handle change

* fox

* improve hook usage

* enable PBAC router

* delete modal etc

* i18n and inital rough layout of roles + permisions creating

* add color to migrations

* add colors and new method to tests

* move hooks out of infra into client with provider

* move hooks out of infra into client with provider

* memo features and ensure render once

* remove comment

* seed color

* use role colours

* match i18n

* add custom color picker to edit/create form

* fix advanced mode toggle

* more work on adv permission group

* update migrations

* abstract lots of core form logic to a custom hook

* improve UX for selecting all and toggling all

* improve code quality and use domain mappers in role repositoryu

* call server action to revalidate cache

* call invalidate cache on delete

* fix re-render + improves update logic wip

* fix txn for assinging role to member

* wip on assigning users custom roles

* fix repo

* update logic for checking if users can update roles

* remove member from permission check

* check users permission and assign roles

* move to factory approach

* move default rolesIDs to constant

* add facuted values to table

* display custom role in table

* fix type error

* fix role filter

* check pbac feature flag to see what column to filter on

* push repo mocks and other mocks to fix unit tests

* fix and add test for empty permissions when creating a role

* pass updates to repo so we actually update roles

* fix types

* fix types

* restore lock changes

* fix role service test for new updates section

* fix updated at types

* update mocks to use feature repository mock

* remove roletype from db in model

* prevent multiple queries

* fix typeof in role model

* fix and migrate i18n to one registery

* fix and update i18n to be in registery

* fix type error + fall back in service instead of repo for BL

* more type errors

* update members faceted values to bennys refactor

* fix types

* remove the _resource from type conditionally

* fix managment factory types to expose PBAC enbaled obol

* narrow down types

* wip fix for types

* more fix types

* cast role

* fix tests

* attempt of fixing _resoucre key access type

* attempt of fixing _resoucre key access type

* seperate migraations to batches

* add invalidate time to team features

* restore router to main

* push main lock

* Update packages/features/pbac/domain/types/permission-registry.ts

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Update packages/features/pbac/domain/mappers/PermissionMapper.ts

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Update packages/prisma/migrations/20250527091330_add_color_to_pbac_role/migration.sql

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Update packages/prisma/migrations/20250617070118_update_memberships_one_time/migration.sql

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Update apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/roles/_components/AdvancedPermissionGroup.tsx

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* fix mapped type

* restore migration

* skip _resource

* use partical

* fix type errors in tests and hooks

* Simplified the role field in the editSchema to use z.nativeEnum(MembershipRole)

* fix type errors for editsheet

* fix type errors for editsheet

* Apply suggestion from comment 2151515295

* remove footer since we dont have docs yet

* add i18n

* lock all toggle chevron

* use prisma

* tidy up old manage permission

* fix i18n

* remove can manage from role permission check

* auto select read

* address benny feedback

* fix type

* fix type

* update function name due to merge

* fix types

* update tests to match new membership method from merge

* address cubic feedback

---------

Co-authored-by: Eunjae Lee <[email protected]>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2025-07-07 10:20:24 +01:00

145 lines
5.6 KiB
TypeScript

import { describe, it, expect, beforeEach } from "vitest";
import type { PermissionString } from "../../domain/types/permission-registry";
import { CrudAction, PERMISSION_REGISTRY, Resource } from "../../domain/types/permission-registry";
import { PermissionService } from "../permission.service";
describe("PermissionService", () => {
let service: PermissionService;
beforeEach(() => {
service = new PermissionService();
});
describe("validatePermission", () => {
it("should validate a valid permission", () => {
const result = service.validatePermission("eventType.create" as PermissionString);
expect(result.isValid).toBe(true);
expect(result.error).toBeNull();
});
it("should reject an invalid permission", () => {
const result = service.validatePermission("invalid.action" as PermissionString);
expect(result.isValid).toBe(false);
expect(result.error).toBe("Invalid permission: invalid.action");
});
it("should reject a malformed permission", () => {
const result = service.validatePermission("invalid-format" as PermissionString);
expect(result.isValid).toBe(false);
expect(result.error).toBe("Invalid permission format: invalid-format");
});
});
describe("validatePermissions", () => {
it("should validate multiple valid permissions", () => {
const permissions = [
"eventType.create",
"team.invite",
"organization.manageBilling",
] as PermissionString[];
const result = service.validatePermissions(permissions);
expect(result.isValid).toBe(true);
expect(result.error).toBeNull();
});
it("should reject if any permission is invalid", () => {
const permissions = [
"eventType.create",
"invalid.action",
"organization.manageBilling",
] as PermissionString[];
const result = service.validatePermissions(permissions);
expect(result.isValid).toBe(false);
expect(result.error).toBe("Invalid permission: invalid.action");
});
it("should reject if any permission is malformed", () => {
const permissions = ["eventType.create", "invalid-format", "team.invite"] as PermissionString[];
const result = service.validatePermissions(permissions);
expect(result.isValid).toBe(false);
expect(result.error).toBe("Invalid permission format: invalid-format");
});
});
describe("getAllPermissions", () => {
it("should return all permissions from registry as domain objects", () => {
const permissions = service.getAllPermissions();
const expectedCount = Object.values(PERMISSION_REGISTRY).reduce(
(count, resource) => count + Object.keys(resource).filter((key) => key !== "_resource").length,
0
);
expect(permissions).toHaveLength(expectedCount);
expect(permissions[0]).toHaveProperty("resource");
expect(permissions[0]).toHaveProperty("action");
expect(permissions[0]).toHaveProperty("description");
expect(permissions[0]).toHaveProperty("category");
});
});
describe("getPermissionsByCategory", () => {
it("should return permissions for a specific category as domain objects", () => {
const eventPermissions = service.getPermissionsByCategory("event");
expect(eventPermissions.every((p) => p.category === "event")).toBe(true);
expect(eventPermissions[0]).toMatchObject({
resource: expect.any(String),
action: expect.any(String),
category: "event",
});
});
it("should return empty array for non-existent category", () => {
const permissions = service.getPermissionsByCategory("nonexistent");
expect(permissions).toHaveLength(0);
});
});
describe("getPermissionCategories", () => {
it("should return all unique categories", () => {
const categories = service.getPermissionCategories();
const uniqueCategories = new Set(
Object.values(PERMISSION_REGISTRY).flatMap((resource) =>
Object.entries(resource)
.filter(([key]) => key !== "_resource")
.map(([_, p]) => p?.category)
.filter(Boolean)
)
);
expect(categories).toHaveLength(uniqueCategories.size);
expect(categories.every((category) => typeof category === "string")).toBe(true);
});
});
describe("getPermissionsByResource", () => {
it("should return permissions for a specific resource as domain objects", () => {
const eventTypePermissions = service.getPermissionsByResource(Resource.EventType);
expect(eventTypePermissions.every((p) => p.resource === Resource.EventType)).toBe(true);
expect(eventTypePermissions[0]).toMatchObject({
resource: Resource.EventType,
action: expect.any(String),
});
});
it("should return empty array for non-existent resource", () => {
const permissions = service.getPermissionsByResource("nonexistent" as Resource);
expect(permissions).toHaveLength(0);
});
});
describe("getPermissionsByAction", () => {
it("should return permissions for a specific action as domain objects", () => {
const createPermissions = service.getPermissionsByAction(CrudAction.Create);
expect(createPermissions.every((p) => p.action === CrudAction.Create)).toBe(true);
expect(createPermissions[0]).toMatchObject({
action: CrudAction.Create,
resource: expect.any(String),
});
});
it("should return empty array for non-existent action", () => {
const permissions = service.getPermissionsByAction("nonexistent" as CrudAction);
expect(permissions).toHaveLength(0);
});
});
});