data-primals-engine 1.0.10 → 1.1.0
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.
- package/client/README.md +8 -0
- package/client/index.html +31 -0
- package/client/index.js +25 -0
- package/client/package-lock.json +11664 -0
- package/client/package.json +70 -0
- package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
- package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
- package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
- package/client/public/doc/API.postman_collection.json +214 -0
- package/client/public/github.svg +1 -0
- package/client/public/profilCompany.jpg +0 -0
- package/client/public/profilDeveloper.jpg +0 -0
- package/client/public/profilEngineer.jpg +0 -0
- package/client/public/profilPersonal.jpg +0 -0
- package/client/public/profils .psd +0 -0
- package/client/public/react.svg +9 -0
- package/client/src/APIInfo.jsx +328 -0
- package/client/src/AddWidgetTypeModal.jsx +44 -0
- package/client/src/AddWidgetTypeModal.scss +87 -0
- package/client/src/App.css +42 -0
- package/client/src/App.jsx +586 -0
- package/client/src/App.scss +1466 -0
- package/client/src/AssistantChat.jsx +280 -0
- package/client/src/AssistantChat.scss +229 -0
- package/client/src/Button.jsx +12 -0
- package/client/src/Button.scss +197 -0
- package/client/src/CalculationBuilder.jsx +338 -0
- package/client/src/CalculationStepRow.jsx +199 -0
- package/client/src/CalendarConfigModal.jsx +49 -0
- package/client/src/ChartConfigModal.jsx +402 -0
- package/client/src/ConditionBuilder.jsx +622 -0
- package/client/src/ConditionBuilder.scss +672 -0
- package/client/src/ConditionBuilder2.jsx +757 -0
- package/client/src/ContentView.jsx +274 -0
- package/client/src/CronBuilder.jsx +126 -0
- package/client/src/CronBuilder.scss +128 -0
- package/client/src/CronPartBuilder.jsx +150 -0
- package/client/src/Dashboard.jsx +210 -0
- package/client/src/Dashboard.scss +445 -0
- package/client/src/DashboardChart.jsx +362 -0
- package/client/src/DashboardFlexViewItem.jsx +129 -0
- package/client/src/DashboardView.jsx +554 -0
- package/client/src/DataChart.jsx +349 -0
- package/client/src/DataEditor.jsx +556 -0
- package/client/src/DataLayout.jsx +709 -0
- package/client/src/DataTable.jsx +996 -0
- package/client/src/DataTable.scss +73 -0
- package/client/src/Dialog.jsx +90 -0
- package/client/src/Dialog.scss +117 -0
- package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
- package/client/src/DocumentationPageLayout.jsx +93 -0
- package/client/src/DocumentationPageLayout.scss +262 -0
- package/client/src/Draggable.jsx +222 -0
- package/client/src/Draggable.scss +49 -0
- package/client/src/ExportDialog.jsx +172 -0
- package/client/src/Field.jsx +1558 -0
- package/client/src/FlexBuilder.jsx +269 -0
- package/client/src/FlexBuilder.scss +226 -0
- package/client/src/FlexBuilderControls.jsx +170 -0
- package/client/src/FlexBuilderModal.jsx +75 -0
- package/client/src/FlexBuilderModal.scss +109 -0
- package/client/src/FlexBuilderPreview.jsx +48 -0
- package/client/src/FlexBuilderUtils.js +84 -0
- package/client/src/FlexDataRenderer.jsx +170 -0
- package/client/src/FlexDataRenderer.scss +80 -0
- package/client/src/FlexNode.jsx +290 -0
- package/client/src/FlexTreeUtils.js +129 -0
- package/client/src/HiddenableCell.jsx +46 -0
- package/client/src/HiddenableCell.scss +36 -0
- package/client/src/KPIDialog.jsx +39 -0
- package/client/src/KPIWidget.jsx +139 -0
- package/client/src/KanbanCard.jsx +92 -0
- package/client/src/KanbanColumn.jsx +29 -0
- package/client/src/KanbanConfigModal.jsx +115 -0
- package/client/src/KanbanView.jsx +113 -0
- package/client/src/KanbanView.scss +107 -0
- package/client/src/MessageRotator.jsx +77 -0
- package/client/src/MessageRotator.scss +14 -0
- package/client/src/ModelCreator.jsx +640 -0
- package/client/src/ModelCreator.scss +154 -0
- package/client/src/ModelCreatorField.jsx +810 -0
- package/client/src/ModelImporter.jsx +120 -0
- package/client/src/ModelList.jsx +205 -0
- package/client/src/Notification.jsx +137 -0
- package/client/src/Notification.scss +126 -0
- package/client/src/NotificationProvider.jsx +73 -0
- package/client/src/PackGallery.jsx +211 -0
- package/client/src/PackGallery.scss +156 -0
- package/client/src/Pagination.jsx +141 -0
- package/client/src/RTE.jsx +415 -0
- package/client/src/RTETrans.jsx +103 -0
- package/client/src/RelationField.jsx +247 -0
- package/client/src/RelationValue.jsx +230 -0
- package/client/src/RestoreConfirmationModal.jsx +183 -0
- package/client/src/RestoreDialog.jsx +134 -0
- package/client/src/RestoreDialog.scss +67 -0
- package/client/src/RichTextEditorModal.jsx +34 -0
- package/client/src/RichTextEditorModal.scss +54 -0
- package/client/src/TourSpotlight.jsx +234 -0
- package/client/src/TourSpotlight.scss +100 -0
- package/client/src/TutorialsMenu.jsx +83 -0
- package/client/src/TutorialsMenu.scss +217 -0
- package/client/src/TutorialsRewardModal.jsx +65 -0
- package/client/src/Utils.jsx +78 -0
- package/client/src/ViewSwitcher.jsx +54 -0
- package/client/src/ViewSwitcher.scss +74 -0
- package/client/src/Webpage.jsx +69 -0
- package/client/src/_variables.scss +21 -0
- package/client/src/assets/react.svg +1 -0
- package/client/src/constants.js +68 -0
- package/client/src/contexts/AuthContext.jsx +28 -0
- package/client/src/contexts/ModelContext.jsx +328 -0
- package/client/src/contexts/UIContext.jsx +52 -0
- package/client/src/core/data.js +35 -0
- package/client/src/entry-client.jsx +15 -0
- package/client/src/entry-server.jsx +13 -0
- package/client/src/filter.js +222 -0
- package/client/src/hooks/data.js +101 -0
- package/client/src/hooks/useDebounce.js +20 -0
- package/client/src/hooks/useDragAndDrop.js +59 -0
- package/client/src/hooks/useLocalStorage.js +100 -0
- package/client/src/hooks/useMount.js +42 -0
- package/client/src/hooks/useTutorials.jsx +227 -0
- package/client/src/hooks/useWindowSize.js +24 -0
- package/client/src/i18n.js +47 -0
- package/client/src/index.css +90 -0
- package/client/src/index.js +10 -0
- package/client/src/main.jsx +11 -0
- package/client/src/translations.js +15940 -0
- package/client/src/tutorials.js +112 -0
- package/client/vite.config.js +30 -0
- package/eslint.config.js +50 -0
- package/package.json +48 -27
- package/server.js +9 -0
- package/src/constants.js +12 -23
- package/src/core.js +9 -8
- package/src/data.js +81 -80
- package/src/defaultModels.js +114 -115
- package/src/email.js +8 -9
- package/src/engine.js +5 -5
- package/src/events.js +1 -2
- package/src/i18n.js +5390 -211
- package/src/middlewares/middleware-mongodb.js +94 -93
- package/src/migrate.js +24 -24
- package/src/modules/assistant.js +55 -55
- package/src/modules/bucket.js +10 -9
- package/src/modules/data.js +387 -357
- package/src/modules/file.js +3 -6
- package/src/modules/mongodb.js +6 -7
- package/src/modules/user.js +24 -47
- package/src/modules/workflow.js +68 -67
- package/src/openai.jobs.js +2 -2
- package/src/packs.js +30 -31
- package/src/providers.js +101 -3
- package/src/setenv.js +1 -1
- package/src/user.js +0 -17
- package/src/workers/crypto-worker.js +21 -20
- package/src/workers/import-export-worker.js +18 -18
- package/test/data.backup.integration.test.js +7 -12
- package/test/data.integration.test.js +27 -25
- package/test/globalSetup.js +2 -2
- package/test/import_export.integration.test.js +6 -6
- package/test/model.integration.test.js +21 -22
- package/test/workflow.integration.test.js +2 -5
- package/test/workflow.robustness.test.js +6 -13
- package/vitest.config.js +2 -2
package/src/providers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {compare} from "bcrypt";
|
|
2
|
+
import {maxTotalPrivateFilesSize} from "./constants.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @class UserProvider
|
|
@@ -6,11 +7,15 @@ import {compare} from "bcrypt";
|
|
|
6
7
|
* C'est une classe de base abstraite, elle n'est pas destinée à être utilisée directement.
|
|
7
8
|
*/
|
|
8
9
|
export class UserProvider {
|
|
10
|
+
|
|
11
|
+
plans = {};
|
|
12
|
+
|
|
9
13
|
constructor(engine) {
|
|
10
14
|
if (this.constructor === UserProvider) {
|
|
11
15
|
throw new Error("Abstract classes can't be instantiated.");
|
|
12
16
|
}
|
|
13
17
|
this.engine = engine;
|
|
18
|
+
this.plans = this.getUserPlans();
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
/**
|
|
@@ -50,6 +55,45 @@ export class UserProvider {
|
|
|
50
55
|
throw new Error("Method 'initiateUser()' must be implemented.");
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Récupère la limite de stockage d'un utilisateur.
|
|
60
|
+
* @param user
|
|
61
|
+
* @returns {Promise<number>}
|
|
62
|
+
*/
|
|
63
|
+
async getUserStorageLimit(user) {
|
|
64
|
+
return maxTotalPrivateFilesSize;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Récupère la fréquence de sauvegarde d'un utilisateur.
|
|
69
|
+
* @param user
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async getBackupFrequency(user){
|
|
73
|
+
return 'daily';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Récupère les middlewares d'un utilisateur.
|
|
78
|
+
* @returns {Promise<*[]>}
|
|
79
|
+
*/
|
|
80
|
+
async getMiddlewares(){
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getUserPlans(){
|
|
85
|
+
return {
|
|
86
|
+
free: {
|
|
87
|
+
requestLimitPerHour: 10000,
|
|
88
|
+
features: ['indexes'] // Feature list : indexes is the only option for now. Use a dedicated collection for this user
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async hasFeature(user, feature) {
|
|
94
|
+
return this.plans[user.userPlan]?.features.some(f => f === feature);
|
|
95
|
+
}
|
|
96
|
+
|
|
53
97
|
// Ajoutez ici d'autres méthodes nécessaires : findUserById, createUser, etc.
|
|
54
98
|
}
|
|
55
99
|
|
|
@@ -58,19 +102,73 @@ export class DefaultUserProvider extends UserProvider {
|
|
|
58
102
|
|
|
59
103
|
users= [{ username: "demo", password: "demo" }];
|
|
60
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Trouve un utilisateur. Gère spécifiquement les utilisateurs de démo.
|
|
107
|
+
*/
|
|
61
108
|
async findUserByUsername(username) {
|
|
109
|
+
// Si le nom d'utilisateur commence par "demo", on le considère comme un utilisateur de démo valide et volatile.
|
|
110
|
+
if (typeof username === 'string' && username.startsWith('demo')) {
|
|
111
|
+
// On retourne un objet utilisateur de démo avec la structure attendue.
|
|
112
|
+
return { username: username, password: "demo", userPlan: 'free' }; // Ajout de userPlan pour la cohérence
|
|
113
|
+
}
|
|
114
|
+
// Logique pour les vrais utilisateurs (si vous en ajoutez plus tard)
|
|
62
115
|
return this.users.find(user => user.username === username);
|
|
63
116
|
}
|
|
64
117
|
|
|
118
|
+
async getUserStorageLimit(user) {
|
|
119
|
+
return maxTotalPrivateFilesSize;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async getBackupFrequency(user){
|
|
123
|
+
return 'daily';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async getMiddlewares(){
|
|
127
|
+
return []
|
|
128
|
+
}
|
|
129
|
+
|
|
65
130
|
async validatePassword(user, password) {
|
|
66
|
-
|
|
131
|
+
// Pour un utilisateur de démo, le mot de passe est toujours valide.
|
|
132
|
+
if (user.username.startsWith('demo')) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
// Logique pour les vrais utilisateurs
|
|
136
|
+
return true; // ou votre logique de comparaison bcrypt
|
|
67
137
|
}
|
|
68
138
|
|
|
69
139
|
async updateUser(user, data) {
|
|
70
140
|
this.users = this.users.map(user => user.username === user.username ? {...user, ...data} : user);
|
|
71
141
|
}
|
|
72
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Initialise l'utilisateur sur la requête. C'est ici que la magie opère.
|
|
144
|
+
*/
|
|
73
145
|
async initiateUser(req) {
|
|
74
|
-
|
|
146
|
+
// Priorité 1: Un utilisateur est déjà authentifié via une session (pour les vrais comptes).
|
|
147
|
+
if (req.session?.user?.username) {
|
|
148
|
+
const user = await this.findUserByUsername(req.session.user.username);
|
|
149
|
+
if (user) {
|
|
150
|
+
req.me = user;
|
|
151
|
+
return; // L'utilisateur est trouvé, on s'arrête là.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Priorité 2: PasF de session, mais on vérifie la présence d'un cookie "username" pour la démo.
|
|
156
|
+
const demoUsername = req.cookies?.username;
|
|
157
|
+
if (demoUsername && typeof demoUsername === 'string' && demoUsername.startsWith('demo')) {
|
|
158
|
+
// On a trouvé un cookie de démo. On crée l'objet utilisateur correspondant.
|
|
159
|
+
const demoUser = await this.findUserByUsername(demoUsername);
|
|
160
|
+
if (demoUser) {
|
|
161
|
+
req.me = demoUser;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
getUserPlans(){
|
|
168
|
+
return {
|
|
169
|
+
free: {
|
|
170
|
+
features: ['indexes']
|
|
171
|
+
}
|
|
172
|
+
}
|
|
75
173
|
}
|
|
76
174
|
}
|
package/src/setenv.js
CHANGED
package/src/user.js
CHANGED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
maxTotalPrivateFilesSizeFree,
|
|
3
|
-
maxTotalPrivateFilesSizePremium,
|
|
4
|
-
maxTotalPrivateFilesSizeStandard
|
|
5
|
-
} from "./constants.js";
|
|
6
|
-
|
|
7
|
-
export const getUserStorageLimit = (user) => {
|
|
8
|
-
switch (user.userPlan) {
|
|
9
|
-
case 'premium':
|
|
10
|
-
return maxTotalPrivateFilesSizePremium;
|
|
11
|
-
case 'standard':
|
|
12
|
-
return maxTotalPrivateFilesSizeStandard;
|
|
13
|
-
case 'free':
|
|
14
|
-
default:
|
|
15
|
-
return maxTotalPrivateFilesSizeFree;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
@@ -46,29 +46,30 @@ parentPort.on('message', async ({ action, payload }) => {
|
|
|
46
46
|
try {
|
|
47
47
|
let result;
|
|
48
48
|
switch (action) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
case 'encrypt':
|
|
50
|
+
console.log(`[Worker] Received action: ${action} for file: ${payload.filePath}`);
|
|
51
|
+
await encryptFile(payload.filePath, payload.password);
|
|
52
|
+
parentPort.postMessage({ success: true }); // Pas de données à retourner
|
|
53
|
+
break;
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
case 'decrypt':
|
|
56
|
+
console.log(`[Worker] Received action: ${action} for file: ${payload.filePath}`);
|
|
57
|
+
await decryptFile(payload.filePath, payload.password);
|
|
58
|
+
parentPort.postMessage({ success: true }); // Pas de données à retourner
|
|
59
|
+
break;
|
|
60
|
+
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
case 'hash':
|
|
63
|
+
console.log(`[Worker] Received action: hash`);
|
|
64
|
+
if (!payload.data) throw new Error('Data to hash is missing in payload.');
|
|
65
|
+
// Le worker gère à la fois la génération du "salt" et le hachage
|
|
66
|
+
const salt = await bcrypt.genSalt(10);
|
|
67
|
+
result = await bcrypt.hash(payload.data, salt);
|
|
68
|
+
parentPort.postMessage({ success: true, data: result }); // On retourne le hash
|
|
69
|
+
break;
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
default:
|
|
72
|
+
throw new Error(`Unknown crypto action: ${action}`);
|
|
72
73
|
}
|
|
73
74
|
} catch (error) {
|
|
74
75
|
// Envoie l'erreur au thread principal
|
|
@@ -5,27 +5,27 @@ parentPort.on('message', ({ action, payload }) => {
|
|
|
5
5
|
try {
|
|
6
6
|
let result;
|
|
7
7
|
switch (action) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
case 'parse-json':
|
|
9
|
+
// Tâche: Parser une chaîne/buffer JSON en objet JS
|
|
10
|
+
result = JSON.parse(payload.fileContent.toString('utf-8'));
|
|
11
|
+
break;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
case 'parse-csv':
|
|
14
|
+
// Tâche: Parser une chaîne/buffer CSV en tableau d'objets JS
|
|
15
|
+
result = parse(payload.fileContent, {
|
|
16
|
+
columns: payload.options.columns,
|
|
17
|
+
skip_empty_lines: true,
|
|
18
|
+
trim: true
|
|
19
|
+
});
|
|
20
|
+
break;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
case 'stringify-json':
|
|
23
|
+
// Tâche: Sérialiser un objet/tableau JS en chaîne JSON
|
|
24
|
+
result = JSON.stringify(payload.data);
|
|
25
|
+
break;
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
default:
|
|
28
|
+
throw new Error(`Unknown action: ${action}`);
|
|
29
29
|
}
|
|
30
30
|
// Renvoyer le résultat au thread principal
|
|
31
31
|
parentPort.postMessage({ success: true, data: result });
|
|
@@ -6,25 +6,21 @@ import { Config } from '../src/config.js';
|
|
|
6
6
|
import { ObjectId } from 'mongodb';
|
|
7
7
|
import {expect, describe, it, beforeAll, afterAll, beforeEach} from 'vitest';
|
|
8
8
|
import { vi } from 'vitest'
|
|
9
|
-
import { Buffer } from 'node:buffer'; // Explicitly import Buffer
|
|
10
|
-
import crypto from 'node:crypto'; //Explicitly import crypto
|
|
11
9
|
|
|
12
10
|
import {
|
|
13
11
|
createModel,
|
|
14
|
-
|
|
12
|
+
insertData
|
|
15
13
|
} from 'data-primals-engine/modules/data';
|
|
16
14
|
|
|
17
15
|
import {
|
|
18
16
|
modelsCollection as getAppModelsCollection,
|
|
19
|
-
getCollectionForUser as getAppUserCollection
|
|
17
|
+
getCollectionForUser as getAppUserCollection
|
|
20
18
|
} from 'data-primals-engine/modules/mongodb';
|
|
21
|
-
import { Engine } from "data-primals-engine/engine";
|
|
22
19
|
import process from "node:process";
|
|
23
20
|
|
|
24
|
-
import { dumpUserData, loadFromDump
|
|
21
|
+
import { dumpUserData, loadFromDump } from 'data-primals-engine/modules/data';
|
|
25
22
|
import fs from "node:fs";
|
|
26
|
-
import {
|
|
27
|
-
import {getUniquePort, initEngine, stopEngine} from "../src/setenv.js";
|
|
23
|
+
import {initEngine} from "../src/setenv.js";
|
|
28
24
|
|
|
29
25
|
vi.mock('data-primals-engine/engine', async(importOriginal) => {
|
|
30
26
|
const mod = await importOriginal() // type is inferred
|
|
@@ -37,7 +33,6 @@ vi.mock('data-primals-engine/engine', async(importOriginal) => {
|
|
|
37
33
|
const mockUser = {
|
|
38
34
|
username: 'testuserBackup',
|
|
39
35
|
_user: 'testuserBackup',
|
|
40
|
-
userPlan: 'premium',
|
|
41
36
|
email: 'testBackup@example.com',
|
|
42
37
|
configS3: {
|
|
43
38
|
bucketName: null
|
|
@@ -50,9 +45,9 @@ const testModelDefinition = {
|
|
|
50
45
|
description: 'Model for testing backup/restore',
|
|
51
46
|
fields: [
|
|
52
47
|
{ name: 'testField', type: 'string', required: true },
|
|
53
|
-
{ name: 'optionalField', type: 'number' }
|
|
48
|
+
{ name: 'optionalField', type: 'number' }
|
|
54
49
|
],
|
|
55
|
-
maxRequestData: 10
|
|
50
|
+
maxRequestData: 10
|
|
56
51
|
};
|
|
57
52
|
|
|
58
53
|
let testModelsColInstance;
|
|
@@ -101,7 +96,7 @@ beforeAll(async () =>{
|
|
|
101
96
|
})
|
|
102
97
|
beforeEach(async () => {
|
|
103
98
|
testModelsColInstance = getAppModelsCollection;
|
|
104
|
-
testDatasColInstance = getAppUserCollection(mockUser);
|
|
99
|
+
testDatasColInstance = await getAppUserCollection(mockUser);
|
|
105
100
|
});
|
|
106
101
|
|
|
107
102
|
describe('Data Backup and Restore Integration', () => {
|
|
@@ -14,8 +14,7 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
modelsCollection as getAppModelsCollection,
|
|
16
16
|
getCollection,
|
|
17
|
-
getCollectionForUser as getAppUserCollection
|
|
18
|
-
datasCollection // Accès direct pour vérifications
|
|
17
|
+
getCollectionForUser as getAppUserCollection
|
|
19
18
|
} from 'data-primals-engine/modules/mongodb';
|
|
20
19
|
import { Engine } from "data-primals-engine/engine";
|
|
21
20
|
import process from "node:process";
|
|
@@ -41,6 +40,9 @@ async function setupTestContext() {
|
|
|
41
40
|
email: generateUniqueName('test') + '@example.com'
|
|
42
41
|
};
|
|
43
42
|
|
|
43
|
+
// Initialize collection instances after the engine is ready
|
|
44
|
+
testModelsColInstance = getAppModelsCollection;
|
|
45
|
+
testDatasColInstance = await getAppUserCollection(currentTestUser);
|
|
44
46
|
|
|
45
47
|
const relatedModelDefinition = {
|
|
46
48
|
name: currentRelatedModelName,
|
|
@@ -50,7 +52,7 @@ async function setupTestContext() {
|
|
|
50
52
|
{name: 'relatedName', type: 'string', required: true, unique: true},
|
|
51
53
|
{name: 'relatedValue', type: 'number'}
|
|
52
54
|
],
|
|
53
|
-
maxRequestData: 10
|
|
55
|
+
maxRequestData: 10
|
|
54
56
|
};
|
|
55
57
|
|
|
56
58
|
const comprehensiveTestModelDefinition = {
|
|
@@ -102,23 +104,25 @@ async function setupTestContext() {
|
|
|
102
104
|
{name: 'objectField', type: 'object'},
|
|
103
105
|
// Model & ModelField (validation of string format, not existence)
|
|
104
106
|
{name: 'modelNameField', type: 'model'},
|
|
105
|
-
{name: 'modelFieldNameField', type: 'modelField'}
|
|
107
|
+
{name: 'modelFieldNameField', type: 'modelField'} // Note: modelField type expects an object {model: 'modelName', field: 'fieldName'}
|
|
106
108
|
],
|
|
107
|
-
maxRequestData: 50
|
|
109
|
+
maxRequestData: 50
|
|
108
110
|
};
|
|
109
111
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
if( await testModelsColInstance.find({ $or: [{name: comprehensiveTestModelDefinition.name}, {name: relatedModelDefinition.name}]})) {
|
|
113
|
+
// Insérer les modèles en base
|
|
114
|
+
await testModelsColInstance.insertMany([
|
|
115
|
+
comprehensiveTestModelDefinition,
|
|
116
|
+
relatedModelDefinition
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
115
119
|
await testDatasColInstance.deleteMany({ _user: currentTestUser.username });
|
|
116
120
|
|
|
117
121
|
// Retourner toutes les variables nécessaires pour un test
|
|
118
122
|
return {
|
|
119
123
|
currentTestUser,
|
|
120
124
|
comprehensiveTestModelDefinition,
|
|
121
|
-
relatedModelDefinition
|
|
125
|
+
relatedModelDefinition
|
|
122
126
|
};
|
|
123
127
|
}
|
|
124
128
|
|
|
@@ -128,9 +132,6 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
128
132
|
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
|
|
129
133
|
await initEngine();
|
|
130
134
|
|
|
131
|
-
// Initialize collection instances after the engine is ready
|
|
132
|
-
testModelsColInstance = getAppModelsCollection;
|
|
133
|
-
testDatasColInstance = datasCollection;
|
|
134
135
|
})
|
|
135
136
|
|
|
136
137
|
describe('insertData avec comprehensiveTestModel', () => {
|
|
@@ -186,13 +187,14 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
186
187
|
codeJsField: 'console.log("hello");',
|
|
187
188
|
objectField: { a: 1, b: "test" },
|
|
188
189
|
modelNameField: 'someModelName',
|
|
189
|
-
modelFieldNameField: { model: 'someModelName', field: 'someFieldName' }
|
|
190
|
+
modelFieldNameField: { model: 'someModelName', field: 'someFieldName' }
|
|
190
191
|
};
|
|
191
192
|
|
|
192
193
|
const result = await insertData(comprehensiveTestModelDefinition.name, validData, {}, currentTestUser, false);
|
|
193
194
|
expect(result.success, `L'insertion principale a échoué: ${result.error}`).toBe(true);
|
|
194
195
|
expect(result.insertedIds).toHaveLength(1);
|
|
195
196
|
|
|
197
|
+
console.log(result);
|
|
196
198
|
const insertedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(result.insertedIds[0]) });
|
|
197
199
|
expect(insertedDoc).not.toBeNull();
|
|
198
200
|
expect(insertedDoc.stringRequired).toBe('Valid String');
|
|
@@ -365,7 +367,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
365
367
|
codeJsField: 'console.log("find");',
|
|
366
368
|
objectField: { find: "test" },
|
|
367
369
|
modelNameField: 'findModel',
|
|
368
|
-
modelFieldNameField: { model: 'findModel', field: 'findField' }
|
|
370
|
+
modelFieldNameField: { model: 'findModel', field: 'findField' }
|
|
369
371
|
};
|
|
370
372
|
|
|
371
373
|
// 3. Insérer le document principal
|
|
@@ -411,7 +413,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
411
413
|
stringUnique: 'UniqueForEdit',
|
|
412
414
|
enumField: 'alpha',
|
|
413
415
|
passwordField: 'initialPass',
|
|
414
|
-
relationSingle: relatedDocId
|
|
416
|
+
relationSingle: relatedDocId
|
|
415
417
|
};
|
|
416
418
|
const insertResult = await insertData(comprehensiveTestModelDefinition.name, initialData, {}, currentTestUser, false);
|
|
417
419
|
docToEditId = insertResult.insertedIds[0];
|
|
@@ -493,17 +495,17 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
493
495
|
await insertData(simpleModel.name, { name: 'Autre Item', value: 403 }, {}, currentTestUser, false);
|
|
494
496
|
|
|
495
497
|
const filter = { $regexMatch: {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
498
|
+
input: '$name',
|
|
499
|
+
regex: "^Item Filtrable"
|
|
500
|
+
} };
|
|
499
501
|
const deleteResult = await deleteData(simpleModel.name, [], filter, currentTestUser, false);
|
|
500
502
|
|
|
501
503
|
expect(deleteResult.success).toBe(true);
|
|
502
504
|
expect(deleteResult.deletedCount).toBe(2);
|
|
503
505
|
|
|
504
|
-
const remainingCount = await testDatasColInstance.countDocuments({ _model: simpleModel.name, name: { $regex: "^Item Filtrable" } });
|
|
506
|
+
const remainingCount = await testDatasColInstance.countDocuments({ _user: currentTestUser.username, _model: simpleModel.name, name: { $regex: "^Item Filtrable" } });
|
|
505
507
|
expect(remainingCount).toBe(0);
|
|
506
|
-
const totalRemaining = await testDatasColInstance.countDocuments({ _model: simpleModel.name });
|
|
508
|
+
const totalRemaining = await testDatasColInstance.countDocuments({ _user: currentTestUser.username, _model: simpleModel.name });
|
|
507
509
|
expect(totalRemaining).toBe(1);
|
|
508
510
|
});
|
|
509
511
|
});
|
|
@@ -531,7 +533,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
531
533
|
passwordField: 'searchPass1',
|
|
532
534
|
relationSingle: relatedDocId_A1,
|
|
533
535
|
relationMultiple: [relatedDocId_A1, relatedDocId_A2],
|
|
534
|
-
number: 10
|
|
536
|
+
number: 10
|
|
535
537
|
};
|
|
536
538
|
const insertResult1 = await insertData(comprehensiveTestModelDefinition.name, data1, {}, currentTestUser, false);
|
|
537
539
|
docId1 = insertResult1.insertedIds[0].toString();
|
|
@@ -543,7 +545,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
543
545
|
passwordField: 'searchPass2',
|
|
544
546
|
relationSingle: relatedDocId_B1,
|
|
545
547
|
relationMultiple: [relatedDocId_B1],
|
|
546
|
-
number: 20
|
|
548
|
+
number: 20
|
|
547
549
|
};
|
|
548
550
|
const insertResult2 = await insertData(comprehensiveTestModelDefinition.name, data2, {}, currentTestUser, false);
|
|
549
551
|
docId2 = insertResult2.insertedIds[0].toString();
|
|
@@ -564,7 +566,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
564
566
|
},
|
|
565
567
|
autoExpand: true,
|
|
566
568
|
depth: 8
|
|
567
|
-
}
|
|
569
|
+
}
|
|
568
570
|
};
|
|
569
571
|
const { data, count } = await searchData(searchParams);
|
|
570
572
|
expect(count).toBe(1);
|
package/test/globalSetup.js
CHANGED
|
@@ -4,7 +4,7 @@ import {getCollection} from "../src/modules/mongodb.js";
|
|
|
4
4
|
|
|
5
5
|
export default async function () {
|
|
6
6
|
|
|
7
|
-
// --- Configuration initiale ---
|
|
7
|
+
// --- Configuration initiale ---
|
|
8
8
|
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
|
|
9
9
|
|
|
10
10
|
console.log('\n--- Global Setup: Initializing engine and database for all tests ---');
|
|
@@ -12,4 +12,4 @@ export default async function () {
|
|
|
12
12
|
|
|
13
13
|
// Initialisez et exportez les collections
|
|
14
14
|
console.log('--- Global Setup: Complete. Engine is running. ---');
|
|
15
|
-
}
|
|
15
|
+
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
modelsCollection as getAppModelsCollection,
|
|
13
|
-
getCollectionForUser as getAppUserCollection
|
|
13
|
+
getCollectionForUser as getAppUserCollection
|
|
14
14
|
} from 'data-primals-engine/modules/mongodb';
|
|
15
15
|
import {sleep} from "data-primals-engine/core";
|
|
16
16
|
import fs from "node:fs";
|
|
@@ -33,8 +33,8 @@ const impexTestModel = {
|
|
|
33
33
|
{ name: 'name', type: 'string', required: true },
|
|
34
34
|
{ name: 'sku', type: 'string', unique: true },
|
|
35
35
|
{ name: 'price', type: 'number', required: true },
|
|
36
|
-
{ name: 'inStock', type: 'boolean', default: true }
|
|
37
|
-
]
|
|
36
|
+
{ name: 'inStock', type: 'boolean', default: true }
|
|
37
|
+
]
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
// --- Setup de l'environnement de test ---
|
|
@@ -62,7 +62,7 @@ describe('Intégration des fonctions d\'Import/Export', () => {
|
|
|
62
62
|
// Préparation avant chaque test du bloc
|
|
63
63
|
beforeEach(async () => {
|
|
64
64
|
testModelsColInstance = getAppModelsCollection;
|
|
65
|
-
testDatasColInstance = getAppUserCollection(mockUser);
|
|
65
|
+
testDatasColInstance = await getAppUserCollection(mockUser);
|
|
66
66
|
|
|
67
67
|
// Nettoyage complet pour un état propre
|
|
68
68
|
await testDatasColInstance.deleteMany({ _user: "testuserImpex"});
|
|
@@ -74,7 +74,7 @@ describe('Intégration des fonctions d\'Import/Export', () => {
|
|
|
74
74
|
await insertData(impexTestModel.name, [
|
|
75
75
|
{ name: 'Produit A', sku: 'SKU-A', price: 10.50, inStock: true },
|
|
76
76
|
{ name: 'Produit B', sku: 'SKU-B', price: 25.00, inStock: false },
|
|
77
|
-
{ name: 'Produit C', sku: 'SKU-C', price: 99.99, inStock: true }
|
|
77
|
+
{ name: 'Produit C', sku: 'SKU-C', price: 99.99, inStock: true }
|
|
78
78
|
], {}, mockUser, false);
|
|
79
79
|
});
|
|
80
80
|
|
|
@@ -82,7 +82,7 @@ describe('Intégration des fonctions d\'Import/Export', () => {
|
|
|
82
82
|
it('devrait exporter les données en format JSON', async () => {
|
|
83
83
|
const res= await exportData({
|
|
84
84
|
models: [impexTestModel.name],
|
|
85
|
-
depth: 1
|
|
85
|
+
depth: 1
|
|
86
86
|
}, mockUser);
|
|
87
87
|
|
|
88
88
|
expect(res.success).toBeTruthy();
|
|
@@ -38,7 +38,7 @@ async function setupTestContext() {
|
|
|
38
38
|
{name: 'relatedName', type: 'string', required: true, unique: true},
|
|
39
39
|
{name: 'relatedValue', type: 'number'}
|
|
40
40
|
],
|
|
41
|
-
maxRequestData: 10
|
|
41
|
+
maxRequestData: 10
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
const comprehensiveTestModelDefinition = {
|
|
@@ -90,9 +90,9 @@ async function setupTestContext() {
|
|
|
90
90
|
{name: 'objectField', type: 'object'},
|
|
91
91
|
// Model & ModelField (validation of string format, not existence)
|
|
92
92
|
{name: 'modelNameField', type: 'model'},
|
|
93
|
-
{name: 'modelFieldNameField', type: 'modelField'}
|
|
93
|
+
{name: 'modelFieldNameField', type: 'modelField'} // Note: modelField type expects an object {model: 'modelName', field: 'fieldName'}
|
|
94
94
|
],
|
|
95
|
-
maxRequestData: 50
|
|
95
|
+
maxRequestData: 50
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
// Insérer les modèles en base
|
|
@@ -108,7 +108,7 @@ async function setupTestContext() {
|
|
|
108
108
|
return {
|
|
109
109
|
currentTestUser,
|
|
110
110
|
comprehensiveTestModelDefinition,
|
|
111
|
-
relatedModelDefinition
|
|
111
|
+
relatedModelDefinition
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -127,22 +127,21 @@ describe('CRUD on model definitions and integrity tests', () => {
|
|
|
127
127
|
it('should create and drop index when field.index is toggled (premium user)', async () => {
|
|
128
128
|
// --- SETUP ---
|
|
129
129
|
const { currentTestUser, comprehensiveTestModelDefinition } = await setupTestContext();
|
|
130
|
-
const
|
|
131
|
-
const dataCollection = getCollectionForUser(premiumUser);
|
|
130
|
+
const dataCollection = await getCollectionForUser(currentTestUser);
|
|
132
131
|
const fieldToIndex = 'stringUnique'; // Utiliser un champ qui existe vraiment dans le modèle
|
|
133
132
|
|
|
133
|
+
// --- FIX: Ensure the collection exists before any operation ---
|
|
134
|
+
// By inserting and deleting a dummy document, we force MongoDB to create the
|
|
135
|
+
// collection and its default indexes. This prevents the "ns does not exist"
|
|
136
|
+
// error in asynchronous listeners (like workflow triggers).
|
|
137
|
+
const dummyDoc = await dataCollection.insertOne({ _model: 'dummy', _user: currentTestUser.username });
|
|
138
|
+
await dataCollection.deleteOne({ _id: dummyDoc.insertedId });
|
|
139
|
+
|
|
140
|
+
|
|
134
141
|
// --- VERIFICATION INITIALE ---
|
|
135
142
|
// S'assurer qu'aucun index n'existe au départ.
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
initialIndexes = await dataCollection.indexes();
|
|
140
|
-
} catch (e) {
|
|
141
|
-
// C'est normal si la collection n'existe pas encore. On considère qu'il n'y a pas d'index.
|
|
142
|
-
if (e.codeName !== 'NamespaceNotFound') {
|
|
143
|
-
throw e; // Relancer les autres erreurs inattendues
|
|
144
|
-
}
|
|
145
|
-
}
|
|
143
|
+
// Cet appel ne plantera plus car la collection est maintenant créée.
|
|
144
|
+
const initialIndexes = await dataCollection.indexes();
|
|
146
145
|
expect(initialIndexes.some(i => i.key[fieldToIndex] === 1)).toBe(false);
|
|
147
146
|
|
|
148
147
|
// --- ACTION 1 : AJOUTER UN INDEX ---
|
|
@@ -150,9 +149,9 @@ describe('CRUD on model definitions and integrity tests', () => {
|
|
|
150
149
|
...comprehensiveTestModelDefinition,
|
|
151
150
|
fields: comprehensiveTestModelDefinition.fields.map(f =>
|
|
152
151
|
f.name === fieldToIndex ? { ...f, index: true } : f
|
|
153
|
-
)
|
|
152
|
+
)
|
|
154
153
|
};
|
|
155
|
-
await editModel(
|
|
154
|
+
await editModel(currentTestUser, testModelId, modelWithIndex);
|
|
156
155
|
|
|
157
156
|
// --- VERIFICATION 1 ---
|
|
158
157
|
// Maintenant, la collection et l'index doivent exister.
|
|
@@ -163,7 +162,7 @@ describe('CRUD on model definitions and integrity tests', () => {
|
|
|
163
162
|
// Le filtre partiel est crucial pour que l'index ne s'applique qu'aux bonnes données
|
|
164
163
|
expect(newIndex.partialFilterExpression).toEqual({
|
|
165
164
|
_model: comprehensiveTestModelDefinition.name,
|
|
166
|
-
_user:
|
|
165
|
+
_user: currentTestUser.username
|
|
167
166
|
});
|
|
168
167
|
|
|
169
168
|
// --- ACTION 2 : SUPPRIMER L'INDEX ---
|
|
@@ -171,14 +170,14 @@ describe('CRUD on model definitions and integrity tests', () => {
|
|
|
171
170
|
...comprehensiveTestModelDefinition,
|
|
172
171
|
fields: comprehensiveTestModelDefinition.fields.map(f =>
|
|
173
172
|
f.name === fieldToIndex ? { ...f, index: false } : f
|
|
174
|
-
)
|
|
173
|
+
)
|
|
175
174
|
};
|
|
176
|
-
await editModel(
|
|
175
|
+
await editModel(currentTestUser, testModelId, modelWithoutIndex);
|
|
177
176
|
|
|
178
177
|
// --- VERIFICATION 2 ---
|
|
179
178
|
const indexesAfterDeletion = await dataCollection.indexes();
|
|
180
179
|
expect(indexesAfterDeletion.some(i => i.key[fieldToIndex] === 1)).toBe(false);
|
|
181
|
-
}, 20000);
|
|
180
|
+
}, 20000);
|
|
182
181
|
|
|
183
182
|
it('should not save extra, non-defined fields in the model definition', async () => {
|
|
184
183
|
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|