data-primals-engine 1.0.7 → 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.
- package/.github/workflows/node.js.yml +59 -0
- package/.github/workflows/npm-publish.yml +23 -0
- package/Dockerfile +15 -0
- package/DockerfileTest +15 -0
- package/package.json +4 -7
- package/server.js +11 -2
- package/src/data.js +15 -5
- package/src/email.js +2 -0
- package/src/engine.js +42 -40
- package/src/gameObject.js +5 -7
- package/src/migrate.js +1 -1
- package/src/modules/data.js +87 -61
- package/src/modules/mongodb.js +1 -8
- package/src/modules/workflow.js +190 -58
- package/src/packs.js +26 -10
- package/src/setenv.js +39 -0
- package/test/data.backup.integration.test.js +141 -0
- package/test/data.integration.test.js +796 -0
- package/test/globalSetup.js +15 -0
- package/test/globalTeardown.js +8 -0
- package/test/import_export.integration.test.js +200 -0
- package/test/workflow.integration.test.js +314 -0
- package/test/workflow.robustness.test.js +195 -0
- package/vitest.config.js +16 -0
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
// __tests__/data.integration.test.js
|
|
2
|
+
import { ObjectId } from 'mongodb';
|
|
3
|
+
import {expect, describe, it, beforeEach, afterEach, beforeAll, afterAll, vi} from 'vitest';
|
|
4
|
+
import { Config } from '../src/config.js';
|
|
5
|
+
|
|
6
|
+
// --- Importations des modules de votre application ---
|
|
7
|
+
import {
|
|
8
|
+
insertData,
|
|
9
|
+
editData,
|
|
10
|
+
deleteData,
|
|
11
|
+
getModel, searchData, installPack
|
|
12
|
+
} from 'data-primals-engine/modules/data';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
modelsCollection as getAppModelsCollection,
|
|
16
|
+
getCollection,
|
|
17
|
+
getCollectionForUser as getAppUserCollection,
|
|
18
|
+
datasCollection // Accès direct pour vérifications
|
|
19
|
+
} from 'data-primals-engine/modules/mongodb';
|
|
20
|
+
import { Engine } from "data-primals-engine/engine";
|
|
21
|
+
import process from "node:process";
|
|
22
|
+
import fs from "node:fs";
|
|
23
|
+
import util from "node:util";
|
|
24
|
+
import {getRandom} from "data-primals-engine/core";
|
|
25
|
+
import {generateUniqueName, getUniquePort, initEngine} from "../src/setenv.js";
|
|
26
|
+
import {MongoDatabase} from "../src/engine.js";
|
|
27
|
+
|
|
28
|
+
let testModelsColInstance;
|
|
29
|
+
let testDatasColInstance;
|
|
30
|
+
|
|
31
|
+
// Cette fonction va remplacer la logique de votre beforeEach pour la création de contexte
|
|
32
|
+
async function setupTestContext() {
|
|
33
|
+
|
|
34
|
+
const currentTestModelName = generateUniqueName('relatedModel');
|
|
35
|
+
const currentRelatedModelName = generateUniqueName('comprehensiveModel');
|
|
36
|
+
|
|
37
|
+
// Créer un utilisateur unique pour ce test
|
|
38
|
+
const currentTestUser = {
|
|
39
|
+
username: generateUniqueName('testuserDataIntegration'),
|
|
40
|
+
userPlan: 'free',
|
|
41
|
+
email: generateUniqueName('test') + '@example.com'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const relatedModelDefinition = {
|
|
46
|
+
name: currentRelatedModelName,
|
|
47
|
+
_user: currentTestUser.username,
|
|
48
|
+
description: 'Model for testing relations',
|
|
49
|
+
fields: [
|
|
50
|
+
{name: 'relatedName', type: 'string', required: true, unique: true},
|
|
51
|
+
{name: 'relatedValue', type: 'number'}
|
|
52
|
+
],
|
|
53
|
+
maxRequestData: 10,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const comprehensiveTestModelDefinition = {
|
|
57
|
+
name: currentTestModelName,
|
|
58
|
+
_user: currentTestUser.username,
|
|
59
|
+
description: 'A model with all field types and constraints for testing',
|
|
60
|
+
fields: [
|
|
61
|
+
// String types
|
|
62
|
+
{name: 'stringRequired', type: 'string', required: true, hint: 'A required string'},
|
|
63
|
+
{name: 'stringUnique', type: 'string', unique: true, hint: 'A unique string'},
|
|
64
|
+
{name: 'stringMaxLength', type: 'string', maxlength: 5, hint: 'String with max length 5'},
|
|
65
|
+
{name: 'stringDefault', type: 'string', default: 'defaultString'},
|
|
66
|
+
{name: 'string_tLang', type: 'string_t', hint: 'Localizable string'},
|
|
67
|
+
{name: 'richtextField', type: 'richtext', maxlength: 200},
|
|
68
|
+
{name: 'passwordField', type: 'password'},
|
|
69
|
+
{name: 'emailField', type: 'email'},
|
|
70
|
+
{name: 'phoneField', type: 'phone'},
|
|
71
|
+
{name: 'urlField', type: 'url'},
|
|
72
|
+
// Number types
|
|
73
|
+
{name: 'number', type: 'number'},
|
|
74
|
+
{name: 'numberMinMax', type: 'number', min: 10, max: 20},
|
|
75
|
+
{name: 'numberStep', type: 'number', step: 0.5},
|
|
76
|
+
{name: 'numberDefault', type: 'number', default: 42},
|
|
77
|
+
// Boolean
|
|
78
|
+
{name: 'booleanField', type: 'boolean'},
|
|
79
|
+
{name: 'booleanDefault', type: 'boolean', default: true},
|
|
80
|
+
// Date & Datetime
|
|
81
|
+
{name: 'dateField', type: 'date'},
|
|
82
|
+
{name: 'datetimeField', type: 'datetime', default: 'now'},
|
|
83
|
+
{name: 'dateMinMax', type: 'date', min: '2023-01-01', max: '2023-12-31'},
|
|
84
|
+
// Enum
|
|
85
|
+
{name: 'enumField', type: 'enum', items: ['alpha', 'beta', 'gamma']},
|
|
86
|
+
{name: 'enumDefault', type: 'enum', items: ['one', 'two'], default: 'one'},
|
|
87
|
+
// Array
|
|
88
|
+
{name: 'arrayString', type: 'array', itemsType: 'string', maxItems: 2},
|
|
89
|
+
{name: 'arrayNumber', type: 'array', itemsType: 'number', minItems: 1},
|
|
90
|
+
{name: 'arrayEnum', type: 'array', itemsType: 'enum', items: ['a', 'b']},
|
|
91
|
+
// Relation
|
|
92
|
+
{name: 'relationSingle', type: 'relation', relation: currentRelatedModelName},
|
|
93
|
+
{name: 'relationMultiple', type: 'relation', relation: currentRelatedModelName, multiple: true},
|
|
94
|
+
// File (metadata validation)
|
|
95
|
+
{name: 'fileField', type: 'file', mimeTypes: ['image/png', 'application/pdf'], maxSize: 1024 * 10}, // 10KB
|
|
96
|
+
// Color
|
|
97
|
+
{name: 'colorField', type: 'color'},
|
|
98
|
+
// Code
|
|
99
|
+
{name: 'codeJsonField', type: 'code', language: 'json'},
|
|
100
|
+
{name: 'codeJsField', type: 'code', language: 'javascript', maxlength: 100},
|
|
101
|
+
// Object (simple validation, structure not deeply validated by default dataTypes.object)
|
|
102
|
+
{name: 'objectField', type: 'object'},
|
|
103
|
+
// Model & ModelField (validation of string format, not existence)
|
|
104
|
+
{name: 'modelNameField', type: 'model'},
|
|
105
|
+
{name: 'modelFieldNameField', type: 'modelField'}, // Note: modelField type expects an object {model: 'modelName', field: 'fieldName'}
|
|
106
|
+
],
|
|
107
|
+
maxRequestData: 50,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Insérer les modèles en base
|
|
111
|
+
await testModelsColInstance.insertMany([
|
|
112
|
+
comprehensiveTestModelDefinition,
|
|
113
|
+
relatedModelDefinition
|
|
114
|
+
]);
|
|
115
|
+
await testDatasColInstance.deleteMany({ _user: currentTestUser.username });
|
|
116
|
+
|
|
117
|
+
// Retourner toutes les variables nécessaires pour un test
|
|
118
|
+
return {
|
|
119
|
+
currentTestUser,
|
|
120
|
+
comprehensiveTestModelDefinition,
|
|
121
|
+
relatedModelDefinition,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
describe('Intégration des fonctions CRUD de données avec validation complète', () => {
|
|
126
|
+
|
|
127
|
+
beforeAll(async () =>{
|
|
128
|
+
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
|
|
129
|
+
await initEngine();
|
|
130
|
+
|
|
131
|
+
// Initialize collection instances after the engine is ready
|
|
132
|
+
testModelsColInstance = getAppModelsCollection;
|
|
133
|
+
testDatasColInstance = datasCollection;
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('insertData avec comprehensiveTestModel', () => {
|
|
137
|
+
it('devrait insérer des données valides pour tous les types de champs', async () => {
|
|
138
|
+
|
|
139
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
140
|
+
// 1. Créer un suffixe unique pour cette exécution de test
|
|
141
|
+
const testSuffix = getRandom(1000, 9999); // Génère un nombre aléatoire
|
|
142
|
+
|
|
143
|
+
// 2. Insérer les documents liés avec des noms uniques garantis
|
|
144
|
+
const relatedDoc1 = await insertData(relatedModelDefinition.name, {
|
|
145
|
+
relatedName: `Rel1_Unique_${testSuffix}`, // Nom unique
|
|
146
|
+
relatedValue: 100
|
|
147
|
+
}, {}, currentTestUser, false);
|
|
148
|
+
|
|
149
|
+
const relatedDoc2 = await insertData(relatedModelDefinition.name, {
|
|
150
|
+
relatedName: `Rel2_Unique_${testSuffix}`, // Nom unique
|
|
151
|
+
relatedValue: 200
|
|
152
|
+
}, {}, currentTestUser, false);
|
|
153
|
+
|
|
154
|
+
// S'assurer que les insertions précédentes ont réussi avant de continuer
|
|
155
|
+
expect(relatedDoc1.success, "L'insertion du document lié 1 a échoué").toBe(true);
|
|
156
|
+
expect(relatedDoc2.success, "L'insertion du document lié 2 a échoué").toBe(true);
|
|
157
|
+
|
|
158
|
+
// --- FIN DU SETUP AMÉLIORÉ ---
|
|
159
|
+
|
|
160
|
+
const validData = {
|
|
161
|
+
stringRequired: 'Valid String',
|
|
162
|
+
stringUnique: `UniqueValueForInsert_${testSuffix}`, // Valeur unique garantie
|
|
163
|
+
stringMaxLength: '12345',
|
|
164
|
+
string_tLang: 'Bonjour',
|
|
165
|
+
richtextField: '<p>Valid rich text</p>',
|
|
166
|
+
passwordField: 'validPassword123',
|
|
167
|
+
emailField: 'valid@example.com',
|
|
168
|
+
phoneField: '+1234567890',
|
|
169
|
+
urlField: 'https://example.com',
|
|
170
|
+
number: 123,
|
|
171
|
+
numberMinMax: 15,
|
|
172
|
+
numberStep: 10.5,
|
|
173
|
+
booleanField: true,
|
|
174
|
+
dateField: '2023-07-15',
|
|
175
|
+
dateMinMax: '2023-06-01',
|
|
176
|
+
enumField: 'alpha',
|
|
177
|
+
arrayString: ['one', 'two'],
|
|
178
|
+
arrayNumber: [1, 2, 3],
|
|
179
|
+
arrayEnum: ['a'],
|
|
180
|
+
relationSingle: relatedDoc1.insertedIds[0].toString(),
|
|
181
|
+
relationMultiple: [relatedDoc1.insertedIds[0].toString(), relatedDoc2.insertedIds[0].toString()],
|
|
182
|
+
// Le mock de fichier est OK, pas besoin de le rendre unique
|
|
183
|
+
fileField: { name: 'test.png', type: 'image/png', size: 1024 * 5, guid: `dummy-guid-png-${testSuffix}` },
|
|
184
|
+
colorField: '#FF0000',
|
|
185
|
+
codeJsonField: { "key": "value" },
|
|
186
|
+
codeJsField: 'console.log("hello");',
|
|
187
|
+
objectField: { a: 1, b: "test" },
|
|
188
|
+
modelNameField: 'someModelName',
|
|
189
|
+
modelFieldNameField: { model: 'someModelName', field: 'someFieldName' },
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, validData, {}, currentTestUser, false);
|
|
193
|
+
expect(result.success, `L'insertion principale a échoué: ${result.error}`).toBe(true);
|
|
194
|
+
expect(result.insertedIds).toHaveLength(1);
|
|
195
|
+
|
|
196
|
+
const insertedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(result.insertedIds[0]) });
|
|
197
|
+
expect(insertedDoc).not.toBeNull();
|
|
198
|
+
expect(insertedDoc.stringRequired).toBe('Valid String');
|
|
199
|
+
expect(insertedDoc.stringUnique).toBe(`UniqueValueForInsert_${testSuffix}`); // Vérifier la valeur unique
|
|
200
|
+
expect(insertedDoc.stringDefault).toBe('defaultString');
|
|
201
|
+
expect(new Date(insertedDoc.datetimeField).getFullYear()).toBe(new Date().getFullYear());
|
|
202
|
+
expect(insertedDoc.booleanDefault).toBe(true);
|
|
203
|
+
expect(insertedDoc.relationSingle.toString()).toBe(relatedDoc1.insertedIds[0].toString());
|
|
204
|
+
}, 10000);
|
|
205
|
+
|
|
206
|
+
describe('Validations des champs à l\'insertion', () => {
|
|
207
|
+
// --- STRING VALIDATIONS ---
|
|
208
|
+
it('devrait rejeter un string requis non fourni', async () => {
|
|
209
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
210
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringUnique: 'test' /* stringRequired manquant */ }, {}, currentTestUser, false);
|
|
211
|
+
expect(result.success).toBe(false);
|
|
212
|
+
expect(result.error).toContain('stringRequired');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('devrait rejeter un string trop long pour maxlength', async () => {
|
|
216
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
217
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', stringMaxLength: '123456' }, {}, currentTestUser, false);
|
|
218
|
+
expect(result.success).toBe(false);
|
|
219
|
+
expect(result.error).toContain('stringMaxLength');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('devrait rejeter une valeur dupliquée pour un champ unique (stringUnique)', async () => {
|
|
223
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
224
|
+
await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req1', stringUnique: 'duplicateMe', 'passwordField': 'pass', number: 1, enumField: 'test' }, {}, currentTestUser, false);
|
|
225
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req2', stringUnique: 'duplicateMe', 'passwordField': 'pass', number: 1, enumField: 'test' }, {}, currentTestUser, false);
|
|
226
|
+
expect(result.success).toBe(false);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// --- NUMBER VALIDATIONS ---
|
|
230
|
+
it('devrait rejeter un number non numérique', async () => {
|
|
231
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
232
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', number: 'not-a-number' }, {}, currentTestUser, false);
|
|
233
|
+
expect(result.success).toBe(false);
|
|
234
|
+
expect(result.error).toContain('number');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('devrait rejeter un number infur à min (numberMinMax)', async () => {
|
|
238
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
239
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', numberMinMax: 5 }, {}, currentTestUser, false);
|
|
240
|
+
expect(result.success).toBe(false);
|
|
241
|
+
expect(result.error).toContain('numberMinMax');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('devrait rejeter un number supérieur à max (numberMinMax)', async () => {
|
|
245
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
246
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', numberMinMax: 25 }, {}, currentTestUser, false);
|
|
247
|
+
expect(result.success).toBe(false);
|
|
248
|
+
expect(result.error).toContain('numberMinMax');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// --- ARRAY VALIDATIONS ---
|
|
252
|
+
it('devrait rejeter un array avec trop d\'items (arrayString maxItems: 2)', async () => {
|
|
253
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
254
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', arrayString: ['a', 'b', 'c'] }, {}, currentTestUser, false);
|
|
255
|
+
expect(result.success).toBe(false);
|
|
256
|
+
expect(result.error).toContain('arrayString');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('devrait rejeter un array avec pas assez d\'items (arrayNumber minItems: 1)', async () => {
|
|
260
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
261
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', arrayNumber: [] }, {}, currentTestUser, false);
|
|
262
|
+
expect(result.success).toBe(false);
|
|
263
|
+
expect(result.error).toContain('arrayNumber');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('devrait rejeter un array avec un item de type incorrect (arrayNumber)', async () => {
|
|
267
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
268
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', arrayNumber: [1, 'not-a-number', 3] }, {}, currentTestUser, false);
|
|
269
|
+
expect(result.success).toBe(false);
|
|
270
|
+
expect(result.error).toContain('arrayNumber');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// --- FILE VALIDATIONS (metadata) ---
|
|
274
|
+
it('devrait rejeter un file avec un mimeType non autorisé', async () => {
|
|
275
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
276
|
+
const mockFile = { name: 'test.txt', type: 'text/plain', size: 1024, guid: 'dummy-txt' };
|
|
277
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', fileField: mockFile }, {}, currentTestUser, false);
|
|
278
|
+
expect(result.success).toBe(false);
|
|
279
|
+
expect(result.error).toContain('image/png'); // Ou un message plus spécifique sur mimeTypes
|
|
280
|
+
expect(result.error).toContain('text/plain'); // Ou un message plus spécifique sur mimeTypes
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('devrait rejeter un file avec une taille supérieure à maxSize', async () => {
|
|
284
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
285
|
+
const mockFile = { name: 'large.png', type: 'image/png', size: 1024 * 15, guid: 'dummy-large' }; // 15KB > 10KB
|
|
286
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', fileField: mockFile }, {}, currentTestUser, false);
|
|
287
|
+
expect(result.success).toBe(false);
|
|
288
|
+
expect(result.error).toContain('fileField'); // Ou un message plus spécifique sur maxSize
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// --- EMAIL, URL, PHONE, COLOR (basic format) ---
|
|
292
|
+
it('devrait rejeter un email invalide', async () => {
|
|
293
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
294
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', emailField: 'not-an-email' }, {}, currentTestUser, false);
|
|
295
|
+
expect(result.success).toBe(false);
|
|
296
|
+
expect(result.error).toContain('emailField');
|
|
297
|
+
});
|
|
298
|
+
it('devrait rejeter une URL invalide', async () => {
|
|
299
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
300
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', urlField: 'not a url' }, {}, currentTestUser, false);
|
|
301
|
+
expect(result.success).toBe(false);
|
|
302
|
+
expect(result.error).toContain('urlField');
|
|
303
|
+
});
|
|
304
|
+
it('devrait rejeter une couleur invalide', async () => {
|
|
305
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
306
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', colorField: '#GGHHII' }, {}, currentTestUser, false);
|
|
307
|
+
expect(result.success).toBe(false);
|
|
308
|
+
expect(result.error).toContain('colorField');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// --- DATE / DATETIME VALIDATIONS ---
|
|
312
|
+
it('devrait rejeter une date invalide (format)', async () => {
|
|
313
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
314
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', dateField: 'not-a-date' }, {}, currentTestUser, false);
|
|
315
|
+
expect(result.success).toBe(false);
|
|
316
|
+
expect(result.error).toContain('dateField');
|
|
317
|
+
});
|
|
318
|
+
it('devrait rejeter une date en dehors de min/max (dateMinMax)', async () => {
|
|
319
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
320
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', dateMinMax: '2024-01-01' }, {}, currentTestUser, false);
|
|
321
|
+
expect(result.success).toBe(false);
|
|
322
|
+
expect(result.error).toContain('dateMinMax');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// TODO: Ajouter des tests pour les autres types et contraintes (password, richtext, code, object, model, modelField)
|
|
326
|
+
// Pour 'code' avec language 'json', tester l'insertion d'un JSON invalide si la validation va jusque-là.
|
|
327
|
+
// Pour 'relation', tester l'insertion d'un ObjectId non existant (si la validation vérifie l'existence).
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// --- NOUVEAU TEST POUR $find ---
|
|
331
|
+
it('devrait insérer des données avec des relations référencées par $find', async () => {
|
|
332
|
+
// 1. Insérer les documents liés qui seront trouvés par $find
|
|
333
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
334
|
+
const relatedDocA = await insertData(relatedModelDefinition.name, { relatedName: 'FindMeA', relatedValue: 111 }, {}, currentTestUser, false);
|
|
335
|
+
const relatedDocB = await insertData(relatedModelDefinition.name, { relatedName: 'FindMeB', relatedValue: 222 }, {}, currentTestUser, false);
|
|
336
|
+
const relatedDocC = await insertData(relatedModelDefinition.name, { relatedName: 'FindMeC', relatedValue: 222 }, {}, currentTestUser, false);
|
|
337
|
+
|
|
338
|
+
const relatedIdA = relatedDocA.insertedIds[0];
|
|
339
|
+
const relatedIdB = relatedDocB.insertedIds[0];
|
|
340
|
+
const relatedIdC = relatedDocC.insertedIds[0];
|
|
341
|
+
|
|
342
|
+
// 2. Prr les données du document principal utilisant $find
|
|
343
|
+
const dataWithFindRelations = {
|
|
344
|
+
stringRequired: 'DocWithFind',
|
|
345
|
+
stringUnique: 'UniqueFindValue',
|
|
346
|
+
passwordField: 'findPass',
|
|
347
|
+
enumField: 'beta',
|
|
348
|
+
number: 99,
|
|
349
|
+
// Utiliser $find pour la relation simple
|
|
350
|
+
relationSingle: { $find: { $eq: ["$relatedName", 'FindMeA'] } },
|
|
351
|
+
// Utiliser un tableau de $find pour la relation multiple
|
|
352
|
+
relationMultiple: { $find: { $eq: ["$relatedValue", 222] } },
|
|
353
|
+
// Inclure d'autres champs requis ou avec défauts si nécessaire
|
|
354
|
+
stringMaxLength: 'short',
|
|
355
|
+
emailField: 'find@example.com',
|
|
356
|
+
phoneField: '+19876543210',
|
|
357
|
+
urlField: 'https://find.example.com',
|
|
358
|
+
numberMinMax: 18,
|
|
359
|
+
dateField: '2023-11-20',
|
|
360
|
+
dateMinMax: '2023-11-20',
|
|
361
|
+
arrayNumber: [10],
|
|
362
|
+
fileField: { name: 'find.png', type: 'image/png', size: 1024, guid: 'dummy-find-png' },
|
|
363
|
+
colorField: '#123456',
|
|
364
|
+
codeJsonField: { "find": true },
|
|
365
|
+
codeJsField: 'console.log("find");',
|
|
366
|
+
objectField: { find: "test" },
|
|
367
|
+
modelNameField: 'findModel',
|
|
368
|
+
modelFieldNameField: { model: 'findModel', field: 'findField' },
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// 3. Insérer le document principal
|
|
372
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, dataWithFindRelations, {}, currentTestUser, false);
|
|
373
|
+
|
|
374
|
+
// 4. Vérifier le résultat de l'insertion
|
|
375
|
+
expect(result.success, `Insertion with $find failed: ${result.error}`).toBe(true);
|
|
376
|
+
expect(result.insertedIds).toHaveLength(1);
|
|
377
|
+
|
|
378
|
+
// 5. Récupérer le document inséré depuis la base de données
|
|
379
|
+
const insertedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(result.insertedIds[0]) });
|
|
380
|
+
expect(insertedDoc).not.toBeNull();
|
|
381
|
+
|
|
382
|
+
console.log({insertedDoc: insertedDoc, relatedIdB})
|
|
383
|
+
expect(insertedDoc.relationSingle).toBe(relatedIdA.toString());
|
|
384
|
+
|
|
385
|
+
expect(insertedDoc.relationMultiple).toBeInstanceOf(Array);
|
|
386
|
+
expect(insertedDoc.relationMultiple).toHaveLength(2);
|
|
387
|
+
// Convertir les ObjectIds en string pour une comparaison facile
|
|
388
|
+
const insertedRelationMultipleIds = insertedDoc.relationMultiple.map(id => id.toString());
|
|
389
|
+
expect(insertedRelationMultipleIds).toContain(relatedIdB.toString());
|
|
390
|
+
expect(insertedRelationMultipleIds).toContain(relatedIdC.toString());
|
|
391
|
+
|
|
392
|
+
// Vérifier d'autres champs pour s'assurer que le reste des données est correct
|
|
393
|
+
expect(insertedDoc.stringRequired).toBe('DocWithFind');
|
|
394
|
+
expect(insertedDoc.enumField).toBe('beta');
|
|
395
|
+
|
|
396
|
+
}, 10000); // Augmenter le timeout si nécessaire
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe('editData avec comprehensiveTestModel', async () => {
|
|
400
|
+
let docToEditId;
|
|
401
|
+
let relatedDocId;
|
|
402
|
+
|
|
403
|
+
const initTest = async () => {
|
|
404
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
405
|
+
// Insert a base document and a related document to be used in edit tests
|
|
406
|
+
const relatedInsert = await insertData(relatedModelDefinition.name, { relatedName: 'RelForEdit', relatedValue: 50 }, {}, currentTestUser, false);
|
|
407
|
+
relatedDocId = relatedInsert.insertedIds[0];
|
|
408
|
+
|
|
409
|
+
const initialData = {
|
|
410
|
+
stringRequired: 'Initial',
|
|
411
|
+
stringUnique: 'UniqueForEdit',
|
|
412
|
+
enumField: 'alpha',
|
|
413
|
+
passwordField: 'initialPass',
|
|
414
|
+
relationSingle: relatedDocId,
|
|
415
|
+
};
|
|
416
|
+
const insertResult = await insertData(comprehensiveTestModelDefinition.name, initialData, {}, currentTestUser, false);
|
|
417
|
+
docToEditId = insertResult.insertedIds[0];
|
|
418
|
+
|
|
419
|
+
return { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition };
|
|
420
|
+
};
|
|
421
|
+
it('devrait modifier des données valides', async () => {
|
|
422
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
423
|
+
const dataToEdit = { stringRequired: 'Edited String', numberMinMax: 12 };
|
|
424
|
+
const result = await editData(comprehensiveTestModelDefinition.name, docToEditId, dataToEdit, {}, currentTestUser);
|
|
425
|
+
|
|
426
|
+
expect(result.success, `Edit failed: ${result.error}`).toBe(true);
|
|
427
|
+
expect(result.modifiedCount).toBe(1);
|
|
428
|
+
|
|
429
|
+
const editedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(docToEditId) });
|
|
430
|
+
expect(editedDoc.stringRequired).toBe('Edited String');
|
|
431
|
+
expect(editedDoc.numberMinMax).toBe(12);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('devrait rejeter une modification avec un string requis vide', async () => {
|
|
435
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
436
|
+
const result = await editData(comprehensiveTestModelDefinition.name, docToEditId, { stringRequired: '' }, {}, currentTestUser);
|
|
437
|
+
expect(result.success).toBe(false);
|
|
438
|
+
expect(result.error).toContain('stringRequired');
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('devrait rejeter une modification avec un number hors des bornes (numberMinMax)', async () => {
|
|
442
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
443
|
+
const result = await editData(comprehensiveTestModelDefinition.name, docToEditId, { stringRequired: 'test', numberMinMax: 50 }, {}, currentTestUser);
|
|
444
|
+
expect(result.success).toBe(false);
|
|
445
|
+
expect(result.error).toContain('numberMinMax');
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('devrait rejeter une modification qui viole une contrainte unique (stringUnique)', async ({skip}) => {
|
|
449
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
450
|
+
// Insert another doc to create the unique conflict
|
|
451
|
+
|
|
452
|
+
await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'Other', stringUnique: 'UniqueValue', enumField: 'beta', passwordField: 'pass' }, {}, currentTestUser, false);
|
|
453
|
+
// Insert another doc to create the unique conflict
|
|
454
|
+
const res1 = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'Other', stringUnique: 'ExistingUniqueValue', enumField: 'beta', passwordField: 'pass' }, {}, currentTestUser, false);
|
|
455
|
+
|
|
456
|
+
const result = await editData(comprehensiveTestModelDefinition.name, { stringUnique: 'ExistingUniqueValue'}, { stringRequired: 'Other', stringUnique: 'UniqueValue', enumField: 'beta', passwordField: 'pass' }, {}, currentTestUser);
|
|
457
|
+
expect(result.success).toBe(false);
|
|
458
|
+
expect(result.error).toContain('UniqueValue');
|
|
459
|
+
expect(result.error).toContain('stringUnique');
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// TODO: Ajouter plus de tests de validation pour editData, similaires à ceux de insertData
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// --- Tests deleteData (repris de votre exemple, peuvent être gardés tels quels ou adaptés) ---
|
|
466
|
+
describe('deleteData (tests existants)', () => {
|
|
467
|
+
it('supprimer des données par ID (deleteData)', async () => {
|
|
468
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
469
|
+
const dataToDelete = { name: 'Objet à supprimer', value: 300 };
|
|
470
|
+
// Utiliser le modèle simple pour ces tests delete pour ne pas avoir à remplir tous les champs requis de comprehensive
|
|
471
|
+
const simpleModel = { name: 'simpleDeleteModel', _user: currentTestUser.username, description: '', fields: [{ name: 'name', type: 'string' }, { name: 'value', type: 'number' }] };
|
|
472
|
+
await testModelsColInstance.insertOne(simpleModel);
|
|
473
|
+
|
|
474
|
+
const insertResult = await insertData(simpleModel.name, dataToDelete, {}, currentTestUser, false);
|
|
475
|
+
const docId = insertResult.insertedIds[0];
|
|
476
|
+
|
|
477
|
+
const deleteResult = await deleteData(simpleModel.name, [docId], {}, currentTestUser, false);
|
|
478
|
+
|
|
479
|
+
expect(deleteResult.success).toBe(true);
|
|
480
|
+
expect(deleteResult.deletedCount).toBe(1);
|
|
481
|
+
|
|
482
|
+
const deletedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(docId) });
|
|
483
|
+
expect(deletedDoc).toBeNull();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('supprimer des données par filtre (deleteData)', async () => {
|
|
487
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
488
|
+
const simpleModel = { name: 'deleteModelTest', _user: currentTestUser.username, description: '',fields: [{ name: 'name', type: 'string' }, { name: 'value', type: 'number' }] };
|
|
489
|
+
await testModelsColInstance.insertOne(simpleModel);
|
|
490
|
+
|
|
491
|
+
await insertData(simpleModel.name, { name: 'Item Filtrable 1', value: 401 }, {}, currentTestUser, false);
|
|
492
|
+
await insertData(simpleModel.name, { name: 'Item Filtrable 2', value: 402 }, {}, currentTestUser, false);
|
|
493
|
+
await insertData(simpleModel.name, { name: 'Autre Item', value: 403 }, {}, currentTestUser, false);
|
|
494
|
+
|
|
495
|
+
const filter = { $regexMatch: {
|
|
496
|
+
input: '$name',
|
|
497
|
+
regex: "^Item Filtrable"
|
|
498
|
+
} };
|
|
499
|
+
const deleteResult = await deleteData(simpleModel.name, [], filter, currentTestUser, false);
|
|
500
|
+
|
|
501
|
+
expect(deleteResult.success).toBe(true);
|
|
502
|
+
expect(deleteResult.deletedCount).toBe(2);
|
|
503
|
+
|
|
504
|
+
const remainingCount = await testDatasColInstance.countDocuments({ _model: simpleModel.name, name: { $regex: "^Item Filtrable" } });
|
|
505
|
+
expect(remainingCount).toBe(0);
|
|
506
|
+
const totalRemaining = await testDatasColInstance.countDocuments({ _model: simpleModel.name });
|
|
507
|
+
expect(totalRemaining).toBe(1);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
describe('searchData avec comprehensiveTestModel et filtres $find', async () => {
|
|
512
|
+
let docId1, docId2;
|
|
513
|
+
let relatedDocId_A1, relatedDocId_A2, relatedDocId_B1;
|
|
514
|
+
|
|
515
|
+
const initTest =async () => {
|
|
516
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
517
|
+
|
|
518
|
+
// Insérer des documents liés
|
|
519
|
+
const relA1 = await insertData(relatedModelDefinition.name, { relatedName: 'Rel_Search_A1', relatedValue: 101 }, {}, currentTestUser, false);
|
|
520
|
+
relatedDocId_A1 = relA1.insertedIds[0].toString();
|
|
521
|
+
const relA2 = await insertData(relatedModelDefinition.name, { relatedName: 'Rel_Search_A2', relatedValue: 102 }, {}, currentTestUser, false);
|
|
522
|
+
relatedDocId_A2 = relA2.insertedIds[0].toString();
|
|
523
|
+
const relB1 = await insertData(relatedModelDefinition.name, { relatedName: 'Rel_Search_B1', relatedValue: 201 }, {}, currentTestUser, false);
|
|
524
|
+
relatedDocId_B1 = relB1.insertedIds[0].toString();
|
|
525
|
+
|
|
526
|
+
// Insérer des documents principaux
|
|
527
|
+
const data1 = {
|
|
528
|
+
stringRequired: 'SearchDoc1',
|
|
529
|
+
stringUnique: 'UniqueSearch1',
|
|
530
|
+
enumField: 'alpha',
|
|
531
|
+
passwordField: 'searchPass1',
|
|
532
|
+
relationSingle: relatedDocId_A1,
|
|
533
|
+
relationMultiple: [relatedDocId_A1, relatedDocId_A2],
|
|
534
|
+
number: 10,
|
|
535
|
+
};
|
|
536
|
+
const insertResult1 = await insertData(comprehensiveTestModelDefinition.name, data1, {}, currentTestUser, false);
|
|
537
|
+
docId1 = insertResult1.insertedIds[0].toString();
|
|
538
|
+
|
|
539
|
+
const data2 = {
|
|
540
|
+
stringRequired: 'SearchDoc2',
|
|
541
|
+
stringUnique: 'UniqueSearch2',
|
|
542
|
+
enumField: 'beta',
|
|
543
|
+
passwordField: 'searchPass2',
|
|
544
|
+
relationSingle: relatedDocId_B1,
|
|
545
|
+
relationMultiple: [relatedDocId_B1],
|
|
546
|
+
number: 20,
|
|
547
|
+
};
|
|
548
|
+
const insertResult2 = await insertData(comprehensiveTestModelDefinition.name, data2, {}, currentTestUser, false);
|
|
549
|
+
docId2 = insertResult2.insertedIds[0].toString();
|
|
550
|
+
|
|
551
|
+
return { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition };
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
it('devrait trouver un document par une condition sur une relation simple ($find)', async () => {
|
|
555
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
556
|
+
const searchParams = {
|
|
557
|
+
user: currentTestUser,
|
|
558
|
+
query: {
|
|
559
|
+
model: comprehensiveTestModelDefinition.name,
|
|
560
|
+
filter: {
|
|
561
|
+
relationSingle: {
|
|
562
|
+
"$find": { "$and": [{"$eq": ["$$this.relatedValue", 101]}] }
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
autoExpand: true,
|
|
566
|
+
depth: 8
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
const { data, count } = await searchData(searchParams);
|
|
570
|
+
expect(count).toBe(1);
|
|
571
|
+
expect(data).toHaveLength(1);
|
|
572
|
+
expect(data[0]._id.toString()).toBe(docId1.toString());
|
|
573
|
+
// Vérifier que la relation est bien populée si depth > 0
|
|
574
|
+
expect(data[0].relationSingle).toBeInstanceOf(Object);
|
|
575
|
+
expect(data[0].relationSingle.relatedName).toBe('Rel_Search_A1');
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('ne devrait pas trouver de document si la condition $find sur relation simple ne correspond pas', async () => {
|
|
579
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
580
|
+
const searchParams = {
|
|
581
|
+
user: currentTestUser,
|
|
582
|
+
query: {
|
|
583
|
+
model: comprehensiveTestModelDefinition.name,
|
|
584
|
+
filter: {
|
|
585
|
+
relationSingle: {
|
|
586
|
+
"$find": { "$eq": ["$$this.relatedName", "NonExistentRelName"] }
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
depth: 1
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
const { data, count } = await searchData(searchParams);
|
|
593
|
+
expect(count).toBe(0);
|
|
594
|
+
expect(data).toHaveLength(0);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('devrait trouver des documents par une condition sur une relation multiple ($find)', async () => {
|
|
598
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
599
|
+
const searchParams = {
|
|
600
|
+
user: currentTestUser,
|
|
601
|
+
query: {
|
|
602
|
+
model: comprehensiveTestModelDefinition.name,
|
|
603
|
+
filter: {
|
|
604
|
+
$and: [{$ne:["$stringRequired", null]}, {
|
|
605
|
+
relationMultiple: { // Le champ qui est un tableau de relations
|
|
606
|
+
"$find": {"$eq": ["$$this.relatedValue", 102]} // Condition sur les documents liés
|
|
607
|
+
}
|
|
608
|
+
}]
|
|
609
|
+
},
|
|
610
|
+
depth: 1
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
const { data, count } = await searchData(searchParams);
|
|
614
|
+
expect(count).toBe(1);
|
|
615
|
+
expect(data).toHaveLength(1);
|
|
616
|
+
expect(data[0]._id.toString()).toBe(docId1.toString());
|
|
617
|
+
// Note: searchData with depth > 0 will populate the relationMultiple array
|
|
618
|
+
expect(data[0].relationMultiple).toBeInstanceOf(Array);
|
|
619
|
+
expect(data[0].relationMultiple.some(rel => rel.relatedValue === 102)).toBe(true);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
it('devrait trouver des documents en combinant un filtre normal et un filtre $find', async () => {
|
|
624
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
625
|
+
const searchParams = {
|
|
626
|
+
user: currentTestUser,
|
|
627
|
+
query: {
|
|
628
|
+
model: comprehensiveTestModelDefinition.name,
|
|
629
|
+
filter: {
|
|
630
|
+
stringRequired: "SearchDoc1", // Filtre sur le modèle principal
|
|
631
|
+
relationSingle: {
|
|
632
|
+
"$find": {"$gt": ["$$this.relatedValue", 100] } // Filtre sur la relation
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
depth: 1
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
const { data, count } = await searchData(searchParams);
|
|
639
|
+
expect(count).toBe(1);
|
|
640
|
+
expect(data).toHaveLength(1);
|
|
641
|
+
expect(data[0]._id.toString()).toBe(docId1.toString());
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it('devrait retourner un tableau vide si $find ne correspond à aucun document lié', async () => {
|
|
645
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await initTest();
|
|
646
|
+
const searchParams = {
|
|
647
|
+
user: currentTestUser,
|
|
648
|
+
query: {
|
|
649
|
+
model: comprehensiveTestModelDefinition.name,
|
|
650
|
+
filter: {
|
|
651
|
+
relationMultiple: {
|
|
652
|
+
"$find": { "$eq": ["$$this.relatedName", "NonExistentRelNameForMultiple"] }
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
depth: 1
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
const { data, count } = await searchData(searchParams);
|
|
659
|
+
expect(count).toBe(0);
|
|
660
|
+
expect(data).toHaveLength(0);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// Dans C:/Dev/hackersonline-engine/test/data.integration.test.js
|
|
666
|
+
|
|
667
|
+
describe('installPack', () => {
|
|
668
|
+
let testPacksColInstance;
|
|
669
|
+
|
|
670
|
+
beforeAll(() => {
|
|
671
|
+
// Note: l'initialisation est déjà faite dans le beforeAll global.
|
|
672
|
+
// On s'assure juste d'avoir une référence si on en a besoin spécifiquement ici.
|
|
673
|
+
// Si vous n'avez pas suivi l'étape 1, vous pouvez l'initialiser ici.
|
|
674
|
+
testPacksColInstance = getCollection('packs');
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
beforeEach(async () => {
|
|
678
|
+
// On supprime les données de ce test
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('devrait installer un pack, créer les modèles et insérer les données avec relations via $link', async () => {
|
|
682
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
683
|
+
// 1. Définir le pack de données mock
|
|
684
|
+
const mockPack = {
|
|
685
|
+
name: 'test-pack-for-install',
|
|
686
|
+
models: [
|
|
687
|
+
{
|
|
688
|
+
name: 'packUser',
|
|
689
|
+
description: 'Modèle pour les utilisateurs du pack',
|
|
690
|
+
fields: [
|
|
691
|
+
{ name: 'username', type: 'string', required: true, unique: true },
|
|
692
|
+
{ name: 'level', type: 'number', default: 1 }
|
|
693
|
+
]
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
name: 'packItem',
|
|
697
|
+
description: 'Modèle pour les items du pack',
|
|
698
|
+
fields: [
|
|
699
|
+
{ name: 'itemName', type: 'string', required: true },
|
|
700
|
+
{ name: 'owner', type: 'relation', relation: 'packUser' }
|
|
701
|
+
]
|
|
702
|
+
}
|
|
703
|
+
],
|
|
704
|
+
data: {
|
|
705
|
+
all: { // 'all' pour les données non spécifiques à une langue
|
|
706
|
+
packUser: [
|
|
707
|
+
{ username: 'playerOne' },
|
|
708
|
+
{ username: 'playerTwo', level: 5 }
|
|
709
|
+
],
|
|
710
|
+
packItem: [
|
|
711
|
+
// La nouvelle syntaxe $link pour résoudre les relations
|
|
712
|
+
{ itemName: 'Sword', owner: { $link: { _model: 'packUser', username: 'playerOne' } } },
|
|
713
|
+
{ itemName: 'Shield', owner: { $link: { _model: 'packUser', username: 'playerTwo' } } }
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
// 2. Insérer le pack dans la collection pour le rendre disponible au test
|
|
720
|
+
const packInsertResult = await testPacksColInstance.insertOne(mockPack);
|
|
721
|
+
const packId = packInsertResult.insertedId;
|
|
722
|
+
|
|
723
|
+
// 3. Appeler la fonction à tester avec la nouvelle signature
|
|
724
|
+
const result = await installPack(console, packId, currentTestUser, 'en');
|
|
725
|
+
|
|
726
|
+
// 4. Assertions sur le résumé de l'installation
|
|
727
|
+
expect(result.success, `L'installation du pack a échoué: ${result.errors?.join('; ')}`).toBe(true);
|
|
728
|
+
expect(result.summary.models.installed).toHaveLength(2);
|
|
729
|
+
expect(result.summary.datas.inserted).toBe(4); // 2 utilisateurs + 2 items
|
|
730
|
+
expect(result.summary.datas.updated).toBe(2); // 2 items mis à jour avec la relation
|
|
731
|
+
|
|
732
|
+
// 5. Vérifier la création des modèles
|
|
733
|
+
const userModel = await testModelsColInstance.findOne({ name: 'packUser', _user: currentTestUser.username });
|
|
734
|
+
expect(userModel).not.toBeNull();
|
|
735
|
+
const itemModel = await testModelsColInstance.findOne({ name: 'packItem', _user: currentTestUser.username });
|
|
736
|
+
expect(itemModel).not.toBeNull();
|
|
737
|
+
expect(itemModel.fields.find(f => f.name === 'owner').relation).toBe('packUser');
|
|
738
|
+
|
|
739
|
+
// 6. Vérifier l'insertion des données
|
|
740
|
+
const insertedUsers = await testDatasColInstance.find({ _model: 'packUser', _user: currentTestUser.username }).toArray();
|
|
741
|
+
expect(insertedUsers).toHaveLength(2);
|
|
742
|
+
const insertedItems = await testDatasColInstance.find({ _model: 'packItem', _user: currentTestUser.username }).toArray();
|
|
743
|
+
expect(insertedItems).toHaveLength(2);
|
|
744
|
+
|
|
745
|
+
// 7. Vérifier la résolution des relations via $link
|
|
746
|
+
const playerOne = insertedUsers.find(u => u.username === 'playerOne');
|
|
747
|
+
const sword = insertedItems.find(i => i.itemName === 'Sword');
|
|
748
|
+
expect(sword.owner.toString()).toBe(playerOne._id.toString());
|
|
749
|
+
|
|
750
|
+
const playerTwo = insertedUsers.find(u => u.username === 'playerTwo');
|
|
751
|
+
const shield = insertedItems.find(i => i.itemName === 'Shield');
|
|
752
|
+
expect(shield.owner.toString()).toBe(playerTwo._id.toString());
|
|
753
|
+
}, 10000);
|
|
754
|
+
|
|
755
|
+
it("ne devrait installer que les modèles valides d'un pack et rapporter les erreurs", async () => {
|
|
756
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
757
|
+
const mockPackWithInvalidModel = {
|
|
758
|
+
name: 'invalid-model-pack',
|
|
759
|
+
models: [
|
|
760
|
+
{
|
|
761
|
+
// 'name' est requis, on l'omet pour causer une erreur
|
|
762
|
+
description: 'Modèle invalide sans nom',
|
|
763
|
+
fields: []
|
|
764
|
+
},
|
|
765
|
+
{ // Un modèle valide pour s'assurer que l'installation continue
|
|
766
|
+
name: 'validPackModel',
|
|
767
|
+
description: 'Un modèle valide',
|
|
768
|
+
fields: [{ name: 'title', type: 'string' }]
|
|
769
|
+
}
|
|
770
|
+
],
|
|
771
|
+
data: {}
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
const packInsertResult = await testPacksColInstance.insertOne(mockPackWithInvalidModel);
|
|
775
|
+
const packId = packInsertResult.insertedId;
|
|
776
|
+
|
|
777
|
+
const result = await installPack(console, packId, currentTestUser, 'en');
|
|
778
|
+
|
|
779
|
+
// Le succès global est faux s'il y a des erreurs
|
|
780
|
+
expect(result.success).toBe(false);
|
|
781
|
+
expect(result.errors).toHaveLength(1);
|
|
782
|
+
expect(result.errors[0]).toContain("Failed to install model 'unknown'"); // Le nom est inconnu
|
|
783
|
+
|
|
784
|
+
// Vérifier le résumé de l'option
|
|
785
|
+
expect(result.summary.models.failed).toHaveLength(1);
|
|
786
|
+
expect(result.summary.models.failed[0]).toBe('unknown');
|
|
787
|
+
expect(result.summary.models.installed).toHaveLength(1);
|
|
788
|
+
expect(result.summary.models.installed[0]).toBe('validPackModel');
|
|
789
|
+
|
|
790
|
+
// Vérifier en BDD que le modèle valide a bien été créé
|
|
791
|
+
const validModel = await testModelsColInstance.findOne({ name: 'validPackModel', _user: currentTestUser.username });
|
|
792
|
+
expect(validModel).not.toBeNull();
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
});
|