data-primals-engine 1.6.1 → 1.6.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.
@@ -0,0 +1,207 @@
1
+ // __tests__/assistant.test.js
2
+ import { vi, expect, describe, it, beforeAll, afterAll, beforeEach } from 'vitest';
3
+ import { Config } from '../src/config.js';
4
+ import { getCollection, getCollectionForUser } from '../src/modules/mongodb.js';
5
+ import { generateUniqueName, initEngine } from "../src/setenv.js";
6
+ import { purgeData } from "../src/modules/data/data.history.js";
7
+ import { handleChatRequest } from '../src/modules/assistant/assistant.js';
8
+ import { insertData } from '../src/index.js';
9
+ import * as dataOperations from '../src/modules/data/data.operations.js';
10
+
11
+ // --- CORRECTION ---
12
+ // On mock le nouveau module 'providers.js' qui contient la fonction que l'on veut surcharger.
13
+ vi.mock('../src/modules/assistant/providers.js', () => ({
14
+ getAIProvider: vi.fn(),
15
+ }));
16
+
17
+ // On importe la version mockée de getAIProvider
18
+ import { getAIProvider } from '../src/modules/assistant/providers.js';
19
+
20
+ let testUser;
21
+ let modelsCollection;
22
+ let datasCollection;
23
+
24
+ const modelName = generateUniqueName('productAssistant');
25
+
26
+ describe('Assistant Module Unit Tests', () => {
27
+
28
+ beforeAll(async () => {
29
+ Config.Set("modules", ["mongodb", "data", "user", "assistant"]);
30
+ await initEngine(); // Cette ligne ne devrait plus planter.
31
+ testUser = {
32
+ username: generateUniqueName('testUserAssistant'),
33
+ email: generateUniqueName('test') + '@example.com',
34
+ lang: 'fr'
35
+ };
36
+
37
+ modelsCollection = getCollection('models');
38
+ datasCollection = await getCollectionForUser(testUser);
39
+
40
+ // Create a model for testing
41
+ const productModelDef = {
42
+ name: modelName,
43
+ description: 'A model for testing',
44
+ _user: testUser.username,
45
+ fields: [
46
+ { name: 'name', type: 'string', required: true },
47
+ { name: 'price', type: 'number' }
48
+ ]
49
+ };
50
+ await modelsCollection.insertOne(productModelDef);
51
+ });
52
+
53
+ beforeEach(async () => {
54
+ // Clear mocks before each test
55
+ vi.clearAllMocks();
56
+ // Clean data, but keep the model
57
+ await datasCollection.deleteMany({ _user: testUser.username });
58
+ });
59
+
60
+ // Helper to create a mock LLM stream
61
+ const createMockStream = (content) => {
62
+ return (async function* () {
63
+ yield { content };
64
+ })();
65
+ };
66
+
67
+ it('should return a simple text message from the AI', async () => {
68
+ const mockLLM = {
69
+ stream: vi.fn().mockReturnValue(createMockStream('Bonjour, ceci est un test.')),
70
+ };
71
+ getAIProvider.mockResolvedValue(mockLLM);
72
+
73
+ const params = { message: 'Dis bonjour', history: [] };
74
+ const result = await handleChatRequest(params, testUser);
75
+
76
+ expect(getAIProvider).toHaveBeenCalled();
77
+ expect(mockLLM.stream).toHaveBeenCalled();
78
+ expect(result).toEqual({
79
+ success: true,
80
+ displayMessage: 'Bonjour, ceci est un test.'
81
+ });
82
+ });
83
+
84
+ it('should handle a search action', async () => {
85
+
86
+ // Configure le retour du mock pour ce test spécifique
87
+ const searchDataSpy = vi.spyOn(dataOperations, 'searchData').mockResolvedValue({ data: [{ name: 'Test Widget', price: 100 }], count: 1 });
88
+
89
+ const aiResponse = JSON.stringify([
90
+ { action: 'search_models', params: { query: modelName } },
91
+ { action: 'search', params: { model: modelName, filter: {} } }
92
+ ]);
93
+ const mockLLM = {
94
+ stream: vi.fn().mockReturnValue(createMockStream(aiResponse))
95
+ };
96
+ getAIProvider.mockResolvedValue(mockLLM);
97
+
98
+ const params = { message: `cherche les produits`, history: [] };
99
+ const result = await handleChatRequest(params, testUser);
100
+
101
+ expect(searchDataSpy).toHaveBeenCalled();
102
+
103
+ expect(result.success).toBe(true);
104
+ expect(result.dataResult).toBeDefined();
105
+ expect(result.dataResult.model).toBe(modelName);
106
+ expect(result.dataResult.data).toHaveLength(1);
107
+ expect(result.dataResult.data[0].name).toBe('Test Widget');
108
+ });
109
+
110
+ it('should request confirmation for a "post" action', async () => {
111
+ const aiResponse = JSON.stringify({
112
+ action: 'post',
113
+ params: {
114
+ model: modelName,
115
+ data: { name: 'New Gadget', price: 19.99 }
116
+ }
117
+ });
118
+ const mockLLM = {
119
+ stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
120
+ };
121
+ getAIProvider.mockResolvedValue(mockLLM);
122
+
123
+ const params = { message: 'Crée un produit', history: [] };
124
+ const result = await handleChatRequest(params, testUser);
125
+
126
+ expect(result.success).toBe(true);
127
+ expect(result.confirmationRequest).toBeDefined();
128
+ expect(result.confirmationRequest.action).toBe('post');
129
+ expect(result.confirmationRequest.params.model).toBe(modelName);
130
+ expect(result.confirmationRequest.params.data.name).toBe('New Gadget');
131
+ });
132
+
133
+ it('should execute a confirmed "post" action', async () => {
134
+ const confirmedAction = {
135
+ action: 'post',
136
+ params: {
137
+ model: modelName,
138
+ data: { name: 'Confirmed Gadget', price: 50 }
139
+ }
140
+ };
141
+
142
+ const params = { message: '', confirmedAction };
143
+ const result = await handleChatRequest(params, testUser);
144
+
145
+ expect(result.success).toBe(true);
146
+ // --- CORRECTION ---
147
+ // On vérifie le message de succès spécifique à la création, qui est plus informatif.
148
+ expect(result.displayMessage).toContain("Élément créé avec l'ID:");
149
+
150
+ // Verify the data was actually inserted
151
+ const dataInDb = await datasCollection.findOne({ name: 'Confirmed Gadget' });
152
+ expect(dataInDb).not.toBeNull();
153
+ expect(dataInDb.price).toBe(50);
154
+ });
155
+
156
+ it('should handle malformed JSON from AI by returning it as a text message', async () => {
157
+ const aiResponse = `{ "action": "search", "params": { "model": "${modelName}" }`; // Missing closing brace
158
+ const mockLLM = {
159
+ stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
160
+ };
161
+ getAIProvider.mockResolvedValue(mockLLM);
162
+
163
+ const params = { message: 'test', history: [] };
164
+ const result = await handleChatRequest(params, testUser);
165
+
166
+ expect(result.success).toBe(true);
167
+ expect(result.displayMessage).toBe(aiResponse);
168
+ });
169
+
170
+ it('should correct a malformed filter from the AI', async () => {
171
+ const aiResponse = JSON.stringify({
172
+ action: 'search',
173
+ params: {
174
+ model: modelName,
175
+ // This filter is malformed: multiple operators at the root level
176
+ filter: {
177
+ "$gt": ["$price", 50],
178
+ "$lt": ["$price", 200]
179
+ }
180
+ }
181
+ });
182
+ const mockLLM = {
183
+ stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
184
+ };
185
+ getAIProvider.mockResolvedValue(mockLLM);
186
+
187
+ // Configure le retour du mock pour ce test spécifique
188
+ const searchDataSpy = vi.spyOn(dataOperations, 'searchData').mockResolvedValue({ data: [], count: 0 });
189
+
190
+ const params = { message: 'cherche produits entre 50 et 200', history: [] };
191
+ // On peut maintenant utiliser la fonction handleChatRequest importée normalement
192
+ await handleChatRequest(params, testUser);
193
+
194
+ // On s'attend à ce que le filtre ait été encapsulé dans un $and
195
+ const expectedFilter = {
196
+ "$and": [
197
+ { "$gt": ["$price", 50] },
198
+ { "$lt": ["$price", 200] }
199
+ ]
200
+ };
201
+
202
+ expect(searchDataSpy).toHaveBeenCalledWith(expect.objectContaining({ filter: expectedFilter }), expect.anything());
203
+
204
+ // On nettoie l'espion après le test
205
+ searchDataSpy.mockRestore();
206
+ });
207
+ });
@@ -0,0 +1,41 @@
1
+ // __tests__/config.test.js
2
+ import { describe, it, expect, beforeAll } from 'vitest';
3
+ import { Config } from '../src/config.js';
4
+ import { initEngine } from '../src/setenv.js';
5
+
6
+ describe('Configuration Module Tests', () => {
7
+
8
+ it('should correctly get and set a simple value', () => {
9
+ const key = 'testKey';
10
+ const value = 'testValue';
11
+
12
+ Config.Set(key, value);
13
+ const retrievedValue = Config.Get(key);
14
+
15
+ expect(retrievedValue).toBe(value);
16
+ });
17
+
18
+ it('should return the default value if a key is not set', () => {
19
+ const defaultValue = 'default';
20
+ const retrievedValue = Config.Get('nonExistentKey', defaultValue);
21
+
22
+ expect(retrievedValue).toBe(defaultValue);
23
+ });
24
+
25
+ it('should initialize the engine with only the modules specified in the config', async () => {
26
+ // 1. Définir une configuration de modules minimale.
27
+ const targetModules = ['mongodb', 'user'];
28
+ Config.Set("modules", targetModules);
29
+
30
+ // 2. Initialiser le moteur. initEngine devrait lire cette configuration.
31
+ const engine = await initEngine();
32
+
33
+ // 3. Vérifier que le moteur a chargé UNIQUEMENT les modules spécifiés.
34
+ // La propriété _modules de l'engine contient les instances des modules chargés.
35
+ const loadedModuleNames = engine._modules.map(m => m.module);
36
+
37
+ expect(engine._modules).toBeInstanceOf(Array);
38
+ expect(loadedModuleNames).toHaveLength(targetModules.length);
39
+ expect(loadedModuleNames).toEqual(expect.arrayContaining(targetModules));
40
+ });
41
+ });
@@ -88,7 +88,7 @@ describe('Data History Module Integration Tests', () => {
88
88
  };
89
89
  const insertResult = await insertData(modelName, initialData, {}, testUser);
90
90
  expect(insertResult.success).toBe(true);
91
- const docId = new ObjectId(insertResult.insertedIds[0]);
91
+ const docId = new ObjectId(insertResult.data._id);
92
92
 
93
93
  // 3. Verify the history record
94
94
  const historyRecord = await historyCollection.findOne({ documentId: new ObjectId(docId) });
@@ -136,7 +136,7 @@ describe('Data History Module Integration Tests', () => {
136
136
  // 2. Insert the initial document
137
137
  const initialData = { name: 'Selective Widget', price: 50, stock: 200, description: 'Initial description.' };
138
138
  const insertResult = await insertData(modelName, initialData, {}, testUser);
139
- const docId = new ObjectId(insertResult.insertedIds[0]);
139
+ const docId = new ObjectId(insertResult.data._id);
140
140
 
141
141
  // 3. Edit the document: change one historized field and one non-historized field
142
142
  const updateData = { price: 55.5, description: 'Updated description.' };
@@ -179,7 +179,7 @@ describe('Data History Module Integration Tests', () => {
179
179
  // 2. Insert initial document
180
180
  const initialData = { name: 'No-Op Widget', price: 10, description: 'Initial.' };
181
181
  const insertResult = await insertData(modelName, initialData, {}, testUser);
182
- const docId = new ObjectId(insertResult.insertedIds[0]);
182
+ const docId = new ObjectId(insertResult.data._id);
183
183
 
184
184
  // 3. Edit ONLY a non-historized field
185
185
  const updateData = { description: 'This change should not be recorded.' };
@@ -206,7 +206,7 @@ describe('Data History Module Integration Tests', () => {
206
206
 
207
207
  // 2. Insert initial document
208
208
  const insertResult = await insertData(modelName, { name: 'Time-traveling Widget' }, {}, testUser);
209
- const docId = new ObjectId(insertResult.insertedIds[0]);
209
+ const docId = new ObjectId(insertResult.data._id);
210
210
 
211
211
  // 3. Create history entries at different times
212
212
  // To simulate different timestamps, we'll manually insert history records