@flusys/nestjs-form-builder 6.0.2 → 6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,114 @@
1
+ import { ForbiddenException } from '@nestjs/common';
2
+ import { buildMockUser } from '@test-utils/mocks/logged-user.mock';
3
+ import { validateUserPermissions } from './permission.utils';
4
+ function buildMockCache() {
5
+ return {
6
+ get: jest.fn(),
7
+ set: jest.fn(),
8
+ del: jest.fn()
9
+ };
10
+ }
11
+ describe('validateUserPermissions', ()=>{
12
+ it('returns true immediately without touching the cache when no permissions are required', async ()=>{
13
+ const cache = buildMockCache();
14
+ const result = await validateUserPermissions(buildMockUser(), [], cache, false);
15
+ expect(result).toBe(true);
16
+ expect(cache.get).not.toHaveBeenCalled();
17
+ });
18
+ it('returns true when the user has at least one of the required permissions', async ()=>{
19
+ const cache = buildMockCache();
20
+ cache.get.mockResolvedValue([
21
+ 'hr.survey.submit',
22
+ 'other.permission'
23
+ ]);
24
+ const result = await validateUserPermissions(buildMockUser(), [
25
+ 'hr.survey.submit',
26
+ 'employee.feedback.submit'
27
+ ], cache, false);
28
+ expect(result).toBe(true);
29
+ });
30
+ it('returns false when the user has none of the required permissions', async ()=>{
31
+ const cache = buildMockCache();
32
+ cache.get.mockResolvedValue([
33
+ 'some.other.permission'
34
+ ]);
35
+ const result = await validateUserPermissions(buildMockUser(), [
36
+ 'hr.survey.submit'
37
+ ], cache, false);
38
+ expect(result).toBe(false);
39
+ });
40
+ it('treats a missing cache entry as no permissions (returns false)', async ()=>{
41
+ const cache = buildMockCache();
42
+ cache.get.mockResolvedValue(undefined);
43
+ const result = await validateUserPermissions(buildMockUser(), [
44
+ 'hr.survey.submit'
45
+ ], cache, false);
46
+ expect(result).toBe(false);
47
+ });
48
+ it('fails closed with a ForbiddenException when the cache lookup throws', async ()=>{
49
+ const cache = buildMockCache();
50
+ cache.get.mockRejectedValue(new Error('redis down'));
51
+ await expect(validateUserPermissions(buildMockUser(), [
52
+ 'hr.survey.submit'
53
+ ], cache, false)).rejects.toMatchObject({
54
+ response: expect.objectContaining({
55
+ messageKey: 'form.permission.check.failed'
56
+ })
57
+ });
58
+ await expect(validateUserPermissions(buildMockUser(), [
59
+ 'hr.survey.submit'
60
+ ], cache, false)).rejects.toBeInstanceOf(ForbiddenException);
61
+ });
62
+ describe('cache key construction', ()=>{
63
+ it('uses a company/branch/user scoped key when the company feature is enabled and both are present', async ()=>{
64
+ const cache = buildMockCache();
65
+ cache.get.mockResolvedValue([]);
66
+ const user = buildMockUser({
67
+ companyId: 'company-1',
68
+ branchId: 'branch-1',
69
+ id: 'user-1'
70
+ });
71
+ await validateUserPermissions(user, [
72
+ 'perm.read'
73
+ ], cache, true);
74
+ expect(cache.get).toHaveBeenCalledWith('permissions:company:company-1:branch:branch-1:user:user-1');
75
+ });
76
+ it('uses "null" as the branch placeholder when branchId is missing under the company feature', async ()=>{
77
+ const cache = buildMockCache();
78
+ cache.get.mockResolvedValue([]);
79
+ const user = buildMockUser({
80
+ companyId: 'company-1',
81
+ branchId: undefined,
82
+ id: 'user-1'
83
+ });
84
+ await validateUserPermissions(user, [
85
+ 'perm.read'
86
+ ], cache, true);
87
+ expect(cache.get).toHaveBeenCalledWith('permissions:company:company-1:branch:null:user:user-1');
88
+ });
89
+ it('falls back to a plain user-scoped key when the company feature is enabled but companyId is missing', async ()=>{
90
+ const cache = buildMockCache();
91
+ cache.get.mockResolvedValue([]);
92
+ const user = buildMockUser({
93
+ companyId: undefined,
94
+ id: 'user-1'
95
+ });
96
+ await validateUserPermissions(user, [
97
+ 'perm.read'
98
+ ], cache, true);
99
+ expect(cache.get).toHaveBeenCalledWith('permissions:user:user-1');
100
+ });
101
+ it('uses a plain user-scoped key when the company feature is disabled', async ()=>{
102
+ const cache = buildMockCache();
103
+ cache.get.mockResolvedValue([]);
104
+ const user = buildMockUser({
105
+ companyId: 'company-1',
106
+ id: 'user-1'
107
+ });
108
+ await validateUserPermissions(user, [
109
+ 'perm.read'
110
+ ], cache, false);
111
+ expect(cache.get).toHaveBeenCalledWith('permissions:user:user-1');
112
+ });
113
+ });
114
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flusys/nestjs-form-builder",
3
- "version": "6.0.2",
3
+ "version": "6.1.1",
4
4
  "description": "Dynamic form builder module with schema versioning and access control",
5
5
  "main": "cjs/index.js",
6
6
  "module": "fesm/index.js",
@@ -84,7 +84,7 @@
84
84
  "express": "^5.0.0"
85
85
  },
86
86
  "dependencies": {
87
- "@flusys/nestjs-core": "6.0.2",
88
- "@flusys/nestjs-shared": "6.0.2"
87
+ "@flusys/nestjs-core": "6.1.1",
88
+ "@flusys/nestjs-shared": "6.1.1"
89
89
  }
90
90
  }