@stonecrop/casl-middleware 0.11.0 → 0.11.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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/dist/src/index.js +0 -3
  3. package/dist/src/middleware/ability.js +0 -139
  4. package/dist/src/middleware/graphql.js +0 -120
  5. package/dist/src/middleware/introspection.js +0 -169
  6. package/dist/src/middleware/jwt.js +0 -291
  7. package/dist/src/middleware/postgraphile.js +0 -80
  8. package/dist/src/middleware/yoga.js +0 -32
  9. package/dist/src/tsdoc-metadata.json +0 -11
  10. package/dist/src/types/index.js +0 -0
  11. package/dist/tests/ability.test.d.ts +0 -2
  12. package/dist/tests/ability.test.d.ts.map +0 -1
  13. package/dist/tests/ability.test.js +0 -402
  14. package/dist/tests/helpers/test-utils.d.ts +0 -46
  15. package/dist/tests/helpers/test-utils.d.ts.map +0 -1
  16. package/dist/tests/helpers/test-utils.js +0 -92
  17. package/dist/tests/introspection.test.d.ts +0 -2
  18. package/dist/tests/introspection.test.d.ts.map +0 -1
  19. package/dist/tests/introspection.test.js +0 -368
  20. package/dist/tests/jwt.test.d.ts +0 -2
  21. package/dist/tests/jwt.test.d.ts.map +0 -1
  22. package/dist/tests/jwt.test.js +0 -700
  23. package/dist/tests/middleware.test.d.ts +0 -2
  24. package/dist/tests/middleware.test.d.ts.map +0 -1
  25. package/dist/tests/middleware.test.js +0 -184
  26. package/dist/tests/postgraphile-plugin.test.d.ts +0 -2
  27. package/dist/tests/postgraphile-plugin.test.d.ts.map +0 -1
  28. package/dist/tests/postgraphile-plugin.test.js +0 -56
  29. package/dist/tests/setup.d.ts +0 -2
  30. package/dist/tests/setup.d.ts.map +0 -1
  31. package/dist/tests/setup.js +0 -11
  32. package/dist/tests/types.test.d.ts +0 -2
  33. package/dist/tests/types.test.d.ts.map +0 -1
  34. package/dist/tests/types.test.js +0 -143
  35. package/dist/tests/user-roles.test.d.ts +0 -2
  36. package/dist/tests/user-roles.test.d.ts.map +0 -1
  37. package/dist/tests/user-roles.test.js +0 -157
  38. package/dist/tests/yoga-plugin.test.d.ts +0 -2
  39. package/dist/tests/yoga-plugin.test.d.ts.map +0 -1
  40. package/dist/tests/yoga-plugin.test.js +0 -125
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=middleware.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"middleware.test.d.ts","sourceRoot":"","sources":["../../tests/middleware.test.ts"],"names":[],"mappings":""}
@@ -1,184 +0,0 @@
1
- import { PureAbility, AbilityBuilder } from '@casl/ability';
2
- import { describe, it, expect, vi, beforeEach } from 'vitest';
3
- import { createCaslMiddleware } from '../src/middleware/graphql';
4
- const Ability = PureAbility;
5
- // Helper to create abilities with proper matchers
6
- const createTestAbility = (rules) => {
7
- const { can, cannot, build } = new AbilityBuilder(Ability);
8
- rules.forEach(rule => {
9
- if (rule.inverted) {
10
- if (rule.fields && rule.conditions) {
11
- cannot(rule.action, rule.subject, rule.fields, rule.conditions);
12
- }
13
- else if (rule.fields) {
14
- cannot(rule.action, rule.subject, rule.fields);
15
- }
16
- else if (rule.conditions) {
17
- cannot(rule.action, rule.subject, rule.conditions);
18
- }
19
- else {
20
- cannot(rule.action, rule.subject);
21
- }
22
- }
23
- else {
24
- if (rule.fields && rule.conditions) {
25
- can(rule.action, rule.subject, rule.fields, rule.conditions);
26
- }
27
- else if (rule.fields) {
28
- can(rule.action, rule.subject, rule.fields);
29
- }
30
- else if (rule.conditions) {
31
- can(rule.action, rule.subject, rule.conditions);
32
- }
33
- else {
34
- can(rule.action, rule.subject);
35
- }
36
- }
37
- });
38
- return build({
39
- detectSubjectType: (object) => object?.type || object?.constructor?.name || 'Unknown',
40
- fieldMatcher: fields => field => {
41
- if (!fields || !field)
42
- return false;
43
- return Array.isArray(fields) ? fields.includes(field) : fields === field;
44
- },
45
- conditionsMatcher: conditions => object => {
46
- if (!conditions || !object)
47
- return true;
48
- return Object.keys(conditions).every(key => object[key] === conditions[key]);
49
- },
50
- });
51
- };
52
- describe('CASL GraphQL Middleware', () => {
53
- let mockResolve;
54
- let mockContext;
55
- let mockInfo;
56
- beforeEach(() => {
57
- mockResolve = vi.fn().mockResolvedValue({ data: 'test' });
58
- mockContext = {
59
- ability: createTestAbility([
60
- { action: 'read', subject: 'Query' },
61
- { action: 'read', subject: 'User' },
62
- { action: 'update', subject: 'User', conditions: { id: '123' } },
63
- ]),
64
- user: { id: '123', roles: ['user'] },
65
- };
66
- mockInfo = {
67
- fieldName: 'getUser',
68
- parentType: { name: 'Query' },
69
- path: { typename: 'Query', key: 'getUser' },
70
- operation: {
71
- operation: 'query',
72
- loc: { source: { body: 'query { getUser { id } }' } },
73
- },
74
- };
75
- });
76
- describe('Basic Middleware Functionality', () => {
77
- it('should pass through when ability allows the operation', async () => {
78
- const middleware = createCaslMiddleware();
79
- const result = await middleware(mockResolve, {}, {}, mockContext, mockInfo);
80
- expect(mockResolve).toHaveBeenCalled();
81
- expect(result).toEqual({ data: 'test' });
82
- });
83
- it('should create ability when not in context', async () => {
84
- const middleware = createCaslMiddleware();
85
- const contextWithoutAbility = { user: { id: '123', roles: ['user'] } };
86
- // The middleware creates an ability if none exists
87
- const result = await middleware(mockResolve, {}, {}, contextWithoutAbility, mockInfo);
88
- expect(mockResolve).toHaveBeenCalled();
89
- expect(result).toEqual({ data: 'test' });
90
- expect(contextWithoutAbility.ability).toBeDefined();
91
- });
92
- it('should deny access when ability does not allow operation', async () => {
93
- const middleware = createCaslMiddleware();
94
- mockContext.ability = createTestAbility([{ action: 'read', subject: 'Post' }]);
95
- await expect(middleware(mockResolve, {}, {}, mockContext, mockInfo)).rejects.toThrow('Access denied for read on Query');
96
- });
97
- });
98
- describe('Subject Mapping', () => {
99
- it('should map GraphQL types to custom subjects', async () => {
100
- const middleware = createCaslMiddleware({
101
- subjectMap: {
102
- Query: 'query_operations',
103
- User: 'app_user',
104
- },
105
- });
106
- mockContext.ability = createTestAbility([{ action: 'read', subject: 'query_operations' }]);
107
- const result = await middleware(mockResolve, {}, {}, mockContext, mockInfo);
108
- expect(mockResolve).toHaveBeenCalled();
109
- expect(result).toEqual({ data: 'test' });
110
- });
111
- it('should use original type name when no mapping exists', async () => {
112
- const middleware = createCaslMiddleware({
113
- subjectMap: { User: 'app_user' },
114
- });
115
- // Should work with original 'Query' subject since it's not mapped
116
- const result = await middleware(mockResolve, {}, {}, mockContext, mockInfo);
117
- expect(mockResolve).toHaveBeenCalled();
118
- });
119
- });
120
- describe('Action Mapping', () => {
121
- it('should map GraphQL operations to custom actions', async () => {
122
- const middleware = createCaslMiddleware({
123
- actionMap: {
124
- query: 'view',
125
- mutation: 'modify',
126
- subscription: 'watch',
127
- },
128
- });
129
- mockContext.ability = createTestAbility([{ action: 'view', subject: 'Query' }]);
130
- const result = await middleware(mockResolve, {}, {}, mockContext, mockInfo);
131
- expect(mockResolve).toHaveBeenCalled();
132
- });
133
- });
134
- describe('Field-Level Permissions', () => {
135
- it('should check field-level permissions when configured', async () => {
136
- const middleware = createCaslMiddleware({
137
- fieldPermissions: {
138
- 'User.email': [{ action: 'read', subject: 'UserEmail' }],
139
- 'User.password': [{ action: 'read', subject: 'UserPassword' }],
140
- },
141
- });
142
- mockInfo.parentType = { name: 'User' };
143
- mockInfo.fieldName = 'email';
144
- mockInfo.path = { typename: 'User', key: 'email' };
145
- mockContext.ability = createTestAbility([
146
- { action: 'read', subject: 'User' },
147
- { action: 'read', subject: 'UserEmail' },
148
- ]);
149
- const result = await middleware(mockResolve, {}, {}, mockContext, mockInfo);
150
- expect(mockResolve).toHaveBeenCalled();
151
- });
152
- it('should deny access to fields without permission', async () => {
153
- const middleware = createCaslMiddleware({
154
- fieldPermissions: {
155
- 'User.password': [{ action: 'read', subject: 'UserPassword' }],
156
- },
157
- });
158
- mockInfo.parentType = { name: 'User' };
159
- mockInfo.fieldName = 'password';
160
- mockInfo.path = { typename: 'User', key: 'password' };
161
- mockContext.ability = createTestAbility([{ action: 'read', subject: 'User' }]);
162
- await expect(middleware(mockResolve, {}, {}, mockContext, mockInfo)).rejects.toThrow('Access denied for field User.password');
163
- });
164
- });
165
- describe('Mutation Field Checking', () => {
166
- it('should check field permissions for mutations with input', async () => {
167
- const middleware = createCaslMiddleware();
168
- mockInfo.parentType = { name: 'Mutation' };
169
- mockInfo.operation.operation = 'mutation';
170
- mockInfo.operation.loc = {
171
- source: {
172
- body: 'mutation { updateUser(input: { name: "test", role: "admin" }) { id } }',
173
- },
174
- };
175
- mockContext.ability = createTestAbility([
176
- { action: 'update', subject: 'Mutation' },
177
- { action: 'update', subject: 'User', fields: ['name'] },
178
- ]);
179
- const args = { input: { name: 'test', role: 'admin' } };
180
- const result = await middleware(mockResolve, {}, args, mockContext, mockInfo);
181
- expect(mockResolve).toHaveBeenCalled();
182
- });
183
- });
184
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=postgraphile-plugin.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"postgraphile-plugin.test.d.ts","sourceRoot":"","sources":["../../tests/postgraphile-plugin.test.ts"],"names":[],"mappings":""}
@@ -1,56 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { pglCaslPlugin } from '../src';
3
- import { createConfigBasedAbilityBuilder } from '../src/middleware/ability';
4
- describe('PostGraphile CASL Plugins', () => {
5
- describe('pglCaslPlugin (extendSchema)', () => {
6
- it('should be a valid extendSchema plugin object', () => {
7
- expect(pglCaslPlugin).toBeDefined();
8
- expect(pglCaslPlugin).toBeInstanceOf(Object); // It's an object, not a function
9
- expect(pglCaslPlugin).toHaveProperty('name');
10
- expect(pglCaslPlugin).toHaveProperty('version');
11
- });
12
- it('should have schema extension properties', () => {
13
- // The plugin object has specific properties
14
- expect(pglCaslPlugin).toHaveProperty('name');
15
- expect(pglCaslPlugin.name).toMatch(/^ExtendSchemaPlugin_/);
16
- // Check for schema extension function
17
- expect(pglCaslPlugin).toHaveProperty('schema');
18
- expect(typeof pglCaslPlugin.schema).toBe('object');
19
- // Check for hooks if they exist
20
- if (pglCaslPlugin.schema?.hooks) {
21
- expect(pglCaslPlugin.schema.hooks).toHaveProperty('build');
22
- }
23
- });
24
- it('should define schema extensions when executed', () => {
25
- // Since extendSchema creates a plugin object, we need to test differently
26
- // The actual schema extension happens when PostGraphile processes the plugin
27
- // We can verify the plugin has the expected structure
28
- expect(pglCaslPlugin).toHaveProperty('name');
29
- expect(pglCaslPlugin).toHaveProperty('version');
30
- // The schema extension function is wrapped inside the plugin
31
- // and will be executed by PostGraphile
32
- });
33
- });
34
- describe('Ability Integration', () => {
35
- it('should create abilities with custom builder', async () => {
36
- const customBuilder = createConfigBasedAbilityBuilder({
37
- defaultRules: [{ action: 'read', subject: 'Query' }],
38
- roles: {
39
- editor: [
40
- { action: 'create', subject: 'Post' },
41
- { action: 'update', subject: 'Post' },
42
- ],
43
- },
44
- });
45
- // Test that the ability builder works
46
- const ability = await customBuilder({ id: '123', roles: ['editor'] });
47
- expect(ability.can('create', 'Post')).toBe(true);
48
- expect(ability.can('update', 'Post')).toBe(true);
49
- });
50
- it('should work with PostGraphile plugin system', () => {
51
- // Test that our plugin has the correct shape for PostGraphile
52
- expect(pglCaslPlugin).toHaveProperty('name');
53
- expect(pglCaslPlugin).toHaveProperty('version');
54
- });
55
- });
56
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=setup.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../tests/setup.ts"],"names":[],"mappings":""}
@@ -1,11 +0,0 @@
1
- import { beforeAll, afterEach, afterAll } from 'vitest';
2
- // Setup test environment
3
- beforeAll(() => {
4
- // Add any global setup here
5
- });
6
- afterEach(() => {
7
- // Clean up after each test
8
- });
9
- afterAll(() => {
10
- // Clean up after all tests
11
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.test.d.ts","sourceRoot":"","sources":["../../tests/types.test.ts"],"names":[],"mappings":""}
@@ -1,143 +0,0 @@
1
- import { describe, it, expectTypeOf } from 'vitest';
2
- import { createAbility, detectSubjectType, defaultAbilityBuilder, createDatabaseAbilityBuilder, createConfigBasedAbilityBuilder, } from '../src/middleware/ability';
3
- import { createJWTMiddleware, createJWT } from '../src/middleware/jwt';
4
- import { createCaslMiddleware } from '../src/middleware/graphql';
5
- describe('Type Safety Tests', () => {
6
- describe('Ability Types', () => {
7
- it('should have correct AppAbility type structure', () => {
8
- expectTypeOf().toExtend(); // AppAbility extends PureAbility base type
9
- });
10
- it('should infer correct return type for createAbility', async () => {
11
- const ability = await createAbility({ id: '1', roles: ['user'] });
12
- expectTypeOf(ability).toEqualTypeOf();
13
- });
14
- it('should accept AbilityBuilderFunction as parameter', () => {
15
- const builder = user => defaultAbilityBuilder(user);
16
- expectTypeOf(builder).parameter(0).toEqualTypeOf();
17
- expectTypeOf(builder).returns.resolves.toEqualTypeOf();
18
- });
19
- it('should have correct detectSubjectType signature', () => {
20
- expectTypeOf(detectSubjectType).parameter(0).toEqualTypeOf();
21
- expectTypeOf(detectSubjectType).returns.toEqualTypeOf();
22
- });
23
- it('should type database ability builder correctly', () => {
24
- const fetchRules = async (userId) => [];
25
- const builder = createDatabaseAbilityBuilder(fetchRules);
26
- expectTypeOf(builder).parameter(0).toEqualTypeOf();
27
- expectTypeOf(builder).returns.resolves.toEqualTypeOf();
28
- });
29
- it('should type config-based ability builder correctly', () => {
30
- const config = {
31
- roles: {
32
- admin: [{ action: 'manage', subject: 'all' }],
33
- },
34
- };
35
- const builder = createConfigBasedAbilityBuilder(config);
36
- expectTypeOf(builder).parameter(0).toEqualTypeOf();
37
- // Builder returns AppAbility synchronously
38
- expectTypeOf(builder).toBeFunction();
39
- });
40
- });
41
- describe('JWT Types', () => {
42
- it('should have correct JWTConfig type structure', () => {
43
- const config = {
44
- secret: 'test',
45
- enabled: true,
46
- optional: false,
47
- };
48
- expectTypeOf(config).toExtend(); // Check config has at least these properties
49
- });
50
- it('should have correct JWTPayload structure', () => {
51
- expectTypeOf().toHaveProperty('sub');
52
- expectTypeOf().toHaveProperty('iat');
53
- });
54
- it('should infer correct return type for createJWT', () => {
55
- const user = { id: '1', roles: ['user'] };
56
- const token = createJWT(user, { secret: 'test' });
57
- expectTypeOf(token).toBeString();
58
- });
59
- it('should type middleware correctly', () => {
60
- const middleware = createJWTMiddleware({ secret: 'test' });
61
- expectTypeOf(middleware).parameter(0).toEqualTypeOf();
62
- expectTypeOf(middleware).parameter(1).toBeFunction();
63
- expectTypeOf(middleware).returns.toEqualTypeOf();
64
- });
65
- });
66
- describe('GraphQL Middleware Types', () => {
67
- it('should have correct MiddlewareOptions type structure', () => {
68
- const options = {
69
- subjectMap: {},
70
- actionMap: {},
71
- fieldPermissions: {},
72
- debug: false,
73
- };
74
- expectTypeOf(options).toExtend(); // Check options has at least these properties
75
- });
76
- it('should type middleware function correctly', () => {
77
- const middleware = createCaslMiddleware();
78
- expectTypeOf(middleware).toBeFunction();
79
- expectTypeOf(middleware).parameter(0).toBeFunction(); // resolve
80
- expectTypeOf(middleware).parameter(1).toBeAny(); // root
81
- expectTypeOf(middleware).parameter(2).toBeAny(); // args
82
- expectTypeOf(middleware).parameter(3).toEqualTypeOf(); // context
83
- expectTypeOf(middleware).returns.toEqualTypeOf();
84
- });
85
- });
86
- describe('Context Types', () => {
87
- it('should have correct Context type structure', () => {
88
- expectTypeOf().toHaveProperty('ability');
89
- expectTypeOf().toHaveProperty('user');
90
- });
91
- it('should have correct User type structure', () => {
92
- expectTypeOf().toHaveProperty('id');
93
- expectTypeOf().toHaveProperty('roles');
94
- const user = { id: '1', roles: ['admin'] };
95
- expectTypeOf(user.id).toBeString();
96
- // roles is optional, so check the array type when present
97
- if (user.roles) {
98
- expectTypeOf(user.roles).toEqualTypeOf();
99
- }
100
- });
101
- it('should allow optional properties on User', () => {
102
- const userWithOptionals = {
103
- id: '1',
104
- roles: ['user'],
105
- email: 'test@example.com',
106
- };
107
- expectTypeOf(userWithOptionals).toExtend(); // Check that object is assignable to User type
108
- });
109
- });
110
- describe('Ability Rule Types', () => {
111
- it('should correctly type ability actions', () => {
112
- const ability = defaultAbilityBuilder({ id: '1', roles: ['user'] });
113
- // Type checking that these calls are valid
114
- expectTypeOf(ability.can).parameter(0).toBeString();
115
- expectTypeOf(ability.can).parameter(1).toEqualTypeOf();
116
- });
117
- it('should support subject type checking', () => {
118
- const ability = defaultAbilityBuilder({ id: '1', roles: ['user'] });
119
- // Verify can() method signature accepts string action and optional subject
120
- expectTypeOf(ability.can).parameter(0).toBeString();
121
- expectTypeOf(ability.can).toBeCallableWith('read', 'Post');
122
- expectTypeOf(ability.can).toBeCallableWith('read', undefined);
123
- });
124
- });
125
- describe('Builder Function Types', () => {
126
- it('should accept sync and async builder functions', () => {
127
- const syncBuilder = () => defaultAbilityBuilder();
128
- const asyncBuilder = async () => defaultAbilityBuilder();
129
- // Both should be valid builder functions
130
- expectTypeOf(syncBuilder).toBeFunction();
131
- expectTypeOf(asyncBuilder).toBeFunction();
132
- expectTypeOf(asyncBuilder).returns.resolves.toEqualTypeOf();
133
- });
134
- it('should enforce correct parameter types for builders', () => {
135
- const builder = user => {
136
- expectTypeOf(user).toEqualTypeOf();
137
- return defaultAbilityBuilder(user);
138
- };
139
- expectTypeOf(builder).toBeCallableWith(undefined);
140
- expectTypeOf(builder).toBeCallableWith({ id: '1', roles: [] });
141
- });
142
- });
143
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=user-roles.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"user-roles.test.d.ts","sourceRoot":"","sources":["../../tests/user-roles.test.ts"],"names":[],"mappings":""}
@@ -1,157 +0,0 @@
1
- import { PureAbility, AbilityBuilder } from '@casl/ability';
2
- import { describe, it, expect, vi, beforeEach } from 'vitest';
3
- import { createCaslMiddleware } from '../src/middleware/graphql';
4
- const Ability = PureAbility;
5
- // Helper to create abilities with proper matchers
6
- const createTestAbility = (rules) => {
7
- const { can, cannot, build } = new AbilityBuilder(Ability);
8
- rules.forEach(rule => {
9
- if (rule.inverted) {
10
- if (rule.fields && rule.conditions) {
11
- cannot(rule.action, rule.subject, rule.fields, rule.conditions);
12
- }
13
- else if (rule.fields) {
14
- cannot(rule.action, rule.subject, rule.fields);
15
- }
16
- else if (rule.conditions) {
17
- cannot(rule.action, rule.subject, rule.conditions);
18
- }
19
- else {
20
- cannot(rule.action, rule.subject);
21
- }
22
- }
23
- else {
24
- if (rule.fields && rule.conditions) {
25
- can(rule.action, rule.subject, rule.fields, rule.conditions);
26
- }
27
- else if (rule.fields) {
28
- can(rule.action, rule.subject, rule.fields);
29
- }
30
- else if (rule.conditions) {
31
- can(rule.action, rule.subject, rule.conditions);
32
- }
33
- else {
34
- can(rule.action, rule.subject);
35
- }
36
- }
37
- });
38
- return build({
39
- detectSubjectType: (object) => {
40
- // Handle CASL's subject() helper objects
41
- if (object && typeof object === 'object') {
42
- // CASL uses __caslSubjectType__ property
43
- if ('__caslSubjectType__' in object) {
44
- return object.__caslSubjectType__;
45
- }
46
- // Fallback to other type indicators
47
- if (object.type)
48
- return object.type;
49
- if (object.constructor?.name && object.constructor.name !== 'Object') {
50
- return object.constructor.name;
51
- }
52
- }
53
- return 'Unknown';
54
- },
55
- fieldMatcher: fields => field => {
56
- if (!fields || !field)
57
- return false;
58
- return Array.isArray(fields) ? fields.includes(field) : fields === field;
59
- },
60
- conditionsMatcher: conditions => object => {
61
- if (!conditions || !object)
62
- return true;
63
- return Object.keys(conditions).every(key => object[key] === conditions[key]);
64
- },
65
- });
66
- };
67
- describe('CASL GraphQL Middleware', () => {
68
- let mockResolve;
69
- let mockContext;
70
- let mockInfo;
71
- beforeEach(() => {
72
- mockResolve = vi.fn().mockResolvedValue({ data: 'test' });
73
- mockContext = {
74
- ability: createTestAbility([
75
- { action: 'read', subject: 'Query' },
76
- { action: 'read', subject: 'User' },
77
- { action: 'update', subject: 'User', conditions: { id: '123' } },
78
- ]),
79
- user: { id: '123', roles: ['user'] },
80
- };
81
- mockInfo = {
82
- fieldName: 'getUser',
83
- parentType: { name: 'Query' },
84
- path: { typename: 'Query', key: 'getUser' },
85
- operation: {
86
- operation: 'query',
87
- loc: { source: { body: 'query { getUser { id } }' } },
88
- },
89
- };
90
- });
91
- describe('Basic Middleware Functionality', () => {
92
- it('should pass through when ability allows the operation', async () => {
93
- const middleware = createCaslMiddleware();
94
- // Don't pass args that would be treated as conditions
95
- const result = await middleware(mockResolve, {}, {}, mockContext, mockInfo);
96
- expect(mockResolve).toHaveBeenCalled();
97
- expect(result).toEqual({ data: 'test' });
98
- });
99
- it('should create ability when not in context', async () => {
100
- const middleware = createCaslMiddleware();
101
- const contextWithoutAbility = { user: { id: '123', roles: ['user'] } };
102
- // The middleware creates a default ability if none exists
103
- // Default ability only allows 'read' on 'Query'
104
- const result = await middleware(mockResolve, {}, {}, contextWithoutAbility, mockInfo);
105
- // Should work because default ability allows 'read' on 'Query'
106
- expect(mockResolve).toHaveBeenCalled();
107
- expect(result).toEqual({ data: 'test' });
108
- expect(contextWithoutAbility.ability).toBeDefined();
109
- });
110
- it('should deny access when ability does not allow operation', async () => {
111
- const middleware = createCaslMiddleware();
112
- // Create ability that doesn't allow 'read' on 'Query'
113
- mockContext.ability = createTestAbility([{ action: 'read', subject: 'Post' }]);
114
- await expect(middleware(mockResolve, {}, {}, mockContext, mockInfo)).rejects.toThrow('Access denied for read on Query');
115
- });
116
- });
117
- describe('Action Mapping', () => {
118
- it('should map GraphQL operations to custom actions', async () => {
119
- const middleware = createCaslMiddleware({
120
- actionMap: {
121
- query: 'view',
122
- mutation: 'modify',
123
- subscription: 'watch',
124
- },
125
- });
126
- // Update ability to allow 'view' instead of 'read'
127
- mockContext.ability = createTestAbility([{ action: 'view', subject: 'Query' }]);
128
- const result = await middleware(mockResolve, {}, {}, mockContext, mockInfo);
129
- expect(mockResolve).toHaveBeenCalled();
130
- expect(result).toEqual({ data: 'test' });
131
- });
132
- it('should deny when custom action is not allowed', async () => {
133
- const middleware = createCaslMiddleware({
134
- actionMap: {
135
- query: 'view',
136
- },
137
- });
138
- // Ability still has 'read' permission, not 'view'
139
- await expect(middleware(mockResolve, {}, {}, mockContext, mockInfo)).rejects.toThrow('Access denied for view on Query');
140
- });
141
- });
142
- describe('Subject Conditions', () => {
143
- it('should handle conditional permissions correctly', async () => {
144
- const middleware = createCaslMiddleware();
145
- // Change to User type which has conditional permission
146
- mockInfo.parentType = { name: 'User' };
147
- mockInfo.path = { typename: 'User', key: 'updateUser' };
148
- mockInfo.operation.operation = 'mutation';
149
- // Ability allows update on User with id: '123'
150
- mockContext.ability = createTestAbility([{ action: 'update', subject: 'User', conditions: { id: '123' } }]);
151
- // This should work because we're not passing the conditions through args
152
- // The middleware should check the general permission
153
- const result = await middleware(mockResolve, {}, {}, mockContext, mockInfo);
154
- expect(mockResolve).toHaveBeenCalled();
155
- });
156
- });
157
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=yoga-plugin.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"yoga-plugin.test.d.ts","sourceRoot":"","sources":["../../tests/yoga-plugin.test.ts"],"names":[],"mappings":""}