data-primals-engine 1.6.3 → 1.6.5

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,341 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import {
5
+ sleep,
6
+ escapeRegex,
7
+ sequential,
8
+ isValidRegex,
9
+ escapeHtml,
10
+ isUnsecureKey,
11
+ parseSafeJSON,
12
+ isDate,
13
+ safeAssignObject,
14
+ debounce,
15
+ uuidv4,
16
+ cssProps,
17
+ removeDir,
18
+ wordWrap,
19
+ getObjectHash,
20
+ isPathRelativeTo,
21
+ isGUID,
22
+ isPlainObject,
23
+ escapeRegExp,
24
+ shuffle,
25
+ setSeed,
26
+ getRand,
27
+ getRandom,
28
+ randomDate,
29
+ isLightColor,
30
+ tryParseJson,
31
+ isIsoDate,
32
+ event_trigger,
33
+ event_on,
34
+ event_off,
35
+ slugify,
36
+ getFileExtension,
37
+ object_equals,
38
+ isValidPath,
39
+ stringToHslColor,
40
+ countKeys
41
+ } from '../src/core.js';
42
+
43
+ describe('Core Utility Functions', () => {
44
+
45
+ describe('isPlainObject', () => {
46
+ it('should return true for plain objects', () => {
47
+ expect(isPlainObject({})).toBe(true);
48
+ expect(isPlainObject({ a: 1 })).toBe(true);
49
+ });
50
+
51
+ it('should return false for non-plain objects or other types', () => {
52
+ expect(isPlainObject([])).toBe(false);
53
+ expect(isPlainObject(null)).toBe(false);
54
+ expect(isPlainObject(new Date())).toBe(false);
55
+ expect(isPlainObject("string")).toBe(false);
56
+ expect(isPlainObject(123)).toBe(false);
57
+ });
58
+ });
59
+
60
+ describe('slugify', () => {
61
+ it('should convert string to a slug', () => {
62
+ expect(slugify('Hello World!')).toBe('hello-world');
63
+ expect(slugify(' Test avec des espaces ')).toBe('test-avec-des-espaces');
64
+ expect(slugify('CaractèresSpéciaux-123', '-', true)).toBe('caracteresspeciaux-123');
65
+ });
66
+ });
67
+
68
+ describe('sleep', () => {
69
+ beforeEach(() => {
70
+ vi.useFakeTimers();
71
+ });
72
+ afterEach(() => {
73
+ vi.useRealTimers();
74
+ });
75
+
76
+ it('should resolve after the specified duration', async () => {
77
+ const sleepPromise = sleep(1000);
78
+ vi.advanceTimersByTime(1000);
79
+ await expect(sleepPromise).resolves.toBeUndefined();
80
+ });
81
+ });
82
+
83
+
84
+ describe('getObjectHash', () => {
85
+ it('should generate a consistent hash for the same object', () => {
86
+ const obj1 = { name: 'test', value: 1 };
87
+ const obj2 = { value: 1, name: 'test' }; // Ordre différent
88
+ expect(getObjectHash(obj1)).toBe(getObjectHash(obj2));
89
+ });
90
+
91
+ it('should generate a different hash for different objects', () => {
92
+ const obj1 = { name: 'test', value: 1 };
93
+ const obj2 = { name: 'test', value: 2 };
94
+ expect(getObjectHash(obj1)).not.toBe(getObjectHash(obj2));
95
+ });
96
+ });
97
+
98
+ describe('escapeRegex and escapeRegExp', () => {
99
+ it('should escape special regex characters', () => {
100
+ const specialString = '.*+?^${}()|[]\\';
101
+ const expected = '\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\';
102
+ expect(escapeRegex(specialString)).toBe(expected);
103
+ expect(escapeRegExp(specialString)).toBe(expected);
104
+ });
105
+ });
106
+
107
+ describe('sequential', () => {
108
+ it('should execute async tasks sequentially', async () => {
109
+ const order = [];
110
+ const task1 = async () => { await sleep(20); order.push(1); return 'a'; };
111
+ const task2 = async () => { await sleep(10); order.push(2); return 'b'; };
112
+
113
+ const results = await sequential([task1, task2]);
114
+
115
+ expect(order).toEqual([1, 2]);
116
+ expect(results).toEqual(['a', 'b']);
117
+ });
118
+ });
119
+
120
+ describe('isValidRegex', () => {
121
+ it('should validate correct regex strings', () => {
122
+ expect(isValidRegex('/hello/g')).toBe(true);
123
+ expect(isValidRegex('#[0-9]+#')).toBe(true);
124
+ });
125
+
126
+ it('should invalidate incorrect regex strings', () => {
127
+ expect(isValidRegex('/[a-z/')).toBe(false); // Unmatched bracket
128
+ expect(isValidRegex('hello')).toBe(false); // No delimiters
129
+ });
130
+ });
131
+
132
+ describe('escapeHtml', () => {
133
+ it('should escape HTML tags and dangerous protocols', () => {
134
+ const input = '<script>alert("xss")</script><a href="javascript:void(0)">link</a>'; // Le protocole "javascript:" sera supprimé
135
+ const expected = '&#60;script&#62;alert&#40;&#34;xss&#34;&#41;&#60;&#47;script&#62;&#60;a href&#61;&#34;void&#40;0&#41;&#34;&#62;link&#60;&#47;a&#62;';
136
+ expect(escapeHtml(input)).toBe(expected);
137
+ });
138
+ });
139
+
140
+ describe('isUnsecureKey', () => {
141
+ it('should identify unsecure keys', () => {
142
+ expect(isUnsecureKey('__proto__')).toBe(true);
143
+ expect(isUnsecureKey('constructor')).toBe(true);
144
+ expect(isUnsecureKey('prototype')).toBe(true);
145
+ expect(isUnsecureKey('safeKey')).toBe(false);
146
+ });
147
+ });
148
+
149
+ describe('parseSafeJSON', () => {
150
+ it('should parse valid JSON', () => {
151
+ expect(parseSafeJSON('{"a":1}')).toEqual({ a: 1 });
152
+ });
153
+
154
+ it('should prevent prototype pollution', () => {
155
+ const polluted = parseSafeJSON('{"__proto__":{"polluted":true}}');
156
+ expect(polluted.polluted).toBeUndefined();
157
+ const obj = {};
158
+ expect(obj.polluted).toBeUndefined();
159
+ });
160
+ });
161
+
162
+ describe('isDate', () => {
163
+ it('should return true for valid date strings and objects', () => {
164
+ expect(isDate(new Date())).toBe(true);
165
+ expect(isDate('2023-01-01')).toBe(true);
166
+ });
167
+
168
+ it('should return false for invalid dates', () => {
169
+ expect(isDate('not a date')).toBe(false);
170
+ expect(isDate(null)).toBe(false);
171
+ });
172
+ });
173
+
174
+ describe('safeAssignObject', () => {
175
+ it('should assign property for a safe key', () => {
176
+ const obj = {};
177
+ safeAssignObject(obj, 'a', 1);
178
+ expect(obj.a).toBe(1);
179
+ });
180
+
181
+ it('should not assign property for an unsecure key', () => {
182
+ const obj = {};
183
+ safeAssignObject(obj, '__proto__', { polluted: true });
184
+ expect(obj.polluted).toBeUndefined();
185
+ });
186
+ });
187
+
188
+ describe('debounce', () => {
189
+ beforeEach(() => { vi.useFakeTimers(); });
190
+ afterEach(() => { vi.useRealTimers(); });
191
+
192
+ it('should call the function only once after the delay', () => {
193
+ const spy = vi.fn();
194
+ const debounced = debounce(spy, 500);
195
+
196
+ debounced();
197
+ debounced();
198
+ debounced();
199
+
200
+ vi.advanceTimersByTime(500);
201
+ expect(spy).toHaveBeenCalledTimes(1);
202
+ });
203
+ });
204
+
205
+ describe('uuidv4', () => {
206
+ it('should generate a valid v4 UUID', () => {
207
+ const uuid = uuidv4();
208
+ const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
209
+ expect(uuid).toMatch(uuidV4Regex);
210
+ });
211
+ });
212
+
213
+ describe('cssProps', () => {
214
+ it('should convert a CSS string to a style object', () => {
215
+ const css = 'background-color: red; font-size: 16px;';
216
+ const expected = { backgroundColor: 'red', fontSize: '16px' };
217
+ expect(cssProps(css)).toEqual(expected);
218
+ });
219
+ });
220
+
221
+ describe('wordWrap', () => {
222
+ it('should wrap a long string at a specified width', () => {
223
+ const str = 'this is a long string to be wrapped';
224
+ const wrapped = wordWrap(str, 10);
225
+ expect(wrapped).toBe('this is a\nlong\nstring to\nbe wrapped');
226
+ });
227
+ });
228
+
229
+ describe('isPathRelativeTo', () => {
230
+ it('should correctly identify relative paths', () => {
231
+ expect(isPathRelativeTo('/a/b/c', '/a/b')).toBe(true);
232
+ expect(isPathRelativeTo('/a/d', '/a/b')).toBe(false);
233
+ expect(isPathRelativeTo('/a/b', '/a/b/c')).toBe(false);
234
+ });
235
+ });
236
+
237
+ describe('isGUID', () => {
238
+ it('should validate correct GUIDs', () => {
239
+ expect(isGUID('123e4567-e89b-12d3-a456-426614174000')).toBe(true);
240
+ });
241
+ it('should invalidate incorrect GUIDs', () => {
242
+ expect(isGUID('not-a-guid')).toBe(false);
243
+ });
244
+ });
245
+
246
+ describe('shuffle', () => {
247
+ it('should contain the same elements after shuffling', () => {
248
+ const original = [1, 2, 3, 4, 5];
249
+ const shuffled = [...original];
250
+ shuffle(shuffled);
251
+ expect(shuffled).toHaveLength(original.length);
252
+ expect(shuffled.sort()).toEqual(original.sort());
253
+ });
254
+ });
255
+
256
+ describe('Seeded Random', () => {
257
+ it('should produce consistent random numbers for the same seed', () => {
258
+ setSeed(12345);
259
+ const rand1 = getRand();
260
+ const rand2 = getRand();
261
+
262
+ setSeed(12345);
263
+ const rand3 = getRand();
264
+ const rand4 = getRand();
265
+
266
+ expect(rand1).toBe(rand3);
267
+ expect(rand2).toBe(rand4);
268
+ expect(rand1).not.toBe(rand2);
269
+ });
270
+ });
271
+
272
+ describe('isLightColor', () => {
273
+ it('should identify light and dark colors', () => {
274
+ expect(isLightColor('#FFFFFF')).toBe(true);
275
+ expect(isLightColor('#000000')).toBe(false);
276
+ expect(isLightColor('#f0f0f0')).toBe(true);
277
+ expect(isLightColor('#3498db')).toBe(false); // primary-color from scss
278
+ });
279
+ });
280
+
281
+ describe('tryParseJson', () => {
282
+ it('should parse valid JSON strings', () => {
283
+ expect(tryParseJson('{"a": 1}')).toEqual({ a: 1 });
284
+ });
285
+ it('should return null for invalid JSON strings', () => {
286
+ expect(tryParseJson('{"a": 1')).toBeNull();
287
+ expect(tryParseJson('not json')).toBeNull();
288
+ });
289
+ });
290
+
291
+ describe('isIsoDate', () => {
292
+ it('should validate correct ISO date strings', () => {
293
+ expect(isIsoDate('2023-10-26T10:00:00.000Z')).toBe(true);
294
+ });
295
+ it('should invalidate incorrect ISO date strings', () => {
296
+ expect(isIsoDate('2023-10-26')).toBe(false);
297
+ expect(isIsoDate(new Date().toString())).toBe(false);
298
+ });
299
+ });
300
+
301
+ describe('event system', () => {
302
+ beforeEach(() => {
303
+ event_off('test');
304
+ });
305
+ it('should trigger a registered event', () => {
306
+ const spy = vi.fn();
307
+ event_on('test', spy);
308
+ event_trigger('test', 'arg1');
309
+ expect(spy).toHaveBeenCalledWith('arg1');
310
+ });
311
+ });
312
+
313
+ describe('getFileExtension', () => {
314
+ it('should return the file extension', () => {
315
+ expect(getFileExtension('file.txt')).toBe('txt');
316
+ expect(getFileExtension('archive.tar.gz')).toBe('gz');
317
+ expect(getFileExtension('noextension')).toBe('');
318
+ });
319
+ });
320
+
321
+ describe('object_equals', () => {
322
+ it('should correctly compare objects', () => {
323
+ expect(object_equals({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } })).toBe(true);
324
+ expect(object_equals({ a: 1 }, { a: 2 })).toBe(false);
325
+ });
326
+ });
327
+
328
+ describe('stringToHslColor', () => {
329
+ it('should generate a consistent HSL color string', () => {
330
+ expect(stringToHslColor('test')).toBe('hsl(58, 70%, 55%)');
331
+ });
332
+ });
333
+
334
+ describe('countKeys', () => {
335
+ it('should count keys in nested objects and arrays', () => {
336
+ const obj = { a: 1, b: { c: 2, d: [1, { e: 3 }] } };
337
+ expect(countKeys(obj)).toBe(5);
338
+ });
339
+ });
340
+
341
+ });
@@ -9,7 +9,7 @@ import {
9
9
  insertData,
10
10
  editData,
11
11
  deleteData,
12
- searchData, installPack, deleteModels, createModel, patchData
12
+ searchData, installPack, deleteModels, createModel, patchData, datasCollection
13
13
  } from '../src/index.js';
14
14
 
15
15
  import {
@@ -17,7 +17,8 @@ import {
17
17
  getCollection,
18
18
  getCollectionForUser as getAppUserCollection, getCollectionForUser
19
19
  } from '../src/modules/mongodb.js';
20
- import {getRandom} from "../src/core.js";
20
+ import { isConditionMet } from '../src/filter.js';
21
+ import {getRandom, isPlainObject} from "../src/core.js";
21
22
  import {generateUniqueName, initEngine} from "../src/setenv.js";
22
23
  import {purgeData} from "../src/modules/data/data.history.js";
23
24
  import {removeFile} from "../src/modules/file.js";
@@ -1279,4 +1280,138 @@ describe('Data integration tests (CRUD, validation...)', () => {
1279
1280
  expect(data[0]._id.toString()).toBe(noEnvDocId.toString());
1280
1281
  });
1281
1282
  });
1283
+
1284
+ describe('Data operations with permission filters', () => {
1285
+ let testUser, testSystemUser, permissionAdd, permissionEdit, permissionDelete, role;
1286
+ let orderModelDef, pendingOrderId, shippedOrderId, otherUserOrderId;
1287
+
1288
+ beforeEach(async () => {
1289
+ // Création d'un utilisateur de test isolé
1290
+ const username = generateUniqueName('perm_filter_user');
1291
+ testUser = { _id: new ObjectId(), username: username, _user: 'testSystemUser', _model: 'user', roles: [] };
1292
+ testSystemUser = { _id: new ObjectId(), username: 'testSystemUser', roles: ['API_ADMIN'] }; // L'utilisateur système a tous les droits
1293
+ const collection = await getCollectionForUser(testUser);
1294
+
1295
+ // 1. Créer les modèles nécessaires
1296
+ await createModel({ name: 'permission', description:'t', _user: testSystemUser.username, fields: [{ name: 'name', type: 'string' }, { name: 'filter', type: 'code', language: 'json' }, { name: 'description', type: 'string' }] });
1297
+ await createModel({ name: 'role',description:'t', _user: testSystemUser.username, fields: [{ name: 'name', type: 'string' }, { name: 'permissions', type: 'relation', relation: 'permission', multiple: true }] });
1298
+ // Le modèle 'orderTest' doit exister pour les deux utilisateurs : testUser (pour les opérations de test) et testSystemUser (pour le setup)
1299
+ orderModelDef = { name: 'orderTest',description:'t', _user: testSystemUser.username, fields: [{ name: 'status', type: 'string' }, { name: 'amount', type: 'number' }, { name: 'customer', type: 'string' }] };
1300
+ const t = await createModel(orderModelDef);
1301
+
1302
+ // 2. Créer les permissions avec filtres pour les tests
1303
+ const permissionsToCreate = [
1304
+ // On a besoin de ces permissions pour que testUser puisse créer le rôle et les permissions
1305
+ { name: 'API_ADD_DATA_permission', _user: testSystemUser.username },
1306
+ { name: 'API_ADD_DATA_role', _user: testSystemUser.username },
1307
+ // Permissions de l'application
1308
+ { name: 'API_SEARCH_DATA_orderTest' }, // <-- AJOUTER CETTE LIGNE
1309
+ { name: 'API_ADD_DATA_orderTest', filter: { "$lt" : ["$amount", 1000] }, description: "Filter for adding orders" },
1310
+ { name: 'API_EDIT_DATA_orderTest', filter: { status: 'pending' } },
1311
+ { name: 'API_DELETE_DATA_orderTest', filter: { customer: testUser._id.toString() } }
1312
+ ];
1313
+ // On insère les permissions pour les deux utilisateurs pour que le rôle soit valide pour testUser
1314
+ let permResult = await insertData('permission', permissionsToCreate, {}, testSystemUser, false, false, false); // Pour le setup
1315
+ expect(permResult.success, `La création des permissions a échoué: ${permResult.error}`).toBe(true);
1316
+
1317
+ // 3. Récupérer les IDs des permissions et créer le rôle
1318
+ // On se base sur les permissions de l'utilisateur de test pour créer son rôle
1319
+ const allPerms = await (await getCollectionForUser(testSystemUser)).find({ _model: 'permission' }).toArray();
1320
+ const permIds = allPerms.map(p => p._id.toString());
1321
+
1322
+ // On insère le rôle pour les deux utilisateurs
1323
+ let roleRes = await insertData('role', { name: 'OrderManager', permissions: permIds }, {}, testSystemUser, false, false, false);
1324
+ expect(roleRes.success, `La création du rôle a échoué: ${roleRes.error}`).toBe(true);
1325
+ role = roleRes.insertedIds[0];
1326
+
1327
+ // 4. Assigner le rôle à l'utilisateur de test
1328
+ testUser.roles = [role.toString()];
1329
+
1330
+ // 5. Créer les données de test avec l'utilisateur de test lui-même.
1331
+ // Pour cela, on lui donne temporairement les droits d'admin pour le setup.
1332
+ const originalRoles = testUser.roles;
1333
+ testUser.roles = ['API_ADMIN']; // Droits temporaires pour créer les données
1334
+ const pendingOrder = await insertData('orderTest', { status: 'pending', amount: 100, customer: testUser._id.toString() }, {}, testUser, false, false, false);
1335
+ pendingOrderId = pendingOrder.insertedIds[0];
1336
+ const shippedOrder = await insertData('orderTest', { status: 'shipped', amount: 200, customer: testUser._id.toString() }, {}, testUser, false, false, false);
1337
+ shippedOrderId = shippedOrder.insertedIds[0];
1338
+ const otherOrder = await insertData('orderTest', { status: 'pending', amount: 300, customer: 'other_user_id' }, {}, testUser, false, false, false);
1339
+ otherUserOrderId = otherOrder.insertedIds[0];
1340
+ testUser.roles = originalRoles; // Restaurer les vrais rôles pour le test
1341
+ });
1342
+
1343
+ afterEach(async () => {
1344
+ await (await getCollectionForUser(testUser)).deleteMany({ _user: testUser.username });
1345
+ await (await getCollectionForUser(testSystemUser)).deleteMany({ _user: testSystemUser.username });
1346
+ await deleteModels({ _user: testUser.username });
1347
+ await deleteModels({ _user: testSystemUser.username });
1348
+ });
1349
+
1350
+ afterAll(async () =>{
1351
+ await purgeData(testUser);
1352
+ await purgeData(testSystemUser);
1353
+ await datasCollection.drop();
1354
+ })
1355
+
1356
+ // --- TEST POUR insertData ---
1357
+ it('should REJECT inserting data that violates the permission filter', async () => {
1358
+ // This should fail because the amount (1500) is not < 1000
1359
+ const result = await insertData('orderTest', { status: 'new', amount: 1500, customer: 'new_customer' }, {}, testUser, false, false);
1360
+
1361
+ expect(result.success).toBe(false);
1362
+
1363
+ });
1364
+
1365
+ it('should ALLOW inserting data that respects the permission filter', async () => {
1366
+ // This should succeed because the amount (500) is < 1000
1367
+ const result = await insertData('orderTest', { status: 'new', amount: 500, customer: 'new_customer' }, {}, testUser, false, false);
1368
+
1369
+ expect(result.success, `L'insertion aurait dû réussir: ${result.error}`).toBe(true);
1370
+ expect(result.insertedIds).toHaveLength(1);
1371
+ });
1372
+
1373
+ // --- TESTS POUR editData ---
1374
+ it('should ALLOW editing a document that matches the permission filter', async () => {
1375
+ // Le filtre de permission est { status: 'pending' }
1376
+ const result = await editData('orderTest', { _id: pendingOrderId }, { amount: 150 }, {}, testUser, false, false);
1377
+
1378
+ expect(result.success, `editData failed with error: ${result.error}`).toBe(true);
1379
+ expect(result.modifiedCount).toBeGreaterThanOrEqual(0); // Peut être 0 si le hash ne change pas
1380
+
1381
+ const coll = await getCollectionForUser(testUser);
1382
+ const doc = await coll.findOne({ _id: new ObjectId(pendingOrderId)});
1383
+ expect(doc.amount).toBe(150);
1384
+ });
1385
+
1386
+ it('should REJECT editing a document that does NOT match the permission filter', async () => {
1387
+ // On tente de modifier la commande 'shipped', mais la permission ne l'autorise que pour 'pending'
1388
+ const result = await editData('orderTest', { $eq: ['$_id', new ObjectId(shippedOrderId)] }, { amount: 250 }, {}, testUser, false, false);
1389
+
1390
+ expect(result.success).toBe(false);
1391
+ });
1392
+
1393
+ // --- TESTS POUR deleteData ---
1394
+ it('should ALLOW deleting a document that matches the permission filter', async () => {
1395
+ // La permission autorise la suppression des commandes où `customer` est l'ID de l'utilisateur
1396
+ const result = await deleteData('orderTest', { _id: new ObjectId(pendingOrderId) }, testUser, false, false);
1397
+
1398
+ expect(result.success, `deleteData failed with error: ${result.error}`).toBe(true);
1399
+ expect(result.deletedCount).toBe(1);
1400
+
1401
+ const doc = await (await getCollectionForUser(testUser)).findOne({ _id: new ObjectId(pendingOrderId) });
1402
+ expect(doc).toBeNull();
1403
+ });
1404
+
1405
+ it('should REJECT deleting a document that does NOT match the permission filter', async () => {
1406
+ // On tente de supprimer une commande qui n'appartient pas à l'utilisateur
1407
+ const result = await deleteData('orderTest', { _id: otherUserOrderId }, testUser, false, false);
1408
+
1409
+ // La fonction va trouver 0 document à supprimer après application du filtre de permission
1410
+ expect(result.success).toBe(true); // La fonction réussit, mais ne supprime rien
1411
+ expect(result.deletedCount).toBe(0);
1412
+
1413
+ const doc = await (await getCollectionForUser(testSystemUser)).findOne({ _id: new ObjectId(otherUserOrderId) });
1414
+ expect(doc).not.toBeNull(); // Le document existe toujours
1415
+ });
1416
+ });
1282
1417
  });
package/test/user.test.js CHANGED
@@ -16,34 +16,39 @@ describe('User Permission System with Filters', () => {
16
16
  let permNoFilter, permWithSimpleFilter, permWithUserFilter, permWithReqFilter, permOrderEditForManager;
17
17
  let roleWithPerms;
18
18
 
19
+ // Le parent qui possède les définitions de rôles et permissions
20
+ const parentUser = { username: generateUniqueName('perm_parent_user') };
21
+
19
22
  beforeAll(async () => {
20
23
  Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow", "user", "assistant"]);
21
24
  engine = await initEngine();
22
25
  logger = engine.getComponent(Logger);
23
26
  await onInit(engine);
27
+ // La collection est celle du parent, car c'est lui qui stocke les définitions
28
+ collection = await getCollectionForUser(parentUser);
24
29
  });
25
30
 
26
31
  beforeEach(async () => {
27
32
  // Utiliser un utilisateur unique pour l'isolation des tests
28
33
  const username = generateUniqueName('perm_test_user');
29
- testUser = { _id: new ObjectId(), username: username, _user: username, roles: [], _model: 'user' };
30
- testUserWithRole = { _id: new ObjectId(), username: username, _user: username, roles: [], _model: 'user' }; // roles sera ajouté après la création du rôle
34
+ // L'utilisateur de test est un "enfant" de parentUser
35
+ testUser = { _id: new ObjectId(), username: username, _user: parentUser.username, roles: [], _model: 'user' };
36
+ testUserWithRole = { _id: new ObjectId(), username: username, _user: parentUser.username, roles: [], _model: 'user' };
31
37
  systemUser = { roles: ['admin', 'product.view'] };
32
38
 
33
- collection = await getCollectionForUser(testUser);
34
- await collection.deleteMany({ _user: username });
39
+ await collection.deleteMany({ _user: parentUser.username });
35
40
 
36
41
  // --- Création des données de test ---
37
42
  // Créer les modèles nécessaires pour les permissions et les rôles
38
- await createModel({ name: 'permission', _user: username, fields: [{ name: 'name', type: 'string' }, { name: 'filter', type: 'code' }] });
39
- await createModel({ name: 'role', _user: username, fields: [{ name: 'name', type: 'string' }, { name: 'permissions', type: 'relation', relation: 'permission', multiple: true }] });
40
- await createModel({ name: 'userPermission', _user: username, fields: [{ name: 'user', type: 'relation', relation: 'user' }, { name: 'permission', type: 'relation', relation: 'permission' }, { name: 'isGranted', type: 'boolean' }, { name: 'filter', type: 'code' }] });
43
+ await createModel({ name: 'permission', description: '', _user: parentUser.username, fields: [{ name: 'name', type: 'string' }, { name: 'filter', type: 'code' }] });
44
+ await createModel({ name: 'role', description: '', _user: parentUser.username, fields: [{ name: 'name', type: 'string' }, { name: 'permissions', type: 'relation', relation: 'permission', multiple: true }] });
45
+ await createModel({ name: 'userPermission', description: '', _user: parentUser.username, fields: [{ name: 'user', type: 'relation', relation: 'user' }, { name: 'permission', type: 'relation', relation: 'permission' }, { name: 'isGranted', type: 'boolean' }, { name: 'filter', type: 'code',language:'json' }] });
41
46
 
42
47
  const permResult = await collection.insertMany([
43
- { _model: 'permission', name: 'product.view', _user: username },
44
- { _model: 'permission', name: 'order.edit', filter: { status: 'pending' }, _user: username },
45
- { _model: 'permission', name: 'document.edit', filter: { createdBy: '{user._id}' }, _user: username },
46
- { _model: 'permission', name: 'login.attempt', filter: { "ip": "{req.ip}" }, _user: username }
48
+ { _model: 'permission', name: 'product.view', _user: parentUser.username },
49
+ { _model: 'permission', name: 'order.edit', filter: { status: 'pending' }, _user: parentUser.username },
50
+ { _model: 'permission', name: 'document.edit', filter: { createdBy: '{user._id}' }, _user: parentUser.username },
51
+ { _model: 'permission', name: 'login.attempt', filter: { "ip": "{req.ip}" }, _user: parentUser.username }
47
52
  ]);
48
53
  permNoFilter = permResult.insertedIds[0];
49
54
  permWithSimpleFilter = permResult.insertedIds[1];
@@ -54,7 +59,7 @@ describe('User Permission System with Filters', () => {
54
59
  _model: 'role',
55
60
  name: 'Editor',
56
61
  permissions: [permNoFilter.toString(), permWithSimpleFilter.toString()],
57
- _user: username
62
+ _user: parentUser.username
58
63
  });
59
64
  roleWithPerms = roleResult.insertedId;
60
65
 
@@ -65,9 +70,9 @@ describe('User Permission System with Filters', () => {
65
70
  afterEach(async () => {
66
71
  // Nettoyer les données créées
67
72
  if (collection) {
68
- await collection.deleteMany({ _user: testUser.username });
73
+ await collection.deleteMany({ _user: parentUser.username });
69
74
  }
70
- await deleteModels({ _user: testUser.username });
75
+ await deleteModels({ _user: parentUser.username });
71
76
  });
72
77
 
73
78
  it('should return true for a permission without filter granted by a role', async () => {
@@ -91,7 +96,7 @@ describe('User Permission System with Filters', () => {
91
96
  user: testUser._id.toString(),
92
97
  permission: permWithUserFilter.toString(),
93
98
  isGranted: true,
94
- _user: testUser.username
99
+ _user: parentUser.username
95
100
  });
96
101
 
97
102
  const result = await hasPermission('document.edit', testUser);
@@ -104,7 +109,7 @@ describe('User Permission System with Filters', () => {
104
109
  user: testUserWithRole._id.toString(),
105
110
  permission: permWithUserFilter.toString(),
106
111
  isGranted: true,
107
- _user: testUser.username
112
+ _user: parentUser.username
108
113
  });
109
114
 
110
115
  const result = await hasPermission('document.edit', testUserWithRole);
@@ -117,7 +122,7 @@ describe('User Permission System with Filters', () => {
117
122
  user: testUser._id.toString(),
118
123
  permission: permWithReqFilter.toString(),
119
124
  isGranted: true,
120
- _user: testUser.username
125
+ _user: parentUser.username
121
126
  });
122
127
 
123
128
  const mockReq = {
@@ -134,14 +139,14 @@ describe('User Permission System with Filters', () => {
134
139
  it('should revoke a permission from a role via userPermission exception', async () => {
135
140
  let result = await hasPermission('order.edit', testUserWithRole);
136
141
  expect(result).toEqual({ status: 'pending' });
137
-
138
- await collection.insertOne({
139
- _model: 'userPermission',
142
+
143
+ // Utiliser insertData pour déclencher l'invalidation du cache
144
+ await insertData('userPermission', {
140
145
  user: testUserWithRole._id.toString(),
141
146
  permission: permWithSimpleFilter.toString(),
142
147
  isGranted: false,
143
- _user: testUser.username
144
- });
148
+ }, {}, parentUser, true, true, false // le dernier false pour bypasser la vérification de permission pour le test
149
+ );
145
150
 
146
151
  result = await hasPermission('order.edit', testUserWithRole);
147
152
  expect(result).toBe(false);
@@ -152,7 +157,7 @@ describe('User Permission System with Filters', () => {
152
157
  _model: 'permission',
153
158
  name: 'order.edit',
154
159
  filter: { manager_id: '{user._id}' },
155
- _user: testUser.username
160
+ _user: parentUser.username
156
161
  });
157
162
  permOrderEditForManager = permResult.insertedId;
158
163
 
@@ -161,7 +166,7 @@ describe('User Permission System with Filters', () => {
161
166
  user: testUserWithRole._id.toString(),
162
167
  permission: permOrderEditForManager.toString(),
163
168
  isGranted: true,
164
- _user: testUser.username
169
+ _user: parentUser.username
165
170
  });
166
171
 
167
172
  const result = await hasPermission('order.edit', testUserWithRole);
@@ -175,14 +180,13 @@ describe('User Permission System with Filters', () => {
175
180
  expect(result).toEqual({ status: 'pending' });
176
181
 
177
182
  // On ajoute une exception qui accorde la même permission mais avec un filtre différent
178
- await collection.insertOne({
179
- _model: 'userPermission',
183
+ // Utiliser insertData pour s'assurer que le cache est invalidé
184
+ await insertData('userPermission', {
180
185
  user: testUserWithRole._id.toString(),
181
186
  permission: permWithSimpleFilter.toString(), // La permission 'order.edit'
182
187
  isGranted: true,
183
188
  filter: { "assignedTo": "{user._id}" }, // Le nouveau filtre
184
- _user: testUser.username
185
- });
189
+ }, {}, parentUser, true, true, false);
186
190
 
187
191
  // La fonction doit maintenant retourner le filtre de l'exception
188
192
  result = await hasPermission('order.edit', testUserWithRole);
@@ -198,7 +202,7 @@ describe('User Permission System with Filters', () => {
198
202
  permission: permWithUserFilter.toString(), // La permission 'document.edit'
199
203
  isGranted: true,
200
204
  // Pas de champ 'filter' ici
201
- _user: testUser.username
205
+ _user: parentUser.username
202
206
  });
203
207
 
204
208
  // La fonction doit retourner le filtre de la permission de base.