data-primals-engine 1.2.3 → 1.2.4

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 (49) hide show
  1. package/CONTRIBUTING.md +91 -0
  2. package/README.md +33 -17
  3. package/client/src/App.jsx +0 -5
  4. package/client/src/ConditionBuilder.scss +34 -1
  5. package/client/src/ConditionBuilder2.jsx +179 -53
  6. package/client/src/ContentView.jsx +0 -3
  7. package/client/src/CronBuilder.jsx +0 -1
  8. package/client/src/CronPartBuilder.jsx +0 -2
  9. package/client/src/DashboardView.jsx +0 -5
  10. package/client/src/DataEditor.jsx +8 -10
  11. package/client/src/DataLayout.jsx +0 -1
  12. package/client/src/DataTable.jsx +1 -3
  13. package/client/src/Field.jsx +0 -5
  14. package/client/src/FlexBuilder.jsx +1 -1
  15. package/client/src/ModelCreatorField.jsx +1 -5
  16. package/client/src/RTE.jsx +1 -6
  17. package/client/src/RTETrans.jsx +0 -2
  18. package/client/src/RelationField.jsx +1 -1
  19. package/client/src/RelationValue.jsx +1 -2
  20. package/client/src/TourSpotlight.jsx +0 -2
  21. package/client/src/filter.js +87 -0
  22. package/client/src/hooks/data.js +1 -3
  23. package/client/src/hooks/useTutorials.jsx +0 -1
  24. package/package.json +3 -3
  25. package/server.js +2 -2
  26. package/src/email.js +2 -2
  27. package/src/engine.js +59 -20
  28. package/src/index.js +1 -1
  29. package/src/middlewares/middleware-mongodb.js +0 -1
  30. package/src/modules/assistant.js +1 -3
  31. package/src/modules/bucket.js +3 -4
  32. package/src/modules/{data.js → data/data.js} +34 -51
  33. package/src/modules/data/index.js +1 -0
  34. package/src/modules/file.js +1 -1
  35. package/src/modules/mongodb.js +0 -1
  36. package/src/modules/user.js +1 -1
  37. package/src/modules/workflow.js +38 -38
  38. package/src/packs.js +4 -1
  39. package/test/data.backup.integration.test.js +4 -5
  40. package/test/data.integration.test.js +2 -6
  41. package/test/events.test.js +1 -1
  42. package/test/file.test.js +1 -4
  43. package/test/import_export.integration.test.js +20 -13
  44. package/test/model.integration.test.js +8 -10
  45. package/test/user.test.js +2 -2
  46. package/test/vm.test.js +1 -1
  47. package/test/workflow.integration.test.js +17 -14
  48. package/test/workflow.robustness.test.js +15 -10
  49. package/src/modules/test +0 -147
@@ -1,4 +1,4 @@
1
- import {expect, describe, it, beforeEach, beforeAll, afterAll, vi} from 'vitest';
1
+ import {expect, describe, it, beforeEach, beforeAll, afterAll, vi, afterEach} from 'vitest';
2
2
 
3
3
  // --- Importations des modules de votre application ---
4
4
  import {
@@ -10,12 +10,11 @@ import {
10
10
  import {
11
11
  modelsCollection as getAppModelsCollection,
12
12
  getCollectionForUser as getAppUserCollection, getCollectionForUser
13
- } from 'data-primals-engine/modules/mongodb';
13
+ } from '../src/modules/mongodb.js';
14
14
  import {sleep} from "data-primals-engine/core";
15
15
  import fs from "node:fs";
16
- import {getUniquePort, initEngine} from "../src/setenv.js";
16
+ import {initEngine} from "../src/setenv.js";
17
17
  import {Config} from "../src/index.js";
18
- import {ObjectId} from "mongodb";
19
18
 
20
19
  // --- Données Mock ---
21
20
  const mockUser = {
@@ -38,13 +37,9 @@ const impexTestModel = {
38
37
  };
39
38
 
40
39
  // --- Setup de l'environnement de test ---
41
- let mongod;
42
- let testDbUri;
43
- const testDbName = 'testIntegrationDbHO_Impex';
44
40
  let testModelsColInstance;
45
41
  let testDatasColInstance;
46
- let engineInstance;
47
- const port = getUniquePort(); // Port unique pour cette suite de tests
42
+
48
43
  function blobToFile(theBlob, fileName){
49
44
  //A Blob() is almost a File() - it's just missing the two properties below which we will add
50
45
  theBlob.lastModifiedDate = new Date();
@@ -56,6 +51,17 @@ beforeAll(async () =>{
56
51
  Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
57
52
  await initEngine();
58
53
  })
54
+
55
+ beforeEach(async() =>{
56
+ // tell vitest we use mocked time
57
+ vi.useFakeTimers({ shouldAdvanceTime: true })
58
+ })
59
+
60
+ afterEach(() => {
61
+ vi.runOnlyPendingTimers();
62
+ // restoring date after each test run
63
+ vi.useRealTimers()
64
+ })
59
65
  afterAll(async () => {
60
66
  const coll = await getCollectionForUser(mockUser);
61
67
  await coll.drop();
@@ -84,6 +90,7 @@ describe('Intégration des fonctions d\'Import/Export', () => {
84
90
 
85
91
  describe('Export de données', () => {
86
92
  it('devrait exporter les données en format JSON', async () => {
93
+
87
94
  const res= await exportData({
88
95
  models: [impexTestModel.name],
89
96
  depth: 1
@@ -107,6 +114,7 @@ describe('Intégration des fonctions d\'Import/Export', () => {
107
114
 
108
115
  describe('Import de données', () => {
109
116
  it('devrait importer des données depuis une chaîne JSON', async () => {
117
+
110
118
  const jsonDataToImport = [
111
119
  { name: 'Produit D', sku: 'SKU-D', price: 1.00, inStock: true },
112
120
  { name: 'Produit E', sku: 'SKU-E', price: 2.00 } // inStock utilisera la valeur par défaut
@@ -124,7 +132,7 @@ describe('Intégration des fonctions d\'Import/Export', () => {
124
132
  expect(result.success).toBe(true);
125
133
  expect(result.job.jobId).not.toBeNull();
126
134
 
127
- await sleep(8000)
135
+ await sleep(5000);
128
136
 
129
137
  // Vérification directe en base de données
130
138
  const importedDocs = await testDatasColInstance.find({
@@ -158,7 +166,7 @@ describe('Intégration des fonctions d\'Import/Export', () => {
158
166
  expect(result.success).toBe(true);
159
167
  expect(result.job.jobId).not.toBeNull();
160
168
 
161
- await sleep(8000);
169
+ await sleep(5000);
162
170
  // Vérification en base de données
163
171
  const importedDocs = await testDatasColInstance.find({
164
172
  _model: impexTestModel.name,
@@ -194,8 +202,7 @@ Valide K,SKU-A,40,true`;
194
202
  expect(result.success).toBe(true);
195
203
  expect(result.job.jobId).not.toBeNull();
196
204
 
197
- await sleep(8000);
198
-
205
+ await sleep(5000);
199
206
  // Vérifier que seule les données valides sont en BDD
200
207
  const count = await testDatasColInstance.countDocuments({ _model: impexTestModel.name, sku: 'SKU-H' });
201
208
  expect(count).toBe(1); // Seule la ligne valide 'SKU-H' doit être insérée.
@@ -1,19 +1,17 @@
1
1
  // __tests__/data.integration.test.js
2
2
  import { ObjectId } from 'mongodb';
3
- import {expect, describe, it, beforeEach, afterEach, beforeAll, afterAll, vi} from 'vitest';
3
+ import {expect, describe, it, beforeAll} from 'vitest';
4
4
  import { Config } from '../src/config.js';
5
5
 
6
6
  import {
7
- modelsCollection as getAppModelsCollection,
8
- datasCollection, getCollection // Accès direct pour vérifications
9
- } from 'data-primals-engine/modules/mongodb';
10
- import {generateUniqueName, getUniquePort, initEngine} from "../src/setenv.js";
11
- import {editModel} from "../src/modules/data.js";
12
- import {getCollectionForUser, getUserCollectionName} from "../src/modules/mongodb.js";
7
+ getCollection // Accès direct pour vérifications
8
+ } from '../src/modules/mongodb.js';
9
+ import {generateUniqueName, initEngine} from "../src/setenv.js";
10
+ import {editModel} from "../src/index.js";
11
+ import {getCollectionForUser} from "../src/modules/mongodb.js";
13
12
 
14
13
  let testModelsColInstance;
15
14
  let testModelId;
16
- let lastUser;
17
15
  // Cette fonction va remplacer la logique de votre beforeEach pour la création de contexte
18
16
  async function setupTestContext() {
19
17
 
@@ -24,7 +22,7 @@ async function setupTestContext() {
24
22
  // Créer un utilisateur unique pour ce test
25
23
  const currentTestUser = {
26
24
  username: generateUniqueName('testuserModelIntegration'),
27
- userPlan: 'premium',
25
+ userPlan: 'free',
28
26
  email: generateUniqueName('test') + '@example.com'
29
27
  };
30
28
 
@@ -125,7 +123,7 @@ describe('CRUD on model definitions and integrity tests', () => {
125
123
 
126
124
  describe('editModel unit tests', () => {
127
125
 
128
- it.skip('should create and drop index when field.index is toggled (premium user)', async () => {
126
+ it('should create and drop index when field.index is toggled (premium user)', async () => {
129
127
  // --- SETUP ---
130
128
  const { coll, currentTestUser, comprehensiveTestModelDefinition } = await setupTestContext();
131
129
  const fieldToIndex = 'stringUnique'; // Utiliser un champ qui existe vraiment dans le modèle
package/test/user.test.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { expect, describe, it, beforeEach, beforeAll, afterAll } from 'vitest';
2
2
  import { hasPermission, getUserActivePermissions } from '../src/modules/user.js';
3
- import {initEngine} from "data-primals-engine/setenv";
3
+ import {initEngine} from "../src/setenv";
4
4
  import {generateUniqueName} from "../src/setenv.js";
5
5
  import {
6
6
  getCollectionForUser as getAppUserCollection,
7
7
  modelsCollection as getAppModelsCollection
8
- } from "data-primals-engine/modules/mongodb";
8
+ } from "../src/modules/mongodb.js";
9
9
  import {Config} from "../src/index.js";
10
10
  let permRead, permWrite, permDelete, permManage;
11
11
  let roleEditor;
package/test/vm.test.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {expect, describe, it, beforeEach, afterEach, beforeAll, afterAll, vi, vitest} from 'vitest';
2
2
  import ivm from "isolated-vm";
3
- import {sleep} from "data-primals-engine/core";
3
+ import {sleep} from "../src/core";
4
4
 
5
5
  vi.stubEnv('ENCRYPTION_KEY', '12345678901234567890123456789012');
6
6
 
@@ -1,20 +1,16 @@
1
- // __tests__/workflow.integration.test.js
2
1
  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
- import {getUserCollectionName} from "../src/modules/mongodb.js";
2
+ import { Config } from "../src/config";
11
3
 
4
+ import {insertData, editData, deleteData, patchData} from '../src/index.js';
5
+ import { modelsCollection as getAppModelsCollection, getCollectionForUser } from '../src/modules/mongodb.js';
6
+ import * as workflowModule from '../src/modules/workflow.js';
7
+ import {initEngine} from "../src/setenv.js";
12
8
 
13
9
  beforeAll(async () =>{
14
10
  Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
15
11
  await initEngine();
16
12
  })
17
- vi.mock('data-primals-engine/modules/workflow', { spy: true })
13
+ vi.mock('../src/modules/workflow.js', { spy: true })
18
14
  // --- Données Mock pour les tests ---
19
15
  const mockUser = {
20
16
  username: 'testuserWorkflow',
@@ -139,8 +135,6 @@ const processWorkflowRunSpy = vi.spyOn(workflowModule, 'processWorkflowRun');
139
135
  beforeEach(async () => {
140
136
  testModelsColInstance = getAppModelsCollection;
141
137
  testDatasColInstance = await getCollectionForUser(mockUser);
142
- // Nettoyer les données avant chaque test
143
- await testDatasColInstance.deleteMany({_user: "testuserWorkflow"});
144
138
 
145
139
  // Réinitialiser l'espion
146
140
  processWorkflowRunSpy.mockClear();
@@ -153,9 +147,15 @@ beforeEach(async () => {
153
147
  ...workflowMetaModels.map(m => ({...m})) // Copie pour éviter les mutations
154
148
  ]);
155
149
  }
156
- console.log({mods})
157
- });
150
+ // tell vitest we use mocked time
151
+ vi.useFakeTimers({ shouldAdvanceTime: true })
152
+ })
158
153
 
154
+ afterEach(() => {
155
+ vi.runOnlyPendingTimers();
156
+ // restoring date after each test run
157
+ vi.useRealTimers()
158
+ })
159
159
  afterAll(async () => {
160
160
  const coll = await getCollectionForUser(mockUser);
161
161
  await coll.drop();
@@ -167,6 +167,9 @@ describe('Intégration des Workflows - triggerWorkflows', () => {
167
167
 
168
168
  // Avant chaque test de ce bloc, on crée un workflow et une étape de base
169
169
  const initTest = async () => {
170
+ // Nettoyer les données avant chaque test
171
+ await testDatasColInstance.deleteMany({_user: "testuserWorkflow"});
172
+
170
173
  const workflowInsertResult = await insertData('workflow', { name: 'Test Workflow' }, {}, mockUser, false);
171
174
  testWorkflow = { _id: workflowInsertResult.insertedIds[0] };
172
175
 
@@ -1,15 +1,14 @@
1
1
  import { ObjectId } from 'mongodb';
2
- import {expect, describe, it, beforeEach, beforeAll, vi, afterAll} from 'vitest';
3
- import { Config } from "data-primals-engine/config";
4
- // --- Importations des modules de l'application ---
5
- import { insertData, editData } from 'data-primals-engine/modules/data';
6
- import { modelsCollection as getAppModelsCollection, getCollectionForUser, getCollection } from 'data-primals-engine/modules/mongodb';
7
- import * as workflowModule from 'data-primals-engine/modules/workflow';
2
+ import {expect, describe, it, beforeEach, beforeAll, vi, afterAll, afterEach} from 'vitest';
3
+ import { Config } from "../src/config";
4
+
5
+ import { insertData, editData } from '../src/index.js';
6
+ import { modelsCollection as getAppModelsCollection, getCollectionForUser, getCollection } from '../src/modules/mongodb.js';
7
+ import * as workflowModule from '../src/modules/workflow.js';
8
8
  import {initEngine} from "../src/setenv.js";
9
9
  import {maxExecutionsByStep} from "../src/constants.js";
10
- import {getUserCollectionName} from "../src/modules/mongodb.js";
11
10
 
12
- vi.mock('data-primals-engine/modules/workflow', { spy: true })
11
+ vi.mock('../src/modules/workflow.js', { spy: true })
13
12
 
14
13
  const mockUser = {
15
14
  username: 'robustnessUser',
@@ -42,12 +41,18 @@ beforeEach(async () => {
42
41
  await testDatasColInstance.deleteMany({ _user: mockUser.username });
43
42
  await getCollection('job_locks').deleteMany({}); // Nettoyer les verrous
44
43
  const mods = await testModelsColInstance.find({ $and: [{_user: mockUser.username}, {$or: [{name: targetDataModel.name}, ...workflowMetaModels.map(m =>({name: m.name}))] }]}).toArray();
45
- console.log({mods})
46
44
  if( mods.length === 0){
47
45
  await testModelsColInstance.insertMany([targetDataModel, ...workflowMetaModels]);
48
46
  }
49
- });
47
+ // tell vitest we use mocked time
48
+ vi.useFakeTimers({ shouldAdvanceTime: true })
49
+ })
50
50
 
51
+ afterEach(() => {
52
+ vi.runOnlyPendingTimers();
53
+ // restoring date after each test run
54
+ vi.useRealTimers()
55
+ })
51
56
  afterAll(async () => {
52
57
  const coll = await getCollectionForUser(mockUser);
53
58
  await coll.drop();
package/src/modules/test DELETED
@@ -1,147 +0,0 @@
1
- // src/modules/file.js
2
- import { getCollection } from "./mongodb.js";
3
- import { uuidv4, getFileExtension } from "../core.js";
4
- import { getUserS3Config, uploadToS3, deleteFromS3 } from "./bucket.js";
5
- import { maxFileSize } from "../constants.js";
6
- import path from "node:path";
7
- import fs from "node:fs";
8
- import { promises as fsPromises } from "node:fs";
9
- import { Logger } from "../gameObject.js";
10
-
11
- let logger;
12
-
13
- const getLogger = () => {
14
- if (!logger) {
15
- logger = new Logger();
16
- }
17
- return logger;
18
- };
19
-
20
- /**
21
- * Ajoute un fichier au système. Le téléverse sur S3 si configuré, sinon le sauvegarde localement.
22
- * @param {object} file - L'objet fichier provenant de express-formidable.
23
- * @param {object} user - L'objet utilisateur.
24
- * @returns {Promise<string>} Le GUID du fichier nouvellement ajouté.
25
- */
26
- export const addFile = async (file, user) => {
27
- if (!file) {
28
- throw new Error("Aucun fichier fourni.");
29
- }
30
- if (file.size > maxFileSize) {
31
- throw new Error(`La taille du fichier dépasse la limite de ${maxFileSize / 1024 / 1024} Mo.`);
32
- }
33
-
34
- const guid = uuidv4();
35
- const extension = getFileExtension(file.name);
36
- const newFilename = `${guid}.${extension}`;
37
- const s3Config = await getUserS3Config(user);
38
-
39
- const fileData = {
40
- _id: guid,
41
- guid: guid,
42
- originalFilename: file.name,
43
- size: file.size,
44
- mimetype: file.type,
45
- createdAt: new Date(),
46
- user: user.username // Bonne pratique : garder une trace de l'uploader
47
- };
48
-
49
- if (s3Config && s3Config.bucketName) { // Correction: bucketName au lieu de bucket
50
- try {
51
- // Correction: Appel manquant à la fonction de téléversement
52
- await uploadToS3(s3Config, file.path, newFilename);
53
-
54
- fileData.storage = 's3';
55
- fileData.filename = newFilename; // Le nom sur S3
56
- getLogger().log(`Fichier ${newFilename} téléversé sur le bucket S3 ${s3Config.bucketName}.`);
57
- } catch (error) {
58
- getLogger().log(`Le téléversement S3 a échoué pour ${file.name}: ${error.message}`, 'error');
59
- throw new Error("Le téléversement S3 a échoué.");
60
- } finally {
61
- // Nettoyer le fichier temporaire uploadé par express-formidable
62
- await fsPromises.unlink(file.path).catch(e => getLogger().log(`Échec de la suppression du fichier temporaire ${file.path}: ${e.message}`, 'error'));
63
- }
64
- } else {
65
- // Sauvegarde locale
66
- const uploadDir = path.join(process.cwd(), "uploads");
67
- if (!fs.existsSync(uploadDir)) {
68
- fs.mkdirSync(uploadDir, { recursive: true });
69
- }
70
- const newPath = path.join(uploadDir, newFilename);
71
-
72
- try {
73
- // express-formidable place déjà le fichier dans un répertoire temporaire. Nous n'avons qu'éplacer.
74
- await fsPromises.rename(file.path, newPath);
75
- fileData.storage = 'local';
76
- fileData.filename = newFilename; // Le nom dans le dossier uploads
77
- fileData.path = newPath; // Le chemin complet pour les fichiers locaux
78
- getLogger().log(`Fichier ${newFilename} sauvegardé localement dans ${newPath}.`);
79
- } catch (error) {
80
- getLogger().log(`Le déplacement du fichier local a échoué pour ${file.name}: ${error.message}`, 'error');
81
- // Essayer de nettoyer le fichier temporaire même si le renommage échoue
82
- await fsPromises.unlink(file.path).catch(e => getLogger().log(`Échec de la suppression du fichier temporaire ${file.path}: ${e.message}`, 'error'));
83
- throw new Error("Le stockage du fichier local a échoué.");
84
- }
85
- }
86
-
87
- const filesCollection = await getCollection("files");
88
- await filesCollection.insertOne(fileData);
89
-
90
- return guid;
91
- };
92
-
93
- /**
94
- * Récupère les métadonnées d'un fichier depuis la base de données.
95
- * @param {string} guid - Le GUID du fichier.
96
- * @returns {Promise<object|null>} L'objet de métadonnées du fichier ou null si non trouvé.
97
- */
98
- export const getFile = async (guid) => {
99
- const filesCollection = await getCollection("files");
100
- return await filesCollection.findOne({ guid });
101
- };
102
-
103
- /**
104
- * Supprime un fichier du système (S3 ou local) et son enregistrement en base de données.
105
- * @param {string} guid - Le GUID du fichier à supprimer.
106
- * @param {object} user - L'objet utilisateur.
107
- * @returns {Promise<void>}
108
- */
109
- export const deleteFile = async (guid, user) => {
110
- const fileData = await getFile(guid);
111
- if (!fileData) {
112
- getLogger().log(`Tentative de suppression d'un fichier inexistant avec le GUID : ${guid}`, 'warn');
113
- return;
114
- }
115
-
116
- if (fileData.storage === 's3') {
117
- const s3Config = await getUserS3Config(user);
118
- if (s3Config && s3Config.bucketName) { // Correction: bucketName au lieu de bucket
119
- try {
120
- await deleteFromS3(s3Config, fileData.filename);
121
- getLogger().log(`Fichier ${fileData.filename} supprimé du bucket S3 ${s3Config.bucketName}.`);
122
- } catch (error) {
123
- getLogger().log(`La suppression S3 a échoué pour ${fileData.filename}: ${error.message}`, 'error');
124
- throw new Error("La suppression S3 a échoué.");
125
- }
126
- } else {
127
- getLogger().log(`Configuration S3 non trouvée pour l'utilisateur, impossible de supprimer le fichier ${fileData.filename} de S3.`, 'error');
128
- throw new Error("Configuration S3 non trouvée, impossible de supprimer le fichier.");
129
- }
130
- } else if (fileData.storage === 'local') {
131
- try {
132
- if (fileData.path && fs.existsSync(fileData.path)) {
133
- await fsPromises.unlink(fileData.path);
134
- getLogger().log(`Fichier local ${fileData.path} supprimé.`);
135
- } else {
136
- getLogger().log(`Fichier local non trouvé au chemin ${fileData.path}, mais suppression de l'enregistrement en BDD.`, 'warn');
137
- }
138
- } catch (error) {
139
- getLogger().log(`La suppression du fichier local a échoué pour ${fileData.path}: ${error.message}`, 'error');
140
- throw new Error("La suppression du fichier local a échoué.");
141
- }
142
- }
143
-
144
- const filesCollection = await getCollection("files");
145
- await filesCollection.deleteOne({ guid });
146
- getLogger().log(`Enregistrement du fichier ${guid} supprimé de la base de données.`);
147
- };