data-primals-engine 1.2.2 → 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.
- package/CONTRIBUTING.md +91 -0
- package/README.md +33 -17
- package/client/src/App.jsx +0 -5
- package/client/src/ConditionBuilder.scss +34 -1
- package/client/src/ConditionBuilder2.jsx +179 -53
- package/client/src/ContentView.jsx +0 -3
- package/client/src/CronBuilder.jsx +0 -1
- package/client/src/CronPartBuilder.jsx +0 -2
- package/client/src/DashboardView.jsx +0 -5
- package/client/src/DataEditor.jsx +8 -211
- package/client/src/DataLayout.jsx +0 -1
- package/client/src/DataTable.jsx +1 -3
- package/client/src/Field.jsx +0 -5
- package/client/src/FlexBuilder.jsx +1 -1
- package/client/src/ModelCreatorField.jsx +1 -5
- package/client/src/RTE.jsx +1 -6
- package/client/src/RTETrans.jsx +0 -2
- package/client/src/RelationField.jsx +1 -1
- package/client/src/RelationValue.jsx +1 -2
- package/client/src/TourSpotlight.jsx +0 -2
- package/client/src/constants.js +1 -1
- package/client/src/filter.js +87 -0
- package/client/src/hooks/data.js +1 -3
- package/client/src/hooks/useTutorials.jsx +0 -1
- package/package.json +3 -3
- package/server.js +2 -2
- package/src/constants.js +2 -2
- package/src/defaultModels.js +1 -14
- package/src/email.js +9 -7
- package/src/engine.js +60 -20
- package/src/events.js +1 -1
- package/src/filter.js +221 -0
- package/src/index.js +1 -1
- package/src/middlewares/middleware-mongodb.js +0 -1
- package/src/modules/assistant.js +1 -3
- package/src/modules/bucket.js +3 -4
- package/src/modules/{data.js → data/data.js} +42 -59
- package/src/modules/data/index.js +1 -0
- package/src/modules/file.js +1 -1
- package/src/modules/mongodb.js +0 -1
- package/src/modules/user.js +1 -1
- package/src/modules/workflow.js +299 -133
- package/src/packs.js +249 -8
- package/test/data.backup.integration.test.js +7 -5
- package/test/data.integration.test.js +8 -6
- package/test/events.test.js +1 -1
- package/test/file.test.js +11 -17
- package/test/import_export.integration.test.js +38 -27
- package/test/model.integration.test.js +20 -21
- package/test/user.test.js +32 -25
- package/test/vm.test.js +51 -0
- package/test/workflow.integration.test.js +22 -14
- package/test/workflow.robustness.test.js +19 -9
- package/src/modules/test +0 -147
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
// __tests__/data.integration.test.js
|
|
2
2
|
import { ObjectId } from 'mongodb';
|
|
3
|
-
import {expect, describe, it,
|
|
3
|
+
import {expect, describe, it, beforeAll} from 'vitest';
|
|
4
4
|
import { Config } from '../src/config.js';
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from
|
|
10
|
-
import {
|
|
11
|
-
import {editModel} from "../src/modules/data.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";
|
|
12
11
|
import {getCollectionForUser} from "../src/modules/mongodb.js";
|
|
13
12
|
|
|
14
13
|
let testModelsColInstance;
|
|
15
|
-
let testDatasColInstance;
|
|
16
|
-
|
|
17
14
|
let testModelId;
|
|
18
|
-
|
|
19
15
|
// Cette fonction va remplacer la logique de votre beforeEach pour la création de contexte
|
|
20
16
|
async function setupTestContext() {
|
|
21
17
|
|
|
22
18
|
const currentTestModelName = generateUniqueName('relatedModel');
|
|
23
19
|
const currentRelatedModelName = generateUniqueName('comprehensiveModel');
|
|
24
20
|
|
|
21
|
+
|
|
25
22
|
// Créer un utilisateur unique pour ce test
|
|
26
23
|
const currentTestUser = {
|
|
27
|
-
username: generateUniqueName('
|
|
24
|
+
username: generateUniqueName('testuserModelIntegration'),
|
|
28
25
|
userPlan: 'free',
|
|
29
26
|
email: generateUniqueName('test') + '@example.com'
|
|
30
27
|
};
|
|
31
28
|
|
|
29
|
+
testModelsColInstance = getCollection("models");
|
|
30
|
+
const testDatasColInstance = await getCollectionForUser(currentTestUser);
|
|
32
31
|
|
|
33
32
|
const relatedModelDefinition = {
|
|
34
33
|
name: currentRelatedModelName,
|
|
@@ -105,43 +104,42 @@ async function setupTestContext() {
|
|
|
105
104
|
await testDatasColInstance.deleteMany({ _user: currentTestUser.username });
|
|
106
105
|
await testDatasColInstance.deleteMany({ _model: { $in: [comprehensiveTestModelDefinition.name, 'renamedTestModel'] } });
|
|
107
106
|
// Retourner toutes les variables nécessaires pour un test
|
|
107
|
+
|
|
108
108
|
return {
|
|
109
109
|
currentTestUser,
|
|
110
|
+
coll:testDatasColInstance,
|
|
110
111
|
comprehensiveTestModelDefinition,
|
|
111
112
|
relatedModelDefinition
|
|
112
113
|
};
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
|
|
115
117
|
describe('CRUD on model definitions and integrity tests', () => {
|
|
116
118
|
|
|
117
119
|
beforeAll(async () =>{
|
|
118
120
|
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
|
|
119
121
|
await initEngine();
|
|
120
|
-
|
|
121
|
-
// Initialize collection instances after the engine is ready
|
|
122
|
-
testModelsColInstance = getAppModelsCollection;
|
|
123
|
-
testDatasColInstance = datasCollection;
|
|
124
122
|
})
|
|
123
|
+
|
|
125
124
|
describe('editModel unit tests', () => {
|
|
126
125
|
|
|
127
126
|
it('should create and drop index when field.index is toggled (premium user)', async () => {
|
|
128
127
|
// --- SETUP ---
|
|
129
|
-
const { currentTestUser, comprehensiveTestModelDefinition } = await setupTestContext();
|
|
130
|
-
const dataCollection = await getCollectionForUser(currentTestUser);
|
|
128
|
+
const { coll, currentTestUser, comprehensiveTestModelDefinition } = await setupTestContext();
|
|
131
129
|
const fieldToIndex = 'stringUnique'; // Utiliser un champ qui existe vraiment dans le modèle
|
|
132
130
|
|
|
133
131
|
// --- FIX: Ensure the collection exists before any operation ---
|
|
134
132
|
// By inserting and deleting a dummy document, we force MongoDB to create the
|
|
135
133
|
// collection and its default indexes. This prevents the "ns does not exist"
|
|
136
134
|
// error in asynchronous listeners (like workflow triggers).
|
|
137
|
-
const dummyDoc = await
|
|
138
|
-
await
|
|
135
|
+
const dummyDoc = await coll.insertOne({ _model: 'dummy', _user: currentTestUser.username });
|
|
136
|
+
await coll.deleteOne({ _id: dummyDoc.insertedId });
|
|
139
137
|
|
|
140
138
|
|
|
141
139
|
// --- VERIFICATION INITIALE ---
|
|
142
140
|
// S'assurer qu'aucun index n'existe au départ.
|
|
143
141
|
// Cet appel ne plantera plus car la collection est maintenant créée.
|
|
144
|
-
const initialIndexes = await
|
|
142
|
+
const initialIndexes = await coll.indexes();
|
|
145
143
|
expect(initialIndexes.some(i => i.key[fieldToIndex] === 1)).toBe(false);
|
|
146
144
|
|
|
147
145
|
// --- ACTION 1 : AJOUTER UN INDEX ---
|
|
@@ -155,7 +153,7 @@ describe('CRUD on model definitions and integrity tests', () => {
|
|
|
155
153
|
|
|
156
154
|
// --- VERIFICATION 1 ---
|
|
157
155
|
// Maintenant, la collection et l'index doivent exister.
|
|
158
|
-
const indexesAfterCreation = await
|
|
156
|
+
const indexesAfterCreation = await coll.indexes();
|
|
159
157
|
const newIndex = indexesAfterCreation.find(i => i.key[fieldToIndex] === 1);
|
|
160
158
|
|
|
161
159
|
expect(newIndex).toBeDefined();
|
|
@@ -175,8 +173,9 @@ describe('CRUD on model definitions and integrity tests', () => {
|
|
|
175
173
|
await editModel(currentTestUser, testModelId, modelWithoutIndex);
|
|
176
174
|
|
|
177
175
|
// --- VERIFICATION 2 ---
|
|
178
|
-
const indexesAfterDeletion = await
|
|
176
|
+
const indexesAfterDeletion = await coll.indexes();
|
|
179
177
|
expect(indexesAfterDeletion.some(i => i.key[fieldToIndex] === 1)).toBe(false);
|
|
178
|
+
|
|
180
179
|
}, 20000);
|
|
181
180
|
|
|
182
181
|
it('should not save extra, non-defined fields in the model definition', async () => {
|
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 "
|
|
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 "
|
|
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;
|
|
@@ -17,7 +17,7 @@ async function setupTestContext() {
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
// Créer un utilisateur unique pour ce test
|
|
20
|
-
const username = generateUniqueName('
|
|
20
|
+
const username = generateUniqueName('testuserUserIntegration');
|
|
21
21
|
currentTestUser = {
|
|
22
22
|
username,
|
|
23
23
|
userPlan: 'free',
|
|
@@ -58,6 +58,8 @@ async function setupTestContext() {
|
|
|
58
58
|
]);
|
|
59
59
|
testUser = {...currentTestUser, _id: users.insertedIds[0].toString(), roles: [roleEditor.toString()] };
|
|
60
60
|
adminUser = {...currentTestUser, _id: users.insertedIds[1].toString(), roles: [roleEditor.toString()] };
|
|
61
|
+
|
|
62
|
+
return testDatasColInstance;
|
|
61
63
|
};
|
|
62
64
|
let engine;
|
|
63
65
|
describe('User Permission Logic', () => {
|
|
@@ -72,26 +74,24 @@ describe('User Permission Logic', () => {
|
|
|
72
74
|
// --- Tests pour la fonction principale de calcul ---
|
|
73
75
|
describe('getUserActivePermissions()', () => {
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
beforeEach(async () =>{
|
|
77
|
-
await setupTestContext();
|
|
78
|
-
})
|
|
79
77
|
it('should return base permissions from the user\'s role', async () => {
|
|
78
|
+
const coll = await setupTestContext();
|
|
80
79
|
const permissions = await getUserActivePermissions(testUser);
|
|
81
80
|
expect(permissions).to.be.an.instanceOf(Set);
|
|
82
81
|
expect(permissions.size).to.equal(2);
|
|
83
82
|
expect(permissions.has('post:read')).to.be.true;
|
|
84
83
|
expect(permissions.has('post:write')).to.be.true;
|
|
85
84
|
expect(permissions.has('post:delete')).to.be.false;
|
|
85
|
+
await coll.drop();
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it('should grant a temporary permission via an exception', async () => {
|
|
89
|
-
|
|
89
|
+
const coll = await setupTestContext();
|
|
90
90
|
// Ajoute une exception qui donne la permission de supprimer, expirant demain
|
|
91
91
|
const futureDate = new Date();
|
|
92
92
|
futureDate.setDate(futureDate.getDate() + 1);
|
|
93
93
|
|
|
94
|
-
await
|
|
94
|
+
await coll.insertOne({
|
|
95
95
|
_model: 'userPermission',
|
|
96
96
|
user: testUser._id,
|
|
97
97
|
permission: permDelete,
|
|
@@ -104,15 +104,18 @@ describe('User Permission Logic', () => {
|
|
|
104
104
|
expect(permissions.has('post:delete')).to.be.true;
|
|
105
105
|
|
|
106
106
|
// Nettoyage de l'exception pour ne pas affecter les autres tests
|
|
107
|
-
await
|
|
107
|
+
await coll.deleteOne({ _model: 'userPermission', user: testUser._id });
|
|
108
|
+
|
|
109
|
+
await coll.drop();
|
|
108
110
|
});
|
|
109
111
|
|
|
110
112
|
it('should NOT grant an expired temporary permission', async () => {
|
|
113
|
+
const coll = await setupTestContext();
|
|
111
114
|
// Ajoute une exception qui a expiré hier
|
|
112
115
|
const pastDate = new Date();
|
|
113
116
|
pastDate.setDate(pastDate.getDate() - 1);
|
|
114
117
|
|
|
115
|
-
await
|
|
118
|
+
await coll.insertOne({
|
|
116
119
|
_model: 'userPermission',
|
|
117
120
|
user: testUser._id,
|
|
118
121
|
permission: permDelete,
|
|
@@ -124,15 +127,17 @@ describe('User Permission Logic', () => {
|
|
|
124
127
|
expect(permissions.size).to.equal(2); // Revient la normale
|
|
125
128
|
expect(permissions.has('post:delete')).to.be.false;
|
|
126
129
|
|
|
127
|
-
await
|
|
130
|
+
await coll.deleteOne({ _model: 'userPermission', user: testUser._id });
|
|
131
|
+
await coll.drop();
|
|
128
132
|
});
|
|
129
133
|
|
|
130
134
|
it('should revoke a base permission via an exception', async () => {
|
|
135
|
+
const coll = await setupTestContext();
|
|
131
136
|
// L'utilisateur est "Editor", mais on lui retire le droit d'écriture temporairement
|
|
132
137
|
const futureDate = new Date();
|
|
133
138
|
futureDate.setDate(futureDate.getDate() + 1);
|
|
134
139
|
|
|
135
|
-
await
|
|
140
|
+
await coll.insertOne({
|
|
136
141
|
_model: 'userPermission',
|
|
137
142
|
user: testUser._id,
|
|
138
143
|
permission: permWrite,
|
|
@@ -145,15 +150,17 @@ describe('User Permission Logic', () => {
|
|
|
145
150
|
expect(permissions.has('post:read')).to.be.true;
|
|
146
151
|
expect(permissions.has('post:write')).to.be.false; // La permission a été retirée
|
|
147
152
|
|
|
148
|
-
await
|
|
153
|
+
await coll.deleteOne({ _model: 'userPermission', user: testUser._id });
|
|
154
|
+
await coll.drop();
|
|
149
155
|
});
|
|
150
156
|
|
|
151
157
|
it('should restore a revoked permission if the revocation has expired', async () => {
|
|
158
|
+
const coll = await setupTestContext();
|
|
152
159
|
// La révocation a expiré, l'utilisateur devrait retrouver son droit d'écriture
|
|
153
160
|
const pastDate = new Date();
|
|
154
161
|
pastDate.setDate(pastDate.getDate() - 1);
|
|
155
162
|
|
|
156
|
-
await
|
|
163
|
+
await coll.insertOne({
|
|
157
164
|
_model: 'userPermission',
|
|
158
165
|
user: testUser._id,
|
|
159
166
|
permission: permWrite,
|
|
@@ -165,39 +172,39 @@ describe('User Permission Logic', () => {
|
|
|
165
172
|
expect(permissions.size).to.equal(2);
|
|
166
173
|
expect(permissions.has('post:write')).to.be.true; // La permission est de retour
|
|
167
174
|
|
|
168
|
-
await
|
|
175
|
+
await coll.deleteOne({ _model: 'userPermission', user: testUser._id });
|
|
176
|
+
await coll.drop();
|
|
169
177
|
});
|
|
170
178
|
});
|
|
171
179
|
|
|
172
180
|
// --- Tests pour la fonction publique `hasPermission` ---
|
|
173
181
|
describe('hasPermission()', () => {
|
|
174
|
-
beforeEach(async () => {
|
|
175
|
-
await setupTestContext();
|
|
176
|
-
});
|
|
177
182
|
it('should return true for a permission the user has', async () => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
console.log(await testDatasColInstance.find({ _model: 'permission' }).toArray());
|
|
181
|
-
console.log(await testDatasColInstance.find({ _model: 'role' }).toArray());
|
|
182
|
-
console.log(await testDatasColInstance.find({ _model: 'user' }).toArray());
|
|
183
|
-
console.log(await testDatasColInstance.find({ _model: 'userPermission' }).toArray());
|
|
183
|
+
const coll = await setupTestContext();
|
|
184
184
|
const result = await hasPermission('post:read', testUser);
|
|
185
185
|
expect(result).to.be.true;
|
|
186
|
+
await coll.drop();
|
|
186
187
|
});
|
|
187
188
|
|
|
188
189
|
it('should return false for a permission the user does not have', async () => {
|
|
190
|
+
const coll = await setupTestContext();
|
|
189
191
|
const result = await hasPermission('user:manage', testUser);
|
|
190
192
|
expect(result).to.be.false;
|
|
193
|
+
await coll.drop();
|
|
191
194
|
});
|
|
192
195
|
|
|
193
196
|
it('should return true if user has at least one of the required permissions', async () => {
|
|
197
|
+
const coll = await setupTestContext();
|
|
194
198
|
const result = await hasPermission(['user:manage', 'post:write'], testUser);
|
|
195
199
|
expect(result).to.be.true;
|
|
200
|
+
await coll.drop();
|
|
196
201
|
});
|
|
197
202
|
|
|
198
203
|
it('should return false if user has none of the required permissions', async () => {
|
|
204
|
+
const coll = await setupTestContext();
|
|
199
205
|
const result = await hasPermission(['user:manage', 'post:delete'], testUser);
|
|
200
206
|
expect(result).to.be.false;
|
|
207
|
+
await coll.drop();
|
|
201
208
|
});
|
|
202
209
|
});
|
|
203
210
|
});
|
package/test/vm.test.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {expect, describe, it, beforeEach, afterEach, beforeAll, afterAll, vi, vitest} from 'vitest';
|
|
2
|
+
import ivm from "isolated-vm";
|
|
3
|
+
import {sleep} from "../src/core";
|
|
4
|
+
|
|
5
|
+
vi.stubEnv('ENCRYPTION_KEY', '12345678901234567890123456789012');
|
|
6
|
+
|
|
7
|
+
async function executeSafeJavascript() {
|
|
8
|
+
const isolate = new ivm.Isolate()
|
|
9
|
+
|
|
10
|
+
async function myAsyncFunction(str) {
|
|
11
|
+
await sleep(1000);
|
|
12
|
+
console.log("hello"+str)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const context = await isolate.createContext();
|
|
16
|
+
const jail = context.global;
|
|
17
|
+
|
|
18
|
+
await jail.set('myAsync', new ivm.Reference(myAsyncFunction));
|
|
19
|
+
|
|
20
|
+
const fn = await context.eval(`
|
|
21
|
+
const normalizeArgs = args => args.map(arg => {
|
|
22
|
+
if (typeof arg === 'object' && arg !== null) {
|
|
23
|
+
return JSON.stringify(arg); // Convert objects to strings
|
|
24
|
+
}
|
|
25
|
+
return arg;
|
|
26
|
+
});
|
|
27
|
+
const t = (...args) => {
|
|
28
|
+
myAsync.applySyncPromise(null, normalizeArgs(args));
|
|
29
|
+
}
|
|
30
|
+
(async function untrusted() {
|
|
31
|
+
let str = await t("ok", {"ok":true});
|
|
32
|
+
return str;
|
|
33
|
+
})
|
|
34
|
+
`, { reference: true })
|
|
35
|
+
const value = await fn.apply(undefined, [], { result: { promise: true } })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
beforeAll(async () => {
|
|
40
|
+
});
|
|
41
|
+
describe('VM system ingration', () => {
|
|
42
|
+
|
|
43
|
+
it('should successfully add a file with local storage', async () => {
|
|
44
|
+
console.log(await executeSafeJavascript({
|
|
45
|
+
script: 'const t = await addSync();'
|
|
46
|
+
}, {}, { username:'test'}));
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
expect(true).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
// __tests__/workflow.integration.test.js
|
|
2
1
|
import { expect, describe, it, beforeEach,afterEach, beforeAll, afterAll, vi } from 'vitest';
|
|
3
|
-
import { Config } from "
|
|
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";
|
|
2
|
+
import { Config } from "../src/config";
|
|
10
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";
|
|
11
8
|
|
|
12
9
|
beforeAll(async () =>{
|
|
13
10
|
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
|
|
14
11
|
await initEngine();
|
|
15
12
|
})
|
|
16
|
-
vi.mock('
|
|
13
|
+
vi.mock('../src/modules/workflow.js', { spy: true })
|
|
17
14
|
// --- Données Mock pour les tests ---
|
|
18
15
|
const mockUser = {
|
|
19
16
|
username: 'testuserWorkflow',
|
|
20
17
|
_user: 'testuserWorkflow',
|
|
21
|
-
userPlan: '
|
|
18
|
+
userPlan: 'premium',
|
|
22
19
|
email: 'testWorkflow@example.com'
|
|
23
20
|
};
|
|
24
21
|
|
|
@@ -138,8 +135,6 @@ const processWorkflowRunSpy = vi.spyOn(workflowModule, 'processWorkflowRun');
|
|
|
138
135
|
beforeEach(async () => {
|
|
139
136
|
testModelsColInstance = getAppModelsCollection;
|
|
140
137
|
testDatasColInstance = await getCollectionForUser(mockUser);
|
|
141
|
-
// Nettoyer les données avant chaque test
|
|
142
|
-
await testDatasColInstance.deleteMany({_user: "testuserWorkflow"});
|
|
143
138
|
|
|
144
139
|
// Réinitialiser l'espion
|
|
145
140
|
processWorkflowRunSpy.mockClear();
|
|
@@ -152,9 +147,19 @@ beforeEach(async () => {
|
|
|
152
147
|
...workflowMetaModels.map(m => ({...m})) // Copie pour éviter les mutations
|
|
153
148
|
]);
|
|
154
149
|
}
|
|
155
|
-
|
|
156
|
-
})
|
|
150
|
+
// tell vitest we use mocked time
|
|
151
|
+
vi.useFakeTimers({ shouldAdvanceTime: true })
|
|
152
|
+
})
|
|
157
153
|
|
|
154
|
+
afterEach(() => {
|
|
155
|
+
vi.runOnlyPendingTimers();
|
|
156
|
+
// restoring date after each test run
|
|
157
|
+
vi.useRealTimers()
|
|
158
|
+
})
|
|
159
|
+
afterAll(async () => {
|
|
160
|
+
const coll = await getCollectionForUser(mockUser);
|
|
161
|
+
await coll.drop();
|
|
162
|
+
})
|
|
158
163
|
describe('Intégration des Workflows - triggerWorkflows', () => {
|
|
159
164
|
|
|
160
165
|
let testWorkflow;
|
|
@@ -162,6 +167,9 @@ describe('Intégration des Workflows - triggerWorkflows', () => {
|
|
|
162
167
|
|
|
163
168
|
// Avant chaque test de ce bloc, on crée un workflow et une étape de base
|
|
164
169
|
const initTest = async () => {
|
|
170
|
+
// Nettoyer les données avant chaque test
|
|
171
|
+
await testDatasColInstance.deleteMany({_user: "testuserWorkflow"});
|
|
172
|
+
|
|
165
173
|
const workflowInsertResult = await insertData('workflow', { name: 'Test Workflow' }, {}, mockUser, false);
|
|
166
174
|
testWorkflow = { _id: workflowInsertResult.insertedIds[0] };
|
|
167
175
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { ObjectId } from 'mongodb';
|
|
2
|
-
import {
|
|
3
|
-
import { Config } from "
|
|
4
|
-
|
|
5
|
-
import { insertData, editData } from '
|
|
6
|
-
import { modelsCollection as getAppModelsCollection, getCollectionForUser, getCollection } from '
|
|
7
|
-
import * as workflowModule from '
|
|
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
10
|
|
|
11
|
-
vi.mock('
|
|
11
|
+
vi.mock('../src/modules/workflow.js', { spy: true })
|
|
12
12
|
|
|
13
13
|
const mockUser = {
|
|
14
14
|
username: 'robustnessUser',
|
|
@@ -41,12 +41,22 @@ beforeEach(async () => {
|
|
|
41
41
|
await testDatasColInstance.deleteMany({ _user: mockUser.username });
|
|
42
42
|
await getCollection('job_locks').deleteMany({}); // Nettoyer les verrous
|
|
43
43
|
const mods = await testModelsColInstance.find({ $and: [{_user: mockUser.username}, {$or: [{name: targetDataModel.name}, ...workflowMetaModels.map(m =>({name: m.name}))] }]}).toArray();
|
|
44
|
-
console.log({mods})
|
|
45
44
|
if( mods.length === 0){
|
|
46
45
|
await testModelsColInstance.insertMany([targetDataModel, ...workflowMetaModels]);
|
|
47
46
|
}
|
|
48
|
-
|
|
47
|
+
// tell vitest we use mocked time
|
|
48
|
+
vi.useFakeTimers({ shouldAdvanceTime: true })
|
|
49
|
+
})
|
|
49
50
|
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
vi.runOnlyPendingTimers();
|
|
53
|
+
// restoring date after each test run
|
|
54
|
+
vi.useRealTimers()
|
|
55
|
+
})
|
|
56
|
+
afterAll(async () => {
|
|
57
|
+
const coll = await getCollectionForUser(mockUser);
|
|
58
|
+
await coll.drop();
|
|
59
|
+
})
|
|
50
60
|
// ====================================================================================
|
|
51
61
|
// =================== DÉBUT DES TESTS DE ROBUSTESSE ==================================
|
|
52
62
|
// ====================================================================================
|
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
|
-
};
|