From bf02a9b5d46eea0499d46800fd65031abb9cd9eb Mon Sep 17 00:00:00 2001 From: Weiko Date: Wed, 27 May 2026 19:42:13 +0200 Subject: [PATCH] add test coverage for the new helper --- .../__tests__/get-permission-context.spec.ts | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 packages/twenty-sdk/src/sdk/logic-function/permission-context/__tests__/get-permission-context.spec.ts diff --git a/packages/twenty-sdk/src/sdk/logic-function/permission-context/__tests__/get-permission-context.spec.ts b/packages/twenty-sdk/src/sdk/logic-function/permission-context/__tests__/get-permission-context.spec.ts new file mode 100644 index 00000000000..37d46fe1d59 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/logic-function/permission-context/__tests__/get-permission-context.spec.ts @@ -0,0 +1,188 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +import { + FieldPermissionLevel, + type LogicFunctionPermissionContext, + ObjectPermissionLevel, +} from 'twenty-shared/application'; + +import { getPermissionContext } from '@/sdk/logic-function/permission-context/get-permission-context'; + +const OBJECT_UID = 'object-uid'; +const READONLY_OBJECT_UID = 'readonly-object-uid'; +const FIELD_UID = 'field-uid'; +const FLAG_UID = 'flag-uid'; + +const CONTEXT: LogicFunctionPermissionContext = { + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canAccessAllTools: true, + permissionFlags: { + [FLAG_UID]: true, + 'denied-flag-uid': false, + }, + objectsPermissions: { + [OBJECT_UID]: { + canRead: true, + canUpdate: true, + canSoftDelete: false, + canDestroy: false, + restrictedFields: { + [FIELD_UID]: { canRead: false, canUpdate: null }, + }, + }, + [READONLY_OBJECT_UID]: { + canRead: true, + canUpdate: false, + canSoftDelete: false, + canDestroy: false, + restrictedFields: {}, + }, + }, +}; + +describe('getPermissionContext', () => { + afterEach(() => { + delete process.env.TWENTY_PERMISSION_CONTEXT; + }); + + describe('with an injected context', () => { + beforeEach(() => { + process.env.TWENTY_PERMISSION_CONTEXT = JSON.stringify(CONTEXT); + }); + + it('exposes the root capability booleans', () => { + const permissions = getPermissionContext(); + + expect(permissions.canReadAllObjectRecords).toBe(true); + expect(permissions.canUpdateAllObjectRecords).toBe(false); + expect(permissions.canAccessAllTools).toBe(true); + expect(permissions.canUpdateAllSettings).toBe(false); + }); + + it('resolves object permissions per level', () => { + const permissions = getPermissionContext(); + + expect( + permissions.hasObjectPermission(OBJECT_UID, ObjectPermissionLevel.READ), + ).toBe(true); + expect( + permissions.hasObjectPermission( + OBJECT_UID, + ObjectPermissionLevel.UPDATE, + ), + ).toBe(true); + expect( + permissions.hasObjectPermission( + OBJECT_UID, + ObjectPermissionLevel.SOFT_DELETE, + ), + ).toBe(false); + expect( + permissions.hasObjectPermission( + OBJECT_UID, + ObjectPermissionLevel.DESTROY, + ), + ).toBe(false); + }); + + it('returns false for an unknown object', () => { + const permissions = getPermissionContext(); + + expect( + permissions.hasObjectPermission('unknown', ObjectPermissionLevel.READ), + ).toBe(false); + }); + + it('blocks a field whose read is explicitly restricted', () => { + const permissions = getPermissionContext(); + + expect( + permissions.hasFieldPermission( + OBJECT_UID, + FIELD_UID, + FieldPermissionLevel.READ, + ), + ).toBe(false); + }); + + it('allows a restricted field when the level is not explicitly false', () => { + const permissions = getPermissionContext(); + + // restrictedFields[FIELD_UID].canUpdate is null, not false + expect( + permissions.hasFieldPermission( + OBJECT_UID, + FIELD_UID, + FieldPermissionLevel.UPDATE, + ), + ).toBe(true); + }); + + it('allows an unrestricted field when the object permits it', () => { + const permissions = getPermissionContext(); + + expect( + permissions.hasFieldPermission( + OBJECT_UID, + 'unrestricted-field', + FieldPermissionLevel.READ, + ), + ).toBe(true); + }); + + it('gates field permission on the matching object permission', () => { + const permissions = getPermissionContext(); + + expect( + permissions.hasFieldPermission( + READONLY_OBJECT_UID, + 'any-field', + FieldPermissionLevel.READ, + ), + ).toBe(true); + expect( + permissions.hasFieldPermission( + READONLY_OBJECT_UID, + 'any-field', + FieldPermissionLevel.UPDATE, + ), + ).toBe(false); + }); + + it('resolves permission flags by universal identifier', () => { + const permissions = getPermissionContext(); + + expect(permissions.hasPermissionFlag(FLAG_UID)).toBe(true); + expect(permissions.hasPermissionFlag('denied-flag-uid')).toBe(false); + expect(permissions.hasPermissionFlag('missing-flag-uid')).toBe(false); + }); + }); + + describe('without an injected context', () => { + it('falls back to an empty default when the env var is absent', () => { + const permissions = getPermissionContext(); + + expect(permissions.canReadAllObjectRecords).toBe(false); + expect(permissions.canAccessAllTools).toBe(false); + expect(permissions.objectsPermissions).toEqual({}); + expect(permissions.permissionFlags).toEqual({}); + expect( + permissions.hasObjectPermission(OBJECT_UID, ObjectPermissionLevel.READ), + ).toBe(false); + expect(permissions.hasPermissionFlag(FLAG_UID)).toBe(false); + }); + + it('falls back to an empty default when the env var is malformed', () => { + process.env.TWENTY_PERMISSION_CONTEXT = '{ not json'; + + const permissions = getPermissionContext(); + + expect(permissions.canReadAllObjectRecords).toBe(false); + expect(permissions.objectsPermissions).toEqual({}); + }); + }); +});