data-primals-engine 1.0.8 → 1.0.10
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/README.md +217 -0
- package/package.json +4 -7
- package/src/engine.js +2 -2
- package/src/i18n.js +2274 -0
- package/src/index.js +1 -1
- package/src/migrate.js +1 -1
- package/src/modules/data.js +124 -88
- package/src/modules/mongodb.js +0 -7
- 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/model.integration.test.js +219 -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,15 @@
|
|
|
1
|
+
import { initEngine } from '../src/setenv.js';
|
|
2
|
+
import { Config } from '../src/config.js';
|
|
3
|
+
import {getCollection} from "../src/modules/mongodb.js";
|
|
4
|
+
|
|
5
|
+
export default async function () {
|
|
6
|
+
|
|
7
|
+
// --- Configuration initiale ---
|
|
8
|
+
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
|
|
9
|
+
|
|
10
|
+
console.log('\n--- Global Setup: Initializing engine and database for all tests ---');
|
|
11
|
+
// The initEngine function is already a singleton, so this is safe.
|
|
12
|
+
|
|
13
|
+
// Initialisez et exportez les collections
|
|
14
|
+
console.log('--- Global Setup: Complete. Engine is running. ---');
|
|
15
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import {expect, describe, it, beforeEach, beforeAll, afterAll, vi} from 'vitest';
|
|
2
|
+
|
|
3
|
+
// --- Importations des modules de votre application ---
|
|
4
|
+
import {
|
|
5
|
+
createModel,
|
|
6
|
+
insertData,
|
|
7
|
+
exportData,
|
|
8
|
+
importData
|
|
9
|
+
} from 'data-primals-engine/modules/data';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
modelsCollection as getAppModelsCollection,
|
|
13
|
+
getCollectionForUser as getAppUserCollection,
|
|
14
|
+
} from 'data-primals-engine/modules/mongodb';
|
|
15
|
+
import {sleep} from "data-primals-engine/core";
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import {getUniquePort, initEngine} from "../src/setenv.js";
|
|
18
|
+
import {Config} from "../src/index.js";
|
|
19
|
+
|
|
20
|
+
// --- Données Mock ---
|
|
21
|
+
const mockUser = {
|
|
22
|
+
username: 'testuserImpex',
|
|
23
|
+
_user: 'testuserImpex',
|
|
24
|
+
userPlan: 'premium',
|
|
25
|
+
email: 'testImpex@example.com'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const impexTestModel = {
|
|
29
|
+
name: 'impex_products',
|
|
30
|
+
description: 'test',
|
|
31
|
+
_user: mockUser.username,
|
|
32
|
+
fields: [
|
|
33
|
+
{ name: 'name', type: 'string', required: true },
|
|
34
|
+
{ name: 'sku', type: 'string', unique: true },
|
|
35
|
+
{ name: 'price', type: 'number', required: true },
|
|
36
|
+
{ name: 'inStock', type: 'boolean', default: true },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// --- Setup de l'environnement de test ---
|
|
41
|
+
let mongod;
|
|
42
|
+
let testDbUri;
|
|
43
|
+
const testDbName = 'testIntegrationDbHO_Impex';
|
|
44
|
+
let testModelsColInstance;
|
|
45
|
+
let testDatasColInstance;
|
|
46
|
+
let engineInstance;
|
|
47
|
+
const port = getUniquePort(); // Port unique pour cette suite de tests
|
|
48
|
+
function blobToFile(theBlob, fileName){
|
|
49
|
+
//A Blob() is almost a File() - it's just missing the two properties below which we will add
|
|
50
|
+
theBlob.lastModifiedDate = new Date();
|
|
51
|
+
theBlob.name = fileName;
|
|
52
|
+
return theBlob;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
beforeAll(async () =>{
|
|
56
|
+
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
|
|
57
|
+
await initEngine();
|
|
58
|
+
})
|
|
59
|
+
// --- Début des tests ---
|
|
60
|
+
describe('Intégration des fonctions d\'Import/Export', () => {
|
|
61
|
+
|
|
62
|
+
// Préparation avant chaque test du bloc
|
|
63
|
+
beforeEach(async () => {
|
|
64
|
+
testModelsColInstance = getAppModelsCollection;
|
|
65
|
+
testDatasColInstance = getAppUserCollection(mockUser);
|
|
66
|
+
|
|
67
|
+
// Nettoyage complet pour un état propre
|
|
68
|
+
await testDatasColInstance.deleteMany({ _user: "testuserImpex"});
|
|
69
|
+
|
|
70
|
+
if( await testModelsColInstance.find({ name: impexTestModel.name, _user: mockUser.username }).count() === 0) {
|
|
71
|
+
await testModelsColInstance.insertOne(impexTestModel);
|
|
72
|
+
}
|
|
73
|
+
// Insérer des données de base pour les tests d'exportation
|
|
74
|
+
await insertData(impexTestModel.name, [
|
|
75
|
+
{ name: 'Produit A', sku: 'SKU-A', price: 10.50, inStock: true },
|
|
76
|
+
{ name: 'Produit B', sku: 'SKU-B', price: 25.00, inStock: false },
|
|
77
|
+
{ name: 'Produit C', sku: 'SKU-C', price: 99.99, inStock: true },
|
|
78
|
+
], {}, mockUser, false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('Export de données', () => {
|
|
82
|
+
it('devrait exporter les données en format JSON', async () => {
|
|
83
|
+
const res= await exportData({
|
|
84
|
+
models: [impexTestModel.name],
|
|
85
|
+
depth: 1,
|
|
86
|
+
}, mockUser);
|
|
87
|
+
|
|
88
|
+
expect(res.success).toBeTruthy();
|
|
89
|
+
|
|
90
|
+
const data = res.data[impexTestModel.name];
|
|
91
|
+
expect(data).toBeInstanceOf(Array);
|
|
92
|
+
expect(data).toHaveLength(3);
|
|
93
|
+
expect(data[0]).toMatchObject({ name: 'Produit A', sku: 'SKU-A', price: 10.50 });
|
|
94
|
+
// Les champs système (_id, _model, _user, _hash) ne devraient pas être exportés par défaut
|
|
95
|
+
expect(data[0]).not.toHaveProperty('_model');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('devrait lever une exception si le modèle n\'existe pas', async () => {
|
|
99
|
+
// La fonction devrait rejeter la promesse ou lancer une erreur
|
|
100
|
+
await expect((await exportData('model_inexistant', 'json', mockUser)).success).toBeFalsy();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('Import de données', () => {
|
|
105
|
+
it('devrait importer des données depuis une chaîne JSON', async () => {
|
|
106
|
+
const jsonDataToImport = [
|
|
107
|
+
{ name: 'Produit D', sku: 'SKU-D', price: 1.00, inStock: true },
|
|
108
|
+
{ name: 'Produit E', sku: 'SKU-E', price: 2.00 } // inStock utilisera la valeur par défaut
|
|
109
|
+
];
|
|
110
|
+
const jsonString = JSON.stringify(jsonDataToImport);
|
|
111
|
+
|
|
112
|
+
// Exécution de la fonction d'import (hypothétique)x
|
|
113
|
+
var blob = new Blob([jsonString], {type: "application/json"});
|
|
114
|
+
fs.writeFileSync('test.json', jsonString);
|
|
115
|
+
blob.path = 'test.json';
|
|
116
|
+
blob.originalFilename = 'test.json';
|
|
117
|
+
const result = await importData({model: impexTestModel.name}, {file: blobToFile(blob,"test.json")}, mockUser);
|
|
118
|
+
|
|
119
|
+
// Vérifications du résultat de l'opération
|
|
120
|
+
expect(result.success).toBe(true);
|
|
121
|
+
expect(result.jobId).not.toBeNull();
|
|
122
|
+
|
|
123
|
+
await sleep(2000);
|
|
124
|
+
|
|
125
|
+
// Vérification directe en base de données
|
|
126
|
+
const importedDocs = await testDatasColInstance.find({
|
|
127
|
+
_model: impexTestModel.name,
|
|
128
|
+
sku: { $in: ['SKU-D', 'SKU-E'] }
|
|
129
|
+
}).toArray();
|
|
130
|
+
|
|
131
|
+
expect(importedDocs).toHaveLength(2);
|
|
132
|
+
const docD = importedDocs.find(d => d.sku === 'SKU-D');
|
|
133
|
+
const docE = importedDocs.find(d => d.sku === 'SKU-E');
|
|
134
|
+
|
|
135
|
+
expect(docD.name).toBe('Produit D');
|
|
136
|
+
expect(docD.inStock).toBe(true); // Vérification de la valeur par défaut
|
|
137
|
+
expect(docE.price).toBe(2.00);
|
|
138
|
+
|
|
139
|
+
}, 5000);
|
|
140
|
+
|
|
141
|
+
it('devrait importer des données depuis une chaîne CSV et convertir les types', async () => {
|
|
142
|
+
const csvStringToImport = `name,sku,price,inStock\nProduit F,SKU-F,3.55,true\nProduit G,SKU-G,4.99,false`;
|
|
143
|
+
|
|
144
|
+
// Exécution de la fonction d'import (hypothétique)x
|
|
145
|
+
var blob = new Blob([csvStringToImport], {type: "text/csv"});
|
|
146
|
+
fs.writeFileSync('test.csv', csvStringToImport);
|
|
147
|
+
blob.path = 'test.csv';
|
|
148
|
+
blob.originalFilename = 'test.csv';
|
|
149
|
+
|
|
150
|
+
// Exécution de la fonction d'import
|
|
151
|
+
const result = await importData({model:impexTestModel.name}, {file: blobToFile(blob,"test.csv")}, mockUser);
|
|
152
|
+
|
|
153
|
+
console.log(result)
|
|
154
|
+
// Vérifications du résultat
|
|
155
|
+
expect(result.success).toBe(true);
|
|
156
|
+
|
|
157
|
+
await sleep(2000);
|
|
158
|
+
|
|
159
|
+
// Vérification en base de données
|
|
160
|
+
const importedDocs = await testDatasColInstance.find({
|
|
161
|
+
_model: impexTestModel.name,
|
|
162
|
+
sku: { $in: ['SKU-F', 'SKU-G'] }
|
|
163
|
+
}).toArray();
|
|
164
|
+
|
|
165
|
+
expect(importedDocs).toHaveLength(2);
|
|
166
|
+
const docF = importedDocs.find(d => d.sku === 'SKU-F');
|
|
167
|
+
const docG = importedDocs.find(d => d.sku === 'SKU-G');
|
|
168
|
+
|
|
169
|
+
// Vérification de la conversion des types
|
|
170
|
+
expect(docF.price).toBe(3.55);
|
|
171
|
+
expect(docF.inStock).toBe(true);
|
|
172
|
+
expect(docG.inStock).toBe(false);
|
|
173
|
+
}, 20000);
|
|
174
|
+
|
|
175
|
+
it('devrait rejeter les lignes invalides et rapporter les erreurs', async () => {
|
|
176
|
+
const csvStringToImport = `name,sku,price,inStock
|
|
177
|
+
Valide H,SKU-H,10,true
|
|
178
|
+
,SKU-I,20,true
|
|
179
|
+
Valide J,SKU-J,,false
|
|
180
|
+
Valide K,SKU-A,40,true`;
|
|
181
|
+
|
|
182
|
+
// Exécution de la fonction d'import (hypothétique)x
|
|
183
|
+
var blob = new Blob([csvStringToImport], {type: "text/csv"});
|
|
184
|
+
fs.writeFileSync('test.csv', csvStringToImport);
|
|
185
|
+
blob.path = 'test.csv';
|
|
186
|
+
blob.originalFilename = 'test.csv';
|
|
187
|
+
|
|
188
|
+
const result = await importData(impexTestModel.name, {file: blobToFile(blob,"test.csv")}, 'csv', mockUser);
|
|
189
|
+
|
|
190
|
+
console.log(result);
|
|
191
|
+
// Vérifications du résultat
|
|
192
|
+
expect(result.success).toBe(true); // L'opération globale a des erreurs
|
|
193
|
+
expect(result.job.status).toBe('failed'); // L'opération globale a des erreurs
|
|
194
|
+
|
|
195
|
+
// Vérifier que seule les données valides sont en BDD
|
|
196
|
+
const count = await testDatasColInstance.countDocuments({ _model: impexTestModel.name });
|
|
197
|
+
expect(count).toBe(3);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,219 @@
|
|
|
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
|
+
import {
|
|
7
|
+
modelsCollection as getAppModelsCollection,
|
|
8
|
+
datasCollection // Accès direct pour vérifications
|
|
9
|
+
} from 'data-primals-engine/modules/mongodb';
|
|
10
|
+
import {generateUniqueName, getUniquePort, initEngine} from "../src/setenv.js";
|
|
11
|
+
import {editModel} from "../src/modules/data.js";
|
|
12
|
+
import {getCollectionForUser} from "../src/modules/mongodb.js";
|
|
13
|
+
|
|
14
|
+
let testModelsColInstance;
|
|
15
|
+
let testDatasColInstance;
|
|
16
|
+
|
|
17
|
+
let testModelId;
|
|
18
|
+
|
|
19
|
+
// Cette fonction va remplacer la logique de votre beforeEach pour la création de contexte
|
|
20
|
+
async function setupTestContext() {
|
|
21
|
+
|
|
22
|
+
const currentTestModelName = generateUniqueName('relatedModel');
|
|
23
|
+
const currentRelatedModelName = generateUniqueName('comprehensiveModel');
|
|
24
|
+
|
|
25
|
+
// Créer un utilisateur unique pour ce test
|
|
26
|
+
const currentTestUser = {
|
|
27
|
+
username: generateUniqueName('testuserDataIntegration'),
|
|
28
|
+
userPlan: 'free',
|
|
29
|
+
email: generateUniqueName('test') + '@example.com'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
const relatedModelDefinition = {
|
|
34
|
+
name: currentRelatedModelName,
|
|
35
|
+
_user: currentTestUser.username,
|
|
36
|
+
description: 'Model for testing relations',
|
|
37
|
+
fields: [
|
|
38
|
+
{name: 'relatedName', type: 'string', required: true, unique: true},
|
|
39
|
+
{name: 'relatedValue', type: 'number'}
|
|
40
|
+
],
|
|
41
|
+
maxRequestData: 10,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const comprehensiveTestModelDefinition = {
|
|
45
|
+
name: currentTestModelName,
|
|
46
|
+
_user: currentTestUser.username,
|
|
47
|
+
description: 'A model with all field types and constraints for testing',
|
|
48
|
+
fields: [
|
|
49
|
+
// String types
|
|
50
|
+
{name: 'stringRequired', type: 'string', required: true, hint: 'A required string'},
|
|
51
|
+
{name: 'stringUnique', type: 'string', unique: true, hint: 'A unique string'},
|
|
52
|
+
{name: 'stringMaxLength', type: 'string', maxlength: 5, hint: 'String with max length 5'},
|
|
53
|
+
{name: 'stringDefault', type: 'string', default: 'defaultString'},
|
|
54
|
+
{name: 'string_tLang', type: 'string_t', hint: 'Localizable string'},
|
|
55
|
+
{name: 'richtextField', type: 'richtext', maxlength: 200},
|
|
56
|
+
{name: 'passwordField', type: 'password'},
|
|
57
|
+
{name: 'emailField', type: 'email'},
|
|
58
|
+
{name: 'phoneField', type: 'phone'},
|
|
59
|
+
{name: 'urlField', type: 'url'},
|
|
60
|
+
// Number types
|
|
61
|
+
{name: 'number', type: 'number'},
|
|
62
|
+
{name: 'numberMinMax', type: 'number', min: 10, max: 20},
|
|
63
|
+
{name: 'numberStep', type: 'number', step: 0.5},
|
|
64
|
+
{name: 'numberDefault', type: 'number', default: 42},
|
|
65
|
+
// Boolean
|
|
66
|
+
{name: 'booleanField', type: 'boolean'},
|
|
67
|
+
{name: 'booleanDefault', type: 'boolean', default: true},
|
|
68
|
+
// Date & Datetime
|
|
69
|
+
{name: 'dateField', type: 'date'},
|
|
70
|
+
{name: 'datetimeField', type: 'datetime', default: 'now'},
|
|
71
|
+
{name: 'dateMinMax', type: 'date', min: '2023-01-01', max: '2023-12-31'},
|
|
72
|
+
// Enum
|
|
73
|
+
{name: 'enumField', type: 'enum', items: ['alpha', 'beta', 'gamma']},
|
|
74
|
+
{name: 'enumDefault', type: 'enum', items: ['one', 'two'], default: 'one'},
|
|
75
|
+
// Array
|
|
76
|
+
{name: 'arrayString', type: 'array', itemsType: 'string', maxItems: 2},
|
|
77
|
+
{name: 'arrayNumber', type: 'array', itemsType: 'number', minItems: 1},
|
|
78
|
+
{name: 'arrayEnum', type: 'array', itemsType: 'enum', items: ['a', 'b']},
|
|
79
|
+
// Relation
|
|
80
|
+
{name: 'relationSingle', type: 'relation', relation: currentRelatedModelName},
|
|
81
|
+
{name: 'relationMultiple', type: 'relation', relation: currentRelatedModelName, multiple: true},
|
|
82
|
+
// File (metadata validation)
|
|
83
|
+
{name: 'fileField', type: 'file', mimeTypes: ['image/png', 'application/pdf'], maxSize: 1024 * 10}, // 10KB
|
|
84
|
+
// Color
|
|
85
|
+
{name: 'colorField', type: 'color'},
|
|
86
|
+
// Code
|
|
87
|
+
{name: 'codeJsonField', type: 'code', language: 'json'},
|
|
88
|
+
{name: 'codeJsField', type: 'code', language: 'javascript', maxlength: 100},
|
|
89
|
+
// Object (simple validation, structure not deeply validated by default dataTypes.object)
|
|
90
|
+
{name: 'objectField', type: 'object'},
|
|
91
|
+
// Model & ModelField (validation of string format, not existence)
|
|
92
|
+
{name: 'modelNameField', type: 'model'},
|
|
93
|
+
{name: 'modelFieldNameField', type: 'modelField'}, // Note: modelField type expects an object {model: 'modelName', field: 'fieldName'}
|
|
94
|
+
],
|
|
95
|
+
maxRequestData: 50,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Insérer les modèles en base
|
|
99
|
+
const result= await testModelsColInstance.insertMany([
|
|
100
|
+
comprehensiveTestModelDefinition,
|
|
101
|
+
relatedModelDefinition
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
testModelId = result.insertedIds[0];
|
|
105
|
+
await testDatasColInstance.deleteMany({ _user: currentTestUser.username });
|
|
106
|
+
await testDatasColInstance.deleteMany({ _model: { $in: [comprehensiveTestModelDefinition.name, 'renamedTestModel'] } });
|
|
107
|
+
// Retourner toutes les variables nécessaires pour un test
|
|
108
|
+
return {
|
|
109
|
+
currentTestUser,
|
|
110
|
+
comprehensiveTestModelDefinition,
|
|
111
|
+
relatedModelDefinition,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
describe('CRUD on model definitions and integrity tests', () => {
|
|
116
|
+
|
|
117
|
+
beforeAll(async () =>{
|
|
118
|
+
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
|
|
119
|
+
await initEngine();
|
|
120
|
+
|
|
121
|
+
// Initialize collection instances after the engine is ready
|
|
122
|
+
testModelsColInstance = getAppModelsCollection;
|
|
123
|
+
testDatasColInstance = datasCollection;
|
|
124
|
+
})
|
|
125
|
+
describe('editModel unit tests', () => {
|
|
126
|
+
|
|
127
|
+
it('should create and drop index when field.index is toggled (premium user)', async () => {
|
|
128
|
+
// --- SETUP ---
|
|
129
|
+
const { currentTestUser, comprehensiveTestModelDefinition } = await setupTestContext();
|
|
130
|
+
const premiumUser = { ...currentTestUser, userPlan: 'premium' };
|
|
131
|
+
const dataCollection = getCollectionForUser(premiumUser);
|
|
132
|
+
const fieldToIndex = 'stringUnique'; // Utiliser un champ qui existe vraiment dans le modèle
|
|
133
|
+
|
|
134
|
+
// --- VERIFICATION INITIALE ---
|
|
135
|
+
// S'assurer qu'aucun index n'existe au départ.
|
|
136
|
+
// On enveloppe l'appel dans un try/catch pour gérer le cas où la collection n'existe pas.
|
|
137
|
+
let initialIndexes = [];
|
|
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
|
+
}
|
|
146
|
+
expect(initialIndexes.some(i => i.key[fieldToIndex] === 1)).toBe(false);
|
|
147
|
+
|
|
148
|
+
// --- ACTION 1 : AJOUTER UN INDEX ---
|
|
149
|
+
const modelWithIndex = {
|
|
150
|
+
...comprehensiveTestModelDefinition,
|
|
151
|
+
fields: comprehensiveTestModelDefinition.fields.map(f =>
|
|
152
|
+
f.name === fieldToIndex ? { ...f, index: true } : f
|
|
153
|
+
),
|
|
154
|
+
};
|
|
155
|
+
await editModel(premiumUser, testModelId, modelWithIndex);
|
|
156
|
+
|
|
157
|
+
// --- VERIFICATION 1 ---
|
|
158
|
+
// Maintenant, la collection et l'index doivent exister.
|
|
159
|
+
const indexesAfterCreation = await dataCollection.indexes();
|
|
160
|
+
const newIndex = indexesAfterCreation.find(i => i.key[fieldToIndex] === 1);
|
|
161
|
+
|
|
162
|
+
expect(newIndex).toBeDefined();
|
|
163
|
+
// Le filtre partiel est crucial pour que l'index ne s'applique qu'aux bonnes données
|
|
164
|
+
expect(newIndex.partialFilterExpression).toEqual({
|
|
165
|
+
_model: comprehensiveTestModelDefinition.name,
|
|
166
|
+
_user: premiumUser.username,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// --- ACTION 2 : SUPPRIMER L'INDEX ---
|
|
170
|
+
const modelWithoutIndex = {
|
|
171
|
+
...comprehensiveTestModelDefinition,
|
|
172
|
+
fields: comprehensiveTestModelDefinition.fields.map(f =>
|
|
173
|
+
f.name === fieldToIndex ? { ...f, index: false } : f
|
|
174
|
+
),
|
|
175
|
+
};
|
|
176
|
+
await editModel(premiumUser, testModelId, modelWithoutIndex);
|
|
177
|
+
|
|
178
|
+
// --- VERIFICATION 2 ---
|
|
179
|
+
const indexesAfterDeletion = await dataCollection.indexes();
|
|
180
|
+
expect(indexesAfterDeletion.some(i => i.key[fieldToIndex] === 1)).toBe(false);
|
|
181
|
+
}, 20000); // Augmenter le timeout si nécessaire pour les opérations d'index
|
|
182
|
+
|
|
183
|
+
it('should not save extra, non-defined fields in the model definition', async () => {
|
|
184
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
185
|
+
// 1. Préparer les données avec un champ non sollicité
|
|
186
|
+
const updatedModelData = {
|
|
187
|
+
...comprehensiveTestModelDefinition,
|
|
188
|
+
description: 'An updated description',
|
|
189
|
+
extraBogusField: 'this should not be saved', // Champ arbitraire
|
|
190
|
+
anotherOne: { nested: true }
|
|
191
|
+
};
|
|
192
|
+
// 2. Appeler la fonction d'édition
|
|
193
|
+
const result = await editModel(currentTestUser, testModelId, updatedModelData);
|
|
194
|
+
expect(result.success).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
it('should return an error if trying to edit a non-existent model', async () => {
|
|
197
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
198
|
+
const nonExistentId = new ObjectId();
|
|
199
|
+
const result = await editModel(currentTestUser, nonExistentId, comprehensiveTestModelDefinition);
|
|
200
|
+
expect(result.success).toBe(false);
|
|
201
|
+
expect(result.statusCode).toBe(404);
|
|
202
|
+
expect(result.error).toContain('introuvable');
|
|
203
|
+
});
|
|
204
|
+
it('should return an error if the new model structure is invalid', async () => {
|
|
205
|
+
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition } = await setupTestContext();
|
|
206
|
+
const invalidModelData = {
|
|
207
|
+
...comprehensiveTestModelDefinition,
|
|
208
|
+
fields: [
|
|
209
|
+
{ name: 'title' } // Le champ 'type' est manquant, ce qui est invalide
|
|
210
|
+
]
|
|
211
|
+
};
|
|
212
|
+
const result = await editModel(currentTestUser, testModelId, invalidModelData);
|
|
213
|
+
expect(result.success).toBe(false);
|
|
214
|
+
// L'erreur est levée par validateModelStructure, donc le message peut varier
|
|
215
|
+
expect(result.error).toBeDefined();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
});
|