data-primals-engine 1.6.2 → 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.
Files changed (39) hide show
  1. package/README.md +56 -911
  2. package/client/package-lock.json +64 -50
  3. package/client/package.json +6 -0
  4. package/client/src/App.scss +29 -0
  5. package/client/src/Dashboard.jsx +1 -1
  6. package/client/src/WorkflowEditor.jsx +101 -0
  7. package/client/src/WorkflowEditor.scss +29 -4
  8. package/client/src/_variables.scss +3 -0
  9. package/doc/automation-workflows.md +102 -0
  10. package/doc/core-concepts.md +33 -0
  11. package/doc/custom-api-endpoints.md +40 -0
  12. package/doc/dashboards-kpis-charts.md +49 -0
  13. package/doc/data-management.md +120 -0
  14. package/doc/data-models.md +75 -0
  15. package/doc/index.md +14 -0
  16. package/doc/roles-permissions.md +43 -0
  17. package/doc/users.md +30 -0
  18. package/package.json +7 -5
  19. package/src/client.js +6 -4
  20. package/src/core.js +31 -12
  21. package/src/defaultModels +1628 -0
  22. package/src/defaultModels.js +14 -0
  23. package/src/filter.js +110 -42
  24. package/src/modules/data/data.history.js +5 -1
  25. package/src/modules/data/data.js +2 -1
  26. package/src/modules/data/data.operations.js +116 -72
  27. package/src/modules/data/data.relations.js +1 -1
  28. package/src/modules/mongodb.js +2 -1
  29. package/src/modules/swagger.js +25 -5
  30. package/src/modules/user.js +138 -76
  31. package/src/modules/workflow.js +187 -177
  32. package/src/providers.js +1 -1
  33. package/swagger-en.yml +2308 -472
  34. package/swagger-fr.yml +487 -3
  35. package/test/core.test.js +341 -0
  36. package/test/data.history.integration.test.js +140 -16
  37. package/test/data.integration.test.js +137 -2
  38. package/test/user.test.js +201 -280
  39. package/test/workflow.integration.test.js +8 -0
package/test/user.test.js CHANGED
@@ -1,315 +1,236 @@
1
- import { expect, describe, it, afterEach, beforeAll, afterAll } from 'vitest';
2
- import { hasPermission, getUserActivePermissions } from '../src/modules/user.js';
3
- import {initEngine} from "../src/setenv";
4
- import {generateUniqueName} from "../src/setenv.js";
5
- import {
6
- getCollectionForUser as getAppUserCollection,
7
- modelsCollection as getAppModelsCollection
8
- } from "../src/modules/mongodb.js";
9
- import {Config} from "../src/index.js";
10
- let permRead, permWrite, permDelete, permManage;
11
- let roleEditor;
12
- let testUser, adminUser, roleViewer;
13
- let currentTestUser;
14
- let testModelsColInstance, testDatasColInstance;
15
- // Cette fonction va remplacer la logique de votre beforeEach pour la création de contexte
16
- async function setupTestContext() {
17
-
18
-
19
- // Créer un utilisateur unique pour ce test
20
- const username = generateUniqueName('testuserUserIntegration');
21
- currentTestUser = {
22
- username,
23
- userPlan: 'free',
24
- _model: 'user',
25
- _user: username,
26
- email: generateUniqueName('test') + '@example.com'
27
- };
28
-
29
- // Initialize collection instances after the engine is ready
30
- testModelsColInstance = getAppModelsCollection;
31
- testDatasColInstance = await getAppUserCollection(currentTestUser);
32
-
33
- await testDatasColInstance.deleteMany({}); // Nettoyage de la base de test
34
- // 1. Créer les permissions
35
- const permissions = await testDatasColInstance.insertMany([
36
- { _model: 'permission', name: 'post:read', description: 'Lire les articles' },
37
- { _model: 'permission', name: 'post:write', description: 'Écrire des articles' },
38
- { _model: 'permission', name: 'post:delete', description: 'Supprimer des articles' },
39
- { _model: 'permission', name: 'user:manage', description: 'Gérer les utilisateurs' }
40
- ]);
41
- permRead = permissions.insertedIds[0];
42
- permWrite = permissions.insertedIds[1];
43
- permDelete = permissions.insertedIds[2];
44
- permManage = permissions.insertedIds[3];
45
-
46
- // 2. Créer les rôles
47
- const roles = await testDatasColInstance.insertMany([
48
- { _user: currentTestUser.username, _model: 'role', name: 'Viewer', permissions: [permRead.toString()] },
49
- { _user: currentTestUser.username, _model: 'role', name: 'Editor', permissions: [permRead.toString(), permWrite.toString()] }
50
- ]);
51
- roleViewer = roles.insertedIds[0];
52
- roleEditor = roles.insertedIds[1];
53
-
54
- // 3. Créer les utilisateurs
55
- const users = await testDatasColInstance.insertMany([
56
- { _user: currentTestUser.username, _model: 'user', roles: [roleEditor.toString()] },
57
- { _user: currentTestUser.username, _model: 'user', roles: [roleEditor.toString()] }
58
- ]);
59
- testUser = {...currentTestUser, _id: users.insertedIds[0].toString(), roles: [roleEditor.toString()] };
60
- adminUser = {...currentTestUser, _id: users.insertedIds[1].toString(), roles: [roleEditor.toString()] };
61
-
62
- return testDatasColInstance;
63
- };
1
+ import { vi, describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
2
+ import { ObjectId } from 'mongodb';
3
+ import { hasPermission, onInit } from '../src/modules/user.js';
4
+ import { Config } from '../src/config.js';
5
+ import { initEngine, generateUniqueName } from '../src/setenv.js';
6
+ import { getCollection, getCollectionForUser } from '../src/modules/mongodb.js';
7
+ import { Logger } from '../src/gameObject.js';
8
+ import { insertData, deleteData, deleteModels, createModel } from '../src/index.js';
9
+
64
10
  let engine;
65
- describe('User Permission Logic', () => {
11
+ let logger;
12
+
13
+ describe('User Permission System with Filters', () => {
14
+ let collection;
15
+ let testUser, testUserWithRole, systemUser;
16
+ let permNoFilter, permWithSimpleFilter, permWithUserFilter, permWithReqFilter, permOrderEditForManager;
17
+ let roleWithPerms;
18
+
19
+ // Le parent qui possède les définitions de rôles et permissions
20
+ const parentUser = { username: generateUniqueName('perm_parent_user') };
66
21
 
67
- // --- SETUP : Création des données de test avant tous les tests ---
68
22
  beforeAll(async () => {
69
- Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant", "auth-google", "auth-saml", "auth-microsoft"]);
23
+ Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow", "user", "assistant"]);
70
24
  engine = await initEngine();
71
-
25
+ logger = engine.getComponent(Logger);
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);
72
29
  });
73
30
 
74
- // --- Tests pour la fonction principale de calcul ---
75
- describe('getUserActivePermissions()', () => {
76
-
77
- it('should return base permissions from the user\'s role', async () => {
78
- const coll = await setupTestContext();
79
- const permissions = await getUserActivePermissions(testUser);
80
- expect(permissions).to.be.an.instanceOf(Set);
81
- expect(permissions.size).to.equal(2);
82
- expect(permissions.has('post:read')).to.be.true;
83
- expect(permissions.has('post:write')).to.be.true;
84
- expect(permissions.has('post:delete')).to.be.false;
85
- await coll.drop();
86
- });
31
+ beforeEach(async () => {
32
+ // Utiliser un utilisateur unique pour l'isolation des tests
33
+ const username = generateUniqueName('perm_test_user');
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' };
37
+ systemUser = { roles: ['admin', 'product.view'] };
38
+
39
+ await collection.deleteMany({ _user: parentUser.username });
40
+
41
+ // --- Création des données de test ---
42
+ // Créer les modèles nécessaires pour les permissions et les rôles
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' }] });
46
+
47
+ const permResult = await collection.insertMany([
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 }
52
+ ]);
53
+ permNoFilter = permResult.insertedIds[0];
54
+ permWithSimpleFilter = permResult.insertedIds[1];
55
+ permWithUserFilter = permResult.insertedIds[2];
56
+ permWithReqFilter = permResult.insertedIds[3];
57
+
58
+ const roleResult = await collection.insertOne({
59
+ _model: 'role',
60
+ name: 'Editor',
61
+ permissions: [permNoFilter.toString(), permWithSimpleFilter.toString()],
62
+ _user: parentUser.username
63
+ });
64
+ roleWithPerms = roleResult.insertedId;
65
+
66
+ // Assigner le rôle à l'utilisateur de test
67
+ testUserWithRole.roles = [roleWithPerms.toString()];
68
+ });
87
69
 
88
- it('should grant a temporary permission via an exception', async () => {
89
- const coll = await setupTestContext();
90
- // Ajoute une exception qui donne la permission de supprimer, expirant demain
91
- const futureDate = new Date();
92
- futureDate.setDate(futureDate.getDate() + 1);
70
+ afterEach(async () => {
71
+ // Nettoyer les données créées
72
+ if (collection) {
73
+ await collection.deleteMany({ _user: parentUser.username });
74
+ }
75
+ await deleteModels({ _user: parentUser.username });
76
+ });
93
77
 
94
- await coll.insertOne({
95
- _model: 'userPermission',
96
- user: testUser._id,
97
- permission: permDelete,
98
- isGranted: true,
99
- expiresAt: futureDate
100
- });
78
+ it('should return true for a permission without filter granted by a role', async () => {
79
+ const result = await hasPermission('product.view', testUserWithRole);
80
+ expect(result).toBe(true);
81
+ });
101
82
 
102
- const permissions = await getUserActivePermissions(testUser);
103
- expect(permissions.size).to.equal(3);
104
- expect(permissions.has('post:delete')).to.be.true;
83
+ it('should return a simple filter object for a permission with filter granted by a role', async () => {
84
+ const result = await hasPermission('order.edit', testUserWithRole);
85
+ expect(result).toEqual({ status: 'pending' });
86
+ });
105
87
 
106
- // Nettoyage de l'exception pour ne pas affecter les autres tests
107
- await coll.deleteOne({ _model: 'userPermission', user: testUser._id });
88
+ it('should return false if user does not have the permission', async () => {
89
+ const result = await hasPermission('document.edit', testUserWithRole);
90
+ expect(result).toBe(false);
91
+ });
108
92
 
109
- await coll.drop();
93
+ it('should grant a permission with a filter via userPermission exception', async () => {
94
+ await collection.insertOne({
95
+ _model: 'userPermission',
96
+ user: testUser._id.toString(),
97
+ permission: permWithUserFilter.toString(),
98
+ isGranted: true,
99
+ _user: parentUser.username
110
100
  });
111
101
 
112
- it('should NOT grant an expired temporary permission', async () => {
113
- const coll = await setupTestContext();
114
- // Ajoute une exception qui a expiré hier
115
- const pastDate = new Date();
116
- pastDate.setDate(pastDate.getDate() - 1);
117
-
118
- await coll.insertOne({
119
- _model: 'userPermission',
120
- user: testUser._id,
121
- permission: permDelete,
122
- isGranted: true,
123
- expiresAt: pastDate
124
- });
125
-
126
- const permissions = await getUserActivePermissions(testUser);
127
- expect(permissions.size).to.equal(2); // Revient la normale
128
- expect(permissions.has('post:delete')).to.be.false;
129
-
130
- await coll.deleteOne({ _model: 'userPermission', user: testUser._id });
131
- await coll.drop();
132
- });
102
+ const result = await hasPermission('document.edit', testUser);
103
+ expect(result).toEqual({ createdBy: testUser._id.toString() });
104
+ });
133
105
 
134
- it('should revoke a base permission via an exception', async () => {
135
- const coll = await setupTestContext();
136
- // L'utilisateur est "Editor", mais on lui retire le droit d'écriture temporairement
137
- const futureDate = new Date();
138
- futureDate.setDate(futureDate.getDate() + 1);
139
-
140
- await coll.insertOne({
141
- _model: 'userPermission',
142
- user: testUser._id,
143
- permission: permWrite,
144
- isGranted: false, // Révocation
145
- expiresAt: futureDate
146
- });
147
-
148
- const permissions = await getUserActivePermissions(testUser);
149
- expect(permissions.size).to.equal(1);
150
- expect(permissions.has('post:read')).to.be.true;
151
- expect(permissions.has('post:write')).to.be.false; // La permission a été retirée
152
-
153
- await coll.deleteOne({ _model: 'userPermission', user: testUser._id });
154
- await coll.drop();
106
+ it('should substitute user._id in the filter', async () => {
107
+ await collection.insertOne({
108
+ _model: 'userPermission',
109
+ user: testUserWithRole._id.toString(),
110
+ permission: permWithUserFilter.toString(),
111
+ isGranted: true,
112
+ _user: parentUser.username
155
113
  });
156
114
 
157
- it('should restore a revoked permission if the revocation has expired', async () => {
158
- const coll = await setupTestContext();
159
- // La révocation a expiré, l'utilisateur devrait retrouver son droit d'écriture
160
- const pastDate = new Date();
161
- pastDate.setDate(pastDate.getDate() - 1);
162
-
163
- await coll.insertOne({
164
- _model: 'userPermission',
165
- user: testUser._id,
166
- permission: permWrite,
167
- isGranted: false,
168
- expiresAt: pastDate
169
- });
170
-
171
- const permissions = await getUserActivePermissions(testUser);
172
- expect(permissions.size).to.equal(2);
173
- expect(permissions.has('post:write')).to.be.true; // La permission est de retour
174
-
175
- await coll.deleteOne({ _model: 'userPermission', user: testUser._id });
176
- await coll.drop();
177
- });
115
+ const result = await hasPermission('document.edit', testUserWithRole);
116
+ expect(result).toEqual({ createdBy: testUserWithRole._id.toString() });
178
117
  });
179
118
 
180
- // --- Tests pour la fonction publique `hasPermission` ---
181
- describe('hasPermission()', () => {
182
- it('should return true for a permission the user has', async () => {
183
- const coll = await setupTestContext();
184
- const result = await hasPermission('post:read', testUser);
185
- expect(result).to.be.true;
186
- await coll.drop();
119
+ it('should substitute request context variables like req.ip', async () => {
120
+ await collection.insertOne({
121
+ _model: 'userPermission',
122
+ user: testUser._id.toString(),
123
+ permission: permWithReqFilter.toString(),
124
+ isGranted: true,
125
+ _user: parentUser.username
187
126
  });
188
127
 
189
- it('should return false for a permission the user does not have', async () => {
190
- const coll = await setupTestContext();
191
- const result = await hasPermission('user:manage', testUser);
192
- expect(result).to.be.false;
193
- await coll.drop();
194
- });
128
+ const mockReq = {
129
+ ip: '192.168.1.100',
130
+ query: { from: 'test' },
131
+ body: { name: 'test-body' },
132
+ params: { id: '123' }
133
+ };
195
134
 
196
- it('should return true if user has at least one of the required permissions', async () => {
197
- const coll = await setupTestContext();
198
- const result = await hasPermission(['user:manage', 'post:write'], testUser);
199
- expect(result).to.be.true;
200
- await coll.drop();
201
- });
135
+ const result = await hasPermission('login.attempt', testUser, null, mockReq);
136
+ expect(result).toEqual({ ip: '192.168.1.100' });
137
+ });
202
138
 
203
- it('should return false if user has none of the required permissions', async () => {
204
- const coll = await setupTestContext();
205
- const result = await hasPermission(['user:manage', 'post:delete'], testUser);
206
- expect(result).to.be.false;
207
- await coll.drop();
208
- });
139
+ it('should revoke a permission from a role via userPermission exception', async () => {
140
+ let result = await hasPermission('order.edit', testUserWithRole);
141
+ expect(result).toEqual({ status: 'pending' });
142
+
143
+ // Utiliser insertData pour déclencher l'invalidation du cache
144
+ await insertData('userPermission', {
145
+ user: testUserWithRole._id.toString(),
146
+ permission: permWithSimpleFilter.toString(),
147
+ isGranted: false,
148
+ }, {}, parentUser, true, true, false // le dernier false pour bypasser la vérification de permission pour le test
149
+ );
150
+
151
+ result = await hasPermission('order.edit', testUserWithRole);
152
+ expect(result).toBe(false);
209
153
  });
210
154
 
211
- describe('hasPermission() with environment context', () => {
212
- let user, coll;
213
- let permRead, permWrite;
214
- let envProdId, envStagingId;
215
-
216
- beforeAll(async () => {
217
- // --- Setup ---
218
- coll = await setupTestContext();
219
- user = currentTestUser; // Récupère l'utilisateur du contexte
220
-
221
- // 1. Créer les permissions
222
- const perms = await coll.insertMany([
223
- { _model: 'permission', name: 'env:read', _user: user.username },
224
- { _model: 'permission', name: 'env:write', _user: user.username }
225
- ]);
226
- permRead = perms.insertedIds[0];
227
- permWrite = perms.insertedIds[1];
228
-
229
- // 2. Créer les environnements
230
- const envs = await coll.insertMany([
231
- { _model: 'env', name: 'prod', _user: user.username },
232
- { _model: 'env', name: 'staging', _user: user.username }
233
- ]);
234
- envProdId = envs.insertedIds[0].toString();
235
- envStagingId = envs.insertedIds[1].toString();
236
-
237
- // 3. Créer un rôle de base et l'assigner à l'utilisateur
238
- const role = await coll.insertOne({
239
- _model: 'role',
240
- name: 'EnvTester',
241
- permissions: [permRead.toString()],
242
- _user: user.username
243
- });
244
- await coll.updateOne(
245
- { _id: user._id },
246
- { $set: { roles: [role.insertedId.toString()] } }
247
- );
248
- // Mettre à jour l'objet utilisateur en mémoire
249
- user.roles = [role.insertedId.toString()];
250
- user.permissions = ['env:read'];
155
+ it('should override a role filter with a userPermission filter', async () => {
156
+ const permResult = await collection.insertOne({
157
+ _model: 'permission',
158
+ name: 'order.edit',
159
+ filter: { manager_id: '{user._id}' },
160
+ _user: parentUser.username
251
161
  });
162
+ permOrderEditForManager = permResult.insertedId;
252
163
 
253
- afterAll(async () => {
254
- await coll.drop();
164
+ await collection.insertOne({
165
+ _model: 'userPermission',
166
+ user: testUserWithRole._id.toString(),
167
+ permission: permOrderEditForManager.toString(),
168
+ isGranted: true,
169
+ _user: parentUser.username
255
170
  });
256
171
 
257
- afterEach(async () => {
258
- // Nettoyer les exceptions de permission après chaque test
259
- await coll.deleteMany({ _model: 'userPermission', user: user._id });
260
- });
172
+ const result = await hasPermission('order.edit', testUserWithRole);
261
173
 
262
- it('should grant permission if exception is global (no env)', async () => {
263
- await coll.insertOne({
264
- _model: 'userPermission', user: user._id, permission: permWrite, isGranted: true
265
- });
266
- // L'utilisateur doit avoir la permission dans n'importe quel environnement
267
- expect(await hasPermission(['env:write'], user, envProdId)).toBe(true);
268
- expect(await hasPermission(['env:write'], user, envStagingId)).toBe(true);
269
- expect(await hasPermission(['env:write'], user, null)).toBe(true); // Environnement par défaut
270
- });
174
+ expect(result).toEqual({ manager_id: testUserWithRole._id.toString() });
175
+ });
271
176
 
272
- it('should grant permission only in the specified environment', async () => {
273
- await coll.insertOne({
274
- _model: 'userPermission', user: user._id, permission: permWrite, isGranted: true, env: envProdId
275
- });
276
- // L'utilisateur ne doit avoir la permission qu'en 'prod'
277
- expect(await hasPermission(['env:write'], user, envProdId)).toBe(true);
278
- expect(await hasPermission(['env:write'], user, envStagingId)).toBe(false);
279
- expect(await hasPermission(['env:write'], user, null)).toBe(false);
280
- });
177
+ it('should use the filter from userPermission when overriding a permission', async () => {
178
+ // Le rôle donne 'order.edit' avec le filtre { status: 'pending' }
179
+ let result = await hasPermission('order.edit', testUserWithRole);
180
+ expect(result).toEqual({ status: 'pending' });
181
+
182
+ // On ajoute une exception qui accorde la même permission mais avec un filtre différent
183
+ // Utiliser insertData pour s'assurer que le cache est invalidé
184
+ await insertData('userPermission', {
185
+ user: testUserWithRole._id.toString(),
186
+ permission: permWithSimpleFilter.toString(), // La permission 'order.edit'
187
+ isGranted: true,
188
+ filter: { "assignedTo": "{user._id}" }, // Le nouveau filtre
189
+ }, {}, parentUser, true, true, false);
190
+
191
+ // La fonction doit maintenant retourner le filtre de l'exception
192
+ result = await hasPermission('order.edit', testUserWithRole);
193
+ expect(result).toEqual({ assignedTo: testUserWithRole._id.toString() });
194
+ });
281
195
 
282
- it('should revoke a base permission globally if exception has no env', async () => {
283
- await coll.insertOne({
284
- _model: 'userPermission', user: user._id, permission: permRead, isGranted: false
285
- });
286
- // La permission de lecture (du rôle) doit être révoquée partout
287
- expect(await hasPermission(['env:read'], user, envProdId)).toBe(false);
288
- expect(await hasPermission(['env:read'], user, envStagingId)).toBe(false);
289
- expect(await hasPermission(['env:read'], user, null)).toBe(false);
290
- });
196
+ it('should fallback to base permission filter if userPermission has no filter', async () => {
197
+ // On accorde 'document.edit' (qui a un filtre { createdBy: '{user._id}' })
198
+ // via une exception qui n'a PAS de filtre personnalisé.
199
+ await collection.insertOne({
200
+ _model: 'userPermission',
201
+ user: testUser._id.toString(),
202
+ permission: permWithUserFilter.toString(), // La permission 'document.edit'
203
+ isGranted: true,
204
+ // Pas de champ 'filter' ici
205
+ _user: parentUser.username
206
+ });
207
+
208
+ // La fonction doit retourner le filtre de la permission de base.
209
+ const result = await hasPermission('document.edit', testUser);
210
+ expect(result).toEqual({ createdBy: testUser._id.toString() });
211
+ });
291
212
 
292
- it('should revoke a base permission only in the specified environment', async () => {
293
- await coll.insertOne({
294
- _model: 'userPermission', user: user._id, permission: permRead, isGranted: false, env: envStagingId
295
- });
296
- // La permission de lecture ne doit être révoquée qu'en 'staging'
297
- expect(await hasPermission(['env:read'], user, envProdId)).toBe(true);
298
- expect(await hasPermission(['env:read'], user, envStagingId)).toBe(false);
299
- expect(await hasPermission(['env:read'], user, null)).toBe(true);
300
- });
213
+ it('should handle non-local users correctly', async () => {
214
+ let result = await hasPermission('product.view', systemUser);
215
+ expect(result).toBe(true);
301
216
 
302
- it('should prioritize specific env revocation over a global grant', async () => {
303
- await coll.insertMany([
304
- // On accorde 'env:write' partout
305
- { _model: 'userPermission', user: user._id, permission: permWrite, isGranted: true },
306
- // Mais on le révoque spécifiquement pour 'staging'
307
- { _model: 'userPermission', user: user._id, permission: permWrite, isGranted: false, env: envStagingId }
308
- ]);
309
-
310
- expect(await hasPermission(['env:write'], user, envProdId)).toBe(true);
311
- expect(await hasPermission(['env:write'], user, envStagingId)).toBe(false);
312
- expect(await hasPermission(['env:write'], user, null)).toBe(true);
313
- });
217
+ result = await hasPermission('order.edit', systemUser);
218
+ expect(result).toBe(false);
219
+ });
220
+
221
+ it('should return false and log an error if an exception occurs', async ({skip}) => {
222
+ // Pour simuler une erreur, on peut essayer de requêter avec un utilisateur invalide
223
+ // ou mocker une fonction interne pour qu'elle échoue.
224
+ const getCollectionForUserSpy = vi.spyOn(await import('../src/modules/mongodb.js'), 'getCollectionForUser');
225
+ getCollectionForUserSpy.mockRejectedValueOnce(new Error("DB connection failed"));
226
+ const errorSpy = vi.spyOn(logger, 'error');
227
+
228
+ const result = await hasPermission('product.view', testUserWithRole);
229
+
230
+ expect(result).toBe(false);
231
+ expect(errorSpy).toHaveBeenCalledWith("Erreur lors de la vérification des permissions :", expect.any(Error));
232
+
233
+ getCollectionForUserSpy.mockRestore();
234
+ errorSpy.mockRestore();
314
235
  });
315
236
  });
@@ -207,6 +207,14 @@ describe('Intégration des Workflows - triggerWorkflows', () => {
207
207
  expect(workflowRun.contextData.triggerData._id.toString()).toBe(insertResult.insertedIds[0].toString());
208
208
  expect(workflowRun.contextData.triggerData.projectName).toBe('New Corp Website');
209
209
 
210
+ // --- Vérification du champ 'history' ---
211
+ expect(workflowRun.history).toBeDefined();
212
+ expect(Array.isArray(workflowRun.history)).toBe(true);
213
+ expect(workflowRun.history.length).toBeGreaterThan(0);
214
+ const firstStepHistory = workflowRun.history[0];
215
+ expect(firstStepHistory.stepId).toBe(testStep._id.toString());
216
+ expect(firstStepHistory.status).toBe('success');
217
+
210
218
  });
211
219
 
212
220
  it('ne devrait PAS créer de workflowRun si le trigger est inactif', async () => {