data-primals-engine 1.0.8 → 1.0.9

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,15 @@
1
+ import { initEngine } from '../src/setenv.js';
2
+ import { Config } from '../src/config.js';
3
+ import {getCollection} from "../src/modules/mongodb.js";
4
+
5
+ export default async function () {
6
+
7
+ // --- Configuration initiale ---
8
+ Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
9
+
10
+ console.log('\n--- Global Setup: Initializing engine and database for all tests ---');
11
+ // The initEngine function is already a singleton, so this is safe.
12
+
13
+ // Initialisez et exportez les collections
14
+ console.log('--- Global Setup: Complete. Engine is running. ---');
15
+ }
@@ -0,0 +1,8 @@
1
+
2
+ import { stopEngine } from '../src/setenv.js';
3
+
4
+ export default async function () {
5
+ console.log('\n--- Global Teardown: Stopping engine and database ---');
6
+ await stopEngine();
7
+ console.log('--- Global Teardown: Cleanup complete. ---');
8
+ }
@@ -0,0 +1,200 @@
1
+ import {expect, describe, it, beforeEach, beforeAll, afterAll, vi} from 'vitest';
2
+
3
+ // --- Importations des modules de votre application ---
4
+ import {
5
+ createModel,
6
+ insertData,
7
+ exportData,
8
+ importData
9
+ } from 'data-primals-engine/modules/data';
10
+
11
+ import {
12
+ modelsCollection as getAppModelsCollection,
13
+ getCollectionForUser as getAppUserCollection,
14
+ } from 'data-primals-engine/modules/mongodb';
15
+ import {sleep} from "data-primals-engine/core";
16
+ import fs from "node:fs";
17
+ import {getUniquePort, initEngine} from "../src/setenv.js";
18
+ import {Config} from "../src/index.js";
19
+
20
+ // --- Données Mock ---
21
+ const mockUser = {
22
+ username: 'testuserImpex',
23
+ _user: 'testuserImpex',
24
+ userPlan: 'premium',
25
+ email: 'testImpex@example.com'
26
+ };
27
+
28
+ const impexTestModel = {
29
+ name: 'impex_products',
30
+ description: 'test',
31
+ _user: mockUser.username,
32
+ fields: [
33
+ { name: 'name', type: 'string', required: true },
34
+ { name: 'sku', type: 'string', unique: true },
35
+ { name: 'price', type: 'number', required: true },
36
+ { name: 'inStock', type: 'boolean', default: true },
37
+ ],
38
+ };
39
+
40
+ // --- Setup de l'environnement de test ---
41
+ let mongod;
42
+ let testDbUri;
43
+ const testDbName = 'testIntegrationDbHO_Impex';
44
+ let testModelsColInstance;
45
+ let testDatasColInstance;
46
+ let engineInstance;
47
+ const port = getUniquePort(); // Port unique pour cette suite de tests
48
+ function blobToFile(theBlob, fileName){
49
+ //A Blob() is almost a File() - it's just missing the two properties below which we will add
50
+ theBlob.lastModifiedDate = new Date();
51
+ theBlob.name = fileName;
52
+ return theBlob;
53
+ }
54
+
55
+ beforeAll(async () =>{
56
+ Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
57
+ await initEngine();
58
+ })
59
+ // --- Début des tests ---
60
+ describe('Intégration des fonctions d\'Import/Export', () => {
61
+
62
+ // Préparation avant chaque test du bloc
63
+ beforeEach(async () => {
64
+ testModelsColInstance = getAppModelsCollection;
65
+ testDatasColInstance = getAppUserCollection(mockUser);
66
+
67
+ // Nettoyage complet pour un état propre
68
+ await testDatasColInstance.deleteMany({ _user: "testuserImpex"});
69
+
70
+ if( await testModelsColInstance.find({ name: impexTestModel.name, _user: mockUser.username }).count() === 0) {
71
+ await testModelsColInstance.insertOne(impexTestModel);
72
+ }
73
+ // Insérer des données de base pour les tests d'exportation
74
+ await insertData(impexTestModel.name, [
75
+ { name: 'Produit A', sku: 'SKU-A', price: 10.50, inStock: true },
76
+ { name: 'Produit B', sku: 'SKU-B', price: 25.00, inStock: false },
77
+ { name: 'Produit C', sku: 'SKU-C', price: 99.99, inStock: true },
78
+ ], {}, mockUser, false);
79
+ });
80
+
81
+ describe('Export de données', () => {
82
+ it('devrait exporter les données en format JSON', async () => {
83
+ const res= await exportData({
84
+ models: [impexTestModel.name],
85
+ depth: 1,
86
+ }, mockUser);
87
+
88
+ expect(res.success).toBeTruthy();
89
+
90
+ const data = res.data[impexTestModel.name];
91
+ expect(data).toBeInstanceOf(Array);
92
+ expect(data).toHaveLength(3);
93
+ expect(data[0]).toMatchObject({ name: 'Produit A', sku: 'SKU-A', price: 10.50 });
94
+ // Les champs système (_id, _model, _user, _hash) ne devraient pas être exportés par défaut
95
+ expect(data[0]).not.toHaveProperty('_model');
96
+ });
97
+
98
+ it('devrait lever une exception si le modèle n\'existe pas', async () => {
99
+ // La fonction devrait rejeter la promesse ou lancer une erreur
100
+ await expect((await exportData('model_inexistant', 'json', mockUser)).success).toBeFalsy();
101
+ });
102
+ });
103
+
104
+ describe('Import de données', () => {
105
+ it('devrait importer des données depuis une chaîne JSON', async () => {
106
+ const jsonDataToImport = [
107
+ { name: 'Produit D', sku: 'SKU-D', price: 1.00, inStock: true },
108
+ { name: 'Produit E', sku: 'SKU-E', price: 2.00 } // inStock utilisera la valeur par défaut
109
+ ];
110
+ const jsonString = JSON.stringify(jsonDataToImport);
111
+
112
+ // Exécution de la fonction d'import (hypothétique)x
113
+ var blob = new Blob([jsonString], {type: "application/json"});
114
+ fs.writeFileSync('test.json', jsonString);
115
+ blob.path = 'test.json';
116
+ blob.originalFilename = 'test.json';
117
+ const result = await importData({model: impexTestModel.name}, {file: blobToFile(blob,"test.json")}, mockUser);
118
+
119
+ // Vérifications du résultat de l'opération
120
+ expect(result.success).toBe(true);
121
+ expect(result.jobId).not.toBeNull();
122
+
123
+ await sleep(2000);
124
+
125
+ // Vérification directe en base de données
126
+ const importedDocs = await testDatasColInstance.find({
127
+ _model: impexTestModel.name,
128
+ sku: { $in: ['SKU-D', 'SKU-E'] }
129
+ }).toArray();
130
+
131
+ expect(importedDocs).toHaveLength(2);
132
+ const docD = importedDocs.find(d => d.sku === 'SKU-D');
133
+ const docE = importedDocs.find(d => d.sku === 'SKU-E');
134
+
135
+ expect(docD.name).toBe('Produit D');
136
+ expect(docD.inStock).toBe(true); // Vérification de la valeur par défaut
137
+ expect(docE.price).toBe(2.00);
138
+
139
+ }, 5000);
140
+
141
+ it('devrait importer des données depuis une chaîne CSV et convertir les types', async () => {
142
+ const csvStringToImport = `name,sku,price,inStock\nProduit F,SKU-F,3.55,true\nProduit G,SKU-G,4.99,false`;
143
+
144
+ // Exécution de la fonction d'import (hypothétique)x
145
+ var blob = new Blob([csvStringToImport], {type: "text/csv"});
146
+ fs.writeFileSync('test.csv', csvStringToImport);
147
+ blob.path = 'test.csv';
148
+ blob.originalFilename = 'test.csv';
149
+
150
+ // Exécution de la fonction d'import
151
+ const result = await importData({model:impexTestModel.name}, {file: blobToFile(blob,"test.csv")}, mockUser);
152
+
153
+ console.log(result)
154
+ // Vérifications du résultat
155
+ expect(result.success).toBe(true);
156
+
157
+ await sleep(2000);
158
+
159
+ // Vérification en base de données
160
+ const importedDocs = await testDatasColInstance.find({
161
+ _model: impexTestModel.name,
162
+ sku: { $in: ['SKU-F', 'SKU-G'] }
163
+ }).toArray();
164
+
165
+ expect(importedDocs).toHaveLength(2);
166
+ const docF = importedDocs.find(d => d.sku === 'SKU-F');
167
+ const docG = importedDocs.find(d => d.sku === 'SKU-G');
168
+
169
+ // Vérification de la conversion des types
170
+ expect(docF.price).toBe(3.55);
171
+ expect(docF.inStock).toBe(true);
172
+ expect(docG.inStock).toBe(false);
173
+ }, 20000);
174
+
175
+ it('devrait rejeter les lignes invalides et rapporter les erreurs', async () => {
176
+ const csvStringToImport = `name,sku,price,inStock
177
+ Valide H,SKU-H,10,true
178
+ ,SKU-I,20,true
179
+ Valide J,SKU-J,,false
180
+ Valide K,SKU-A,40,true`;
181
+
182
+ // Exécution de la fonction d'import (hypothétique)x
183
+ var blob = new Blob([csvStringToImport], {type: "text/csv"});
184
+ fs.writeFileSync('test.csv', csvStringToImport);
185
+ blob.path = 'test.csv';
186
+ blob.originalFilename = 'test.csv';
187
+
188
+ const result = await importData(impexTestModel.name, {file: blobToFile(blob,"test.csv")}, 'csv', mockUser);
189
+
190
+ console.log(result);
191
+ // Vérifications du résultat
192
+ expect(result.success).toBe(true); // L'opération globale a des erreurs
193
+ expect(result.job.status).toBe('failed'); // L'opération globale a des erreurs
194
+
195
+ // Vérifier que seule les données valides sont en BDD
196
+ const count = await testDatasColInstance.countDocuments({ _model: impexTestModel.name });
197
+ expect(count).toBe(3);
198
+ });
199
+ });
200
+ });
@@ -0,0 +1,314 @@
1
+ // __tests__/workflow.integration.test.js
2
+ import { expect, describe, it, beforeEach,afterEach, beforeAll, afterAll, vi } from 'vitest';
3
+ import { Config } from "data-primals-engine/config";
4
+
5
+ import {insertData, editData, deleteData, patchData} from 'data-primals-engine/modules/data';
6
+ import { modelsCollection as getAppModelsCollection, getCollectionForUser } from 'data-primals-engine/modules/mongodb';
7
+ import * as workflowModule from 'data-primals-engine/modules/workflow';
8
+ import {getUniquePort, initEngine} from "../src/setenv.js";
9
+ import process from "process";
10
+
11
+
12
+ beforeAll(async () =>{
13
+ Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
14
+ await initEngine();
15
+ })
16
+ vi.mock('data-primals-engine/modules/workflow', { spy: true })
17
+ // --- Données Mock pour les tests ---
18
+ const mockUser = {
19
+ username: 'testuserWorkflow',
20
+ _user: 'testuserWorkflow',
21
+ userPlan: 'free',
22
+ email: 'testWorkflow@example.com'
23
+ };
24
+
25
+ // --- Définitions des modèles ---
26
+
27
+ // Le modèle de données qui déclenchera les workflows
28
+ const targetDataModel = {
29
+ name: 'project',
30
+ _user: mockUser.username,
31
+ description: 'Un modèle pour les projets qui déclencheront des workflows',
32
+ fields: [
33
+ { name: 'projectName', type: 'string', required: true },
34
+ { name: 'status', type: 'string', required: true }, // ex: 'new', 'active', 'archived'
35
+ { name: 'budget', type: 'number' }
36
+ ],
37
+ };
38
+
39
+ // Les modèles qui définissent le système de workflow lui-même.
40
+ // Pour les tests, nous devons insérer leurs définitions dans la collection 'models',
41
+ // puis leurs instances (ex: un trigger spécifique) dans la collection 'datas' de l'utilisateur.
42
+ // Les modèles qui définissent le système de workflow lui-me.
43
+ // Pour les tests, nous devons insérer leurs définitions dans la collection 'models',
44
+ // puis leurs instances (ex: un trigger spécifique) dans la collection 'datas' de l'utilisateur.
45
+ const workflowMetaModels = [
46
+ {
47
+ name: 'workflow',
48
+ _user: mockUser.username,
49
+ description:'',
50
+ fields: [
51
+ { name: 'name', type: 'string' },
52
+ { name: 'startStep', type: 'relation', relation: 'workflowStep' }
53
+ ]
54
+ },
55
+ {
56
+ name: 'workflowStep',
57
+ _user: mockUser.username,
58
+ description:'',
59
+ fields: [
60
+ { name: 'name', type: 'string' },
61
+ { name: 'onSuccessStep', type: 'relation', relation: 'workflowStep' },
62
+ { name: 'onFailureStep', type: 'relation', relation: 'workflowStep' },
63
+ { name: 'isTerminal', type: 'boolean' },
64
+ { name: 'actions', type: 'array', itemsType: 'relation', relation: 'workflowAction' },
65
+ { name: 'conditions', type: 'object' }
66
+ ]
67
+ },
68
+ {
69
+ name: 'workflowTrigger',
70
+ _user: mockUser.username,
71
+ description:'',
72
+ fields: [
73
+ { name: 'name', type: 'string' },
74
+ { name: 'targetModel', type: 'model' },
75
+ { name: 'onEvent', type: 'enum', items: ['DataAdded', 'DataEdited', 'DataDeleted', 'ModelAdded', 'ModelEdited', 'ModelDeleted'] },
76
+ { name: 'isActive', type: 'boolean' },
77
+ { name: 'dataFilter', type: 'object' },
78
+ { name: 'workflow', type: 'relation', relation: 'workflow' },
79
+ { name: 'cronExpression', type: 'cronSchedule' },
80
+ { name: 'lockDurationMinutes', type: 'number' }
81
+ ]
82
+ },
83
+ {
84
+ name: 'workflowRun',
85
+ _user: mockUser.username,
86
+ description:'',
87
+ fields: [
88
+ { name: 'status', type: 'enum', items: ['pending', 'running', 'completed', 'failed', 'cancelled'] },
89
+ { name: 'workflow', type: 'relation', relation: 'workflow' },
90
+ { name: 'contextData', type: 'object' },
91
+ { name: 'currentStep', type: 'relation', relation: 'workflowStep' },
92
+ { name: 'error', type: 'string' },
93
+ { name: 'startedAt', type: 'datetime' },
94
+ { name: 'completedAt', type: 'datetime' },
95
+ { name: 'stepExecutionsCount', type: 'object' }
96
+ ]
97
+ },
98
+ {
99
+ name: 'workflowAction',
100
+ _user: mockUser.username,
101
+ description:'',
102
+ fields: [
103
+ // Common
104
+ { name: 'name', type: 'string' },
105
+ { name: 'type', type: 'enum', items: ['Webhook', 'CreateData', 'UpdateData', 'DeleteData', 'GenerateAIContent', 'SendEmail'] },
106
+ // Webhook
107
+ { name: 'url', type: 'url' },
108
+ { name: 'method', type: 'enum', items: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] },
109
+ { name: 'headers', type: 'code', language: 'json' },
110
+ { name: 'body', type: 'code', language: 'json' },
111
+ // Create/Update/Delete
112
+ { name: 'targetModel', type: 'model' },
113
+ { name: 'dataToCreate', type: 'code', language: 'json' },
114
+ { name: 'targetSelector', type: 'code', language: 'json' },
115
+ { name: 'fieldsToUpdate', type: 'code', language: 'json' },
116
+ { name: 'updateMultiple', type: 'boolean' },
117
+ { name: 'deleteMultiple', type: 'boolean' },
118
+ // GenerateAIContent
119
+ { name: 'aiProvider', type: 'enum', items: ['OpenAI', 'GoogleGemini'] },
120
+ { name: 'aiModel', type: 'string' },
121
+ { name: 'prompt', type: 'richtext' },
122
+ // SendEmail
123
+ { name: 'emailRecipients', type: 'array', itemsType: 'string' },
124
+ { name: 'emailSubject', type: 'string' },
125
+ { name: 'emailContent', type: 'richtext' }
126
+ ]
127
+ }
128
+ ];
129
+
130
+
131
+ let testModelsColInstance;
132
+ let testDatasColInstance;
133
+ let engineInstance;
134
+
135
+ const port = process.env.PORT || getUniquePort(); // Port différent
136
+
137
+ // On "espionne" `c` pour vérifier qu'elle est appelée sans l'exécuter réellement.
138
+ // Cela nous permet de tester uniquement la logique de déclenchement.
139
+ const processWorkflowRunSpy = vi.spyOn(workflowModule, 'processWorkflowRun');
140
+
141
+ beforeEach(async () => {
142
+ testModelsColInstance = getAppModelsCollection;
143
+ testDatasColInstance = getCollectionForUser(mockUser);
144
+ // Nettoyer les données avant chaque test
145
+ await testDatasColInstance.deleteMany({_user: "testuserWorkflow"});
146
+
147
+ // Réinitialiser l'espion
148
+ processWorkflowRunSpy.mockClear();
149
+
150
+ const mods = await testModelsColInstance.find({ $and: [{ _user: mockUser.username}, { $or: [{name: targetDataModel.name}, ...workflowMetaModels.map(m =>({name: m.name}))]}]}).toArray();
151
+ if( mods.length === 0 ) {
152
+ // Insérer les définitions de modèles nécessaires
153
+ await testModelsColInstance.insertMany([
154
+ {...targetDataModel},
155
+ ...workflowMetaModels.map(m => ({...m})) // Copie pour éviter les mutations
156
+ ]);
157
+ }
158
+ console.log({mods})
159
+ });
160
+
161
+ describe('Intégration des Workflows - triggerWorkflows', () => {
162
+
163
+ let testWorkflow;
164
+ let testStep;
165
+
166
+ // Avant chaque test de ce bloc, on crée un workflow et une étape de base
167
+ const initTest = async () => {
168
+ const workflowInsertResult = await insertData('workflow', { name: 'Test Workflow' }, {}, mockUser, false);
169
+ testWorkflow = { _id: workflowInsertResult.insertedIds[0] };
170
+
171
+ const stepInsertResult = await insertData('workflowStep', { name: 'Start Step', isTerminal: true }, {}, mockUser, false);
172
+ testStep = { _id: stepInsertResult.insertedIds[0] };
173
+
174
+ // Lier l'étape au workflow
175
+ await editData('workflow', testWorkflow._id, { startStep: testStep._id.toString() }, {}, mockUser, false);
176
+ };
177
+
178
+ it('devrait créer un workflowRun lors de l\'ajout de données correspondant à un trigger "DataAdded"', async () => {
179
+ await initTest();
180
+ // 1. Arrange: Créer un déclencheur actif pour 'DataAdded' sur le modèle 'project'
181
+ await insertData('workflowTrigger', {
182
+ name: 'Trigger on Project Add',
183
+ targetModel: targetDataModel.name,
184
+ onEvent: 'DataAdded',
185
+ isActive: true,
186
+ workflow: testWorkflow._id.toString()
187
+ }, {}, mockUser, false);
188
+
189
+ // 2. Act: Insérer une donnée qui doit déclencher le workflow
190
+ const projectData = { projectName: 'New Corp Website', status: 'new', budget: 50000 };
191
+ const insertResult = await insertData(targetDataModel.name, projectData, {}, mockUser, true, true);
192
+
193
+ expect(insertResult.success).toBe(true);
194
+
195
+ const workflowRun = await testDatasColInstance.findOne({ _model: 'workflowRun' });
196
+
197
+ expect(workflowRun).not.toBeNull();
198
+ expect(workflowRun.status).toBe('completed');
199
+ expect(workflowRun.workflow.toString()).toBe(testWorkflow._id.toString());
200
+ expect(workflowRun.contextData.triggerData._id.toString()).toBe(insertResult.insertedIds[0].toString());
201
+ expect(workflowRun.contextData.triggerData.projectName).toBe('New Corp Website');
202
+
203
+ });
204
+
205
+ it('ne devrait PAS créer de workflowRun si le trigger est inactif', async () => {
206
+ await initTest();
207
+ // 1. Arrange: Créer un déclencheur INACTIF
208
+ await insertData('workflowTrigger', {
209
+ name: 'Inactive Trigger',
210
+ targetModel: targetDataModel.name,
211
+ onEvent: 'DataAdded',
212
+ isActive: false, // Inactif
213
+ workflow: testWorkflow._id.toString()
214
+ }, {}, mockUser, false);
215
+
216
+ // 2. Act: Insérer des données
217
+ await insertData(targetDataModel.name, { projectName: 'Secret Project', status: 'new' }, {}, mockUser, true);
218
+
219
+ // 3. Assert: Aucun `workflowRun` ne doit être créé
220
+ const workflowRunCount = await testDatasColInstance.countDocuments({ _model: 'workflowRun' });
221
+ expect(workflowRunCount).toBe(0);
222
+ });
223
+
224
+ it('devrait créer un workflowRun si le dataFilter est satisfait', async () => {
225
+ await initTest();
226
+ // 1. Arrange: Créer un déclencheur avec un `dataFilter`
227
+ await insertData('workflowTrigger', {
228
+ name: 'Trigger for Active Projects',
229
+ targetModel: targetDataModel.name,
230
+ onEvent: 'DataAdded',
231
+ isActive: true,
232
+ dataFilter: { $eq: ['$status', 'active'] }, // Filtre pour status = 'active'
233
+ workflow: testWorkflow._id.toString()
234
+ }, {}, mockUser, false);
235
+
236
+ // 2. Act: Insérer des données qui correspondent au filtre
237
+ await insertData(targetDataModel.name, { projectName: 'Go-Live Project', status: 'active' }, {}, mockUser, true, true);
238
+
239
+ // 3. Assert: Un `workflowRun` doit être créé
240
+ const workflowRunCount = await testDatasColInstance.countDocuments({ _model: 'workflowRun' });
241
+ expect(workflowRunCount).toBe(1);
242
+ });
243
+
244
+ it('ne devrait PAS créer de workflowRun si le dataFilter n\'est PAS satisfait', async () => {
245
+ await initTest();
246
+ // 1. Arrange: Créer un déclencheur avec un `dataFilter`
247
+ await insertData('workflowTrigger', {
248
+ name: 'Trigger for Archived Projects',
249
+ targetModel: targetDataModel.name,
250
+ onEvent: 'DataAdded',
251
+ isActive: true,
252
+ dataFilter: { $eq: ['$status', 'archived'] }, // Filtre pour status = 'archived'
253
+ workflow: testWorkflow._id.toString()
254
+ }, {}, mockUser, false);
255
+
256
+ // 2. Act: Insérer des données qui NE correspondent PAS au filtre
257
+ await insertData(targetDataModel.name, { projectName: 'Ongoing Project', status: 'active' }, {}, mockUser, true, true);
258
+
259
+ // 3. Assert: Aucun `workflowRun` ne doit être créé
260
+ const workflowRunCount = await testDatasColInstance.countDocuments({ _model: 'workflowRun' });
261
+ expect(workflowRunCount).toBe(0);
262
+ });
263
+
264
+ it('devrait déclencher un workflow "DataEdited" lors de la modification de données', async () => {
265
+ await initTest();
266
+ // 1. Arrange: Insérer une donnée initiale et un déclencheur pour 'DataEdited'
267
+ const insertResult = await insertData(targetDataModel.name, { projectName: 'To Edit', status: 'initial' }, {}, mockUser, false);
268
+ const projectId = insertResult.insertedIds[0];
269
+
270
+ await insertData('workflowTrigger', {
271
+ name: 'Trigger on Project Edit',
272
+ targetModel: targetDataModel.name,
273
+ onEvent: 'DataEdited',
274
+ isActive: true,
275
+ workflow: testWorkflow._id.toString()
276
+ }, {}, mockUser, false);
277
+
278
+ // 2. Act: Modifier la donnée
279
+ const editResult = await patchData(targetDataModel.name, projectId, { status: 'edited' }, {}, mockUser, true, true);
280
+ expect(editResult.success).toBe(true);
281
+
282
+ // 2. Act: Modifier la donnée
283
+ const editResult2 = await editData(targetDataModel.name, projectId, { projectName: 'test', status: 'edited' }, {}, mockUser, true, true);
284
+ expect(editResult2.success).toBe(true);
285
+
286
+ const workflows = await testDatasColInstance.countDocuments({ _model: 'workflowRun' });
287
+ expect(workflows).toBe(2);
288
+ });
289
+
290
+ it('devrait déclencher un workflow "DataDeleted" lors de la suppression de données', async () => {
291
+ await initTest();
292
+ // 1. Arrange: Insérer une donnée et un déclencheur pour 'DataDeleted'
293
+ const insertResult = await insertData(targetDataModel.name, { projectName: 'To Delete', status: 'temp' }, {}, mockUser, false);
294
+ const projectId = insertResult.insertedIds[0];
295
+
296
+ await insertData('workflowTrigger', {
297
+ name: 'Trigger on Project Delete',
298
+ targetModel: targetDataModel.name,
299
+ onEvent: 'DataDeleted',
300
+ isActive: true,
301
+ workflow: testWorkflow._id.toString()
302
+ }, {}, mockUser, false);
303
+
304
+ // 2. Act: Supprimer la donnée
305
+ const deleteResult = await deleteData(targetDataModel.name, [projectId], {}, mockUser, true, true);
306
+ expect(deleteResult.success).toBe(true);
307
+
308
+ // 3. Assert: Un `workflowRun` doit être créé
309
+ const workflowRun = await testDatasColInstance.findOne({ _model: 'workflowRun' });
310
+ expect(workflowRun).not.toBeNull();
311
+ // Le `triggerData` est le document *avant* sa suppression
312
+ expect(workflowRun.contextData.triggerData.projectName).toBe('To Delete');
313
+ });
314
+ });