data-primals-engine 1.5.0 → 1.5.2
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/README.md +37 -0
- package/client/src/AddWidgetTypeModal.jsx +47 -43
- package/client/src/App.jsx +2 -6
- package/client/src/App.scss +13 -1
- package/client/src/AssistantChat.jsx +363 -323
- package/client/src/AssistantChat.scss +27 -10
- package/client/src/Dashboard.jsx +480 -396
- package/client/src/Dashboard.scss +1 -1
- package/client/src/DashboardHtmlViewItem.jsx +147 -0
- package/client/src/DashboardView.jsx +654 -569
- package/client/src/DataEditor.jsx +10 -3
- package/client/src/DataLayout.jsx +807 -755
- package/client/src/DataLayout.scss +14 -0
- package/client/src/DataTable.jsx +39 -75
- package/client/src/Dialog.scss +1 -1
- package/client/src/Field.jsx +2057 -1825
- package/client/src/FlexViewCard.jsx +44 -0
- package/client/src/HistoryDialog.jsx +69 -14
- package/client/src/HtmlViewBuilderModal.jsx +91 -0
- package/client/src/HtmlViewBuilderModal.scss +18 -0
- package/client/src/HtmlViewCard.jsx +44 -0
- package/client/src/HtmlViewCard.scss +35 -0
- package/client/src/KanbanCard.jsx +1 -2
- package/client/src/ModelCreator.jsx +5 -4
- package/client/src/ModelCreatorField.jsx +51 -4
- package/client/src/ModelList.jsx +280 -236
- package/client/src/Notification.jsx +136 -136
- package/client/src/Notification.scss +0 -18
- package/client/src/Pagination.jsx +5 -3
- package/client/src/RelationField.jsx +354 -258
- package/client/src/RelationSelectorWidget.jsx +173 -0
- package/client/src/contexts/ModelContext.jsx +10 -1
- package/client/src/contexts/UIContext.jsx +72 -63
- package/client/src/filter.js +263 -212
- package/client/src/hooks/useValidation.js +75 -0
- package/client/src/translations.js +24 -24
- package/package.json +7 -6
- package/src/constants.js +1 -1
- package/src/core.js +8 -1
- package/src/defaultModels.js +1596 -1544
- package/src/engine.js +85 -43
- package/src/events.js +137 -113
- package/src/i18n.js +710 -10
- package/src/index.js +3 -0
- package/src/modules/assistant/assistant.js +253 -134
- package/src/modules/assistant/constants.js +2 -1
- package/src/modules/bucket.js +2 -1
- package/src/modules/data/data.core.js +118 -92
- package/src/modules/data/data.history.js +555 -492
- package/src/modules/data/data.js +3 -53
- package/src/modules/data/data.operations.js +3381 -3231
- package/src/modules/data/data.relations.js +686 -686
- package/src/modules/data/data.routes.js +1879 -1821
- package/src/modules/data/data.validation.js +81 -2
- package/src/modules/file.js +247 -238
- package/src/modules/user.js +1 -0
- package/src/modules/workflow.js +2 -2
- package/src/openai.jobs.js +3 -2
- package/src/packs.js +5482 -5478
- package/src/sso.js +2 -2
- package/src/workers/import-export-worker.js +1 -1
- package/test/data.history.integration.test.js +264 -192
- package/test/data.integration.test.js +149 -3
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// __tests__/data.integration.test.js
|
|
2
2
|
import { ObjectId } from 'mongodb';
|
|
3
3
|
import {vi, expect, describe, it, afterEach,beforeEach, beforeAll, afterAll} from 'vitest';
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
4
7
|
import { Config } from '../src/config.js';
|
|
5
8
|
import {
|
|
6
9
|
insertData,
|
|
@@ -17,6 +20,7 @@ import {
|
|
|
17
20
|
import {getRandom} from "../src/core.js";
|
|
18
21
|
import {generateUniqueName, initEngine} from "../src/setenv.js";
|
|
19
22
|
import {purgeData} from "../src/modules/data/data.history.js";
|
|
23
|
+
import {removeFile} from "../src/modules/file.js";
|
|
20
24
|
|
|
21
25
|
let testModelsColInstance;
|
|
22
26
|
let testDatasColInstance;
|
|
@@ -88,7 +92,7 @@ async function setupTestContext() {
|
|
|
88
92
|
{name: 'relationSingle', type: 'relation', relation: currentRelatedModelName},
|
|
89
93
|
{name: 'relationMultiple', type: 'relation', relation: currentRelatedModelName, multiple: true},
|
|
90
94
|
// File (metadata validation)
|
|
91
|
-
{name: 'fileField', type: 'file', mimeTypes: ['image/png', 'application/pdf'], maxSize: 1024 * 10}, // 10KB
|
|
95
|
+
{name: 'fileField', type: 'file', mimeTypes: ['image/png', 'application/pdf', 'text/plain'], maxSize: 1024 * 10}, // 10KB
|
|
92
96
|
// Color
|
|
93
97
|
{name: 'colorField', type: 'color'},
|
|
94
98
|
// Code
|
|
@@ -126,7 +130,7 @@ async function setupTestContext() {
|
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
let engine;
|
|
129
|
-
describe('
|
|
133
|
+
describe('Data integration tests (CRUD, validation...)', () => {
|
|
130
134
|
|
|
131
135
|
beforeAll(async () =>{
|
|
132
136
|
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
|
|
@@ -285,7 +289,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
285
289
|
// --- FILE VALIDATIONS (metadata) ---
|
|
286
290
|
it('devrait rejeter un file avec un mimeType non autorisé', async () => {
|
|
287
291
|
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition, purge } = await setupTestContext();
|
|
288
|
-
const mockFile = { name: 'test.txt', type: 'text/
|
|
292
|
+
const mockFile = { name: 'test.txt', type: 'text/csv', size: 1024, guid: 'dummy-txt' };
|
|
289
293
|
const result = await insertData(comprehensiveTestModelDefinition.name, { stringRequired: 'req', fileField: mockFile }, {}, currentTestUser, false);
|
|
290
294
|
expect(result.success).toBe(false);
|
|
291
295
|
expect(result.error).toContain('image/png'); // Ou un message plus spécifique sur mimeTypes
|
|
@@ -414,6 +418,57 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
414
418
|
|
|
415
419
|
await purge();
|
|
416
420
|
}, 10000); // Augmenter le timeout si nécessaire
|
|
421
|
+
|
|
422
|
+
it('devrait insérer des données avec un nouveau fichier et le sauvegarder', async () => {
|
|
423
|
+
const { currentTestUser, comprehensiveTestModelDefinition, purge } = await setupTestContext();
|
|
424
|
+
|
|
425
|
+
// --- Simuler un vrai fichier uploadé comme le ferait formidable/multer ---
|
|
426
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'data-primals-test-'));
|
|
427
|
+
const filePath = path.join(tmpDir, 'test-upload.txt');
|
|
428
|
+
const fileContent = 'Ceci est un vrai test de fichier binaire.';
|
|
429
|
+
await fs.writeFile(filePath, fileContent);
|
|
430
|
+
const stats = await fs.stat(filePath);
|
|
431
|
+
|
|
432
|
+
const uploadedFile = {
|
|
433
|
+
filepath: filePath,
|
|
434
|
+
originalFilename: 'test-upload-on-insert.txt',
|
|
435
|
+
mimetype: 'text/plain',
|
|
436
|
+
size: stats.size,
|
|
437
|
+
lastModifiedDate: new Date(),
|
|
438
|
+
hash: 'test-hash' // Optionnel, mais souvent présent
|
|
439
|
+
};
|
|
440
|
+
// --- Fin de la simulation ---
|
|
441
|
+
|
|
442
|
+
const dataWithFile = {
|
|
443
|
+
stringRequired: 'DocWithNewFile',
|
|
444
|
+
stringUnique: 'UniqueFileValueForInsert',
|
|
445
|
+
passwordField: 'filePass',
|
|
446
|
+
enumField: 'gamma',
|
|
447
|
+
number: 123
|
|
448
|
+
// Le champ 'fileField' est absent ici, il sera fourni dans l'objet 'files'
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const result = await insertData(comprehensiveTestModelDefinition.name, dataWithFile, { 'fileField[0]': uploadedFile }, currentTestUser, false);
|
|
452
|
+
|
|
453
|
+
expect(result.success, `L'insertion avec un nouveau fichier a échoué: ${result.error}`).toBe(true);
|
|
454
|
+
expect(result.insertedIds).toHaveLength(1);
|
|
455
|
+
|
|
456
|
+
const query = await searchData( { model: comprehensiveTestModelDefinition.name, filter: { _id: new ObjectId(result.insertedIds[0])}}, currentTestUser);
|
|
457
|
+
const insertedDoc = query.data[0];
|
|
458
|
+
expect(insertedDoc).not.toBeNull();
|
|
459
|
+
console.log(insertedDoc.fileField)
|
|
460
|
+
expect(insertedDoc.fileField).toBeInstanceOf(Object);
|
|
461
|
+
expect(insertedDoc.fileField.newFile).toBeUndefined(); // La propriété 'newFile' était une hallucination
|
|
462
|
+
expect(insertedDoc.fileField._id).toBeDefined();
|
|
463
|
+
expect(insertedDoc.fileField.name).toBe('test-upload-on-insert.txt');
|
|
464
|
+
expect(insertedDoc.fileField.guid).toBeDefined();
|
|
465
|
+
|
|
466
|
+
await removeFile(insertedDoc.fileField.guid, currentTestUser);
|
|
467
|
+
|
|
468
|
+
// Nettoyage du répertoire et fichier temporaire
|
|
469
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
470
|
+
await purge();
|
|
471
|
+
});
|
|
417
472
|
});
|
|
418
473
|
|
|
419
474
|
describe('editData avec comprehensiveTestModel', async () => {
|
|
@@ -488,6 +543,97 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
488
543
|
// TODO: Ajouter plus de tests de validation pour editData, similaires à ceux de insertData
|
|
489
544
|
});
|
|
490
545
|
|
|
546
|
+
describe('$push operation using patchData', () =>{
|
|
547
|
+
describe('$push sur tableau existant', () => {
|
|
548
|
+
let docToPatchId;
|
|
549
|
+
let setupContext;
|
|
550
|
+
|
|
551
|
+
beforeEach(async () => {
|
|
552
|
+
setupContext = await setupTestContext();
|
|
553
|
+
const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
|
|
554
|
+
|
|
555
|
+
const initialData = {
|
|
556
|
+
stringRequired: 'InitialForPush',
|
|
557
|
+
stringUnique: 'UniqueForPush',
|
|
558
|
+
enumField: 'alpha',
|
|
559
|
+
passwordField: 'pushPass',
|
|
560
|
+
arrayString: ['tag1'],
|
|
561
|
+
arrayNumber: [100]
|
|
562
|
+
};
|
|
563
|
+
const insertResult = await insertData(comprehensiveTestModelDefinition.name, initialData, {}, currentTestUser, false);
|
|
564
|
+
docToPatchId = insertResult.insertedIds[0];
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
afterEach(async () => {
|
|
568
|
+
await setupContext.purge();
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('devrait ajouter une seule valeur à un tableau existant', async () => {
|
|
572
|
+
const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
|
|
573
|
+
|
|
574
|
+
const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayString: { $push: 'tag2' } }, {}, currentTestUser);
|
|
575
|
+
|
|
576
|
+
expect(result.success, `patchData avec $push a échoué: ${result.error}`).toBe(true);
|
|
577
|
+
expect(result.modifiedCount).toBe(1);
|
|
578
|
+
|
|
579
|
+
const patchedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(docToPatchId) });
|
|
580
|
+
expect(patchedDoc.arrayString).toEqual(['tag1', 'tag2']);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('devrait ajouter plusieurs valeurs à un tableau existant', async () => {
|
|
584
|
+
const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
|
|
585
|
+
|
|
586
|
+
const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayNumber: { $push: [200, 300] } }, {}, currentTestUser);
|
|
587
|
+
|
|
588
|
+
expect(result.success, `patchData avec $push multiple a échoué: ${result.error}`).toBe(true);
|
|
589
|
+
expect(result.modifiedCount).toBe(1);
|
|
590
|
+
|
|
591
|
+
const patchedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(docToPatchId) });
|
|
592
|
+
expect(patchedDoc.arrayNumber).toEqual([100, 200, 300]);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('devrait rejeter un push si la valeur est invalide', async () => {
|
|
596
|
+
const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
|
|
597
|
+
|
|
598
|
+
// Tenter de pusher un nombre dans un tableau de strings
|
|
599
|
+
const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayString: { $push: 12345 } }, {}, currentTestUser);
|
|
600
|
+
|
|
601
|
+
expect(result.success).toBe(false);
|
|
602
|
+
expect(result.error).toContain("Valeur invalide fournie pour le tableau 'arrayString'");
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('devrait rejeter un push si une des valeurs du tableau est invalide', async () => {
|
|
606
|
+
const { currentTestUser, comprehensiveTestModelDefinition } = setupContext;
|
|
607
|
+
|
|
608
|
+
// Tenter de pusher un tableau contenant une valeur invalide
|
|
609
|
+
const result = await patchData(comprehensiveTestModelDefinition.name, { _id: docToPatchId }, { arrayNumber: { $push: [400, 'not-a-number', 500] } }, {}, currentTestUser);
|
|
610
|
+
|
|
611
|
+
expect(result.success).toBe(false);
|
|
612
|
+
expect(result.error).toContain("Valeur invalide fournie pour le tableau 'arrayNumber'");
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
});
|
|
616
|
+
describe('$push sur un tableau inexistant', () => {
|
|
617
|
+
it('devrait créer le tableau et pusher les valeurs s\'il n\'existe pas', async () => {
|
|
618
|
+
// On utilise un setup de contexte complètement isolé pour ce test
|
|
619
|
+
const { currentTestUser, comprehensiveTestModelDefinition, purge } = await setupTestContext();
|
|
620
|
+
|
|
621
|
+
const initialData = { stringRequired: 'NoArrayField', stringUnique: 'UniqueForNoArrayPush', enumField: 'beta', passwordField: 'pass' };
|
|
622
|
+
const insertResult = await insertData(comprehensiveTestModelDefinition.name, initialData, {}, currentTestUser, false);
|
|
623
|
+
const newDocId = insertResult.insertedIds[0];
|
|
624
|
+
|
|
625
|
+
const result = await patchData(comprehensiveTestModelDefinition.name, { _id: newDocId }, { arrayString: { $push: ['first', 'second'] } }, {}, currentTestUser);
|
|
626
|
+
|
|
627
|
+
expect(result.success, `patchData sur un tableau inexistant a échoué: ${result.error}`).toBe(true);
|
|
628
|
+
expect(result.modifiedCount).toBe(1);
|
|
629
|
+
|
|
630
|
+
const patchedDoc = await testDatasColInstance.findOne({ _id: new ObjectId(newDocId) });
|
|
631
|
+
expect(patchedDoc.arrayString).toEqual(['first', 'second']);
|
|
632
|
+
await purge();
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
|
|
491
637
|
// --- Tests deleteData (repris de votre exemple, peuvent être gardés tels quels ou adaptés) ---
|
|
492
638
|
describe('deleteData (tests existants)', () => {
|
|
493
639
|
it('supprimer des données par ID (deleteData)', async () => {
|