data-primals-engine 1.7.1 → 1.7.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 +160 -160
- package/client/package-lock.json +484 -175
- package/client/package.json +7 -4
- package/client/src/AssistantChat.jsx +1 -3
- package/client/src/DataLayout.jsx +19 -20
- package/client/src/DataTable.jsx +2 -2
- package/client/src/DocumentationPageLayout.scss +1 -1
- package/client/src/ViewSwitcher.jsx +1 -1
- package/client/vite.config.js +31 -30
- package/package.json +27 -17
- package/src/ai.jobs.js +135 -0
- package/src/constants.js +561 -545
- package/src/data.js +2 -0
- package/src/engine.js +50 -42
- package/src/modules/assistant/assistant.js +782 -763
- package/src/modules/assistant/constants.js +23 -16
- package/src/modules/assistant/providers.js +77 -37
- package/src/modules/bucket.js +4 -0
- package/src/modules/data/data.cluster.js +191 -0
- package/src/modules/data/data.core.js +11 -8
- package/src/modules/data/data.js +311 -311
- package/src/modules/data/data.operations.js +186 -106
- package/src/modules/data/data.relations.js +1 -0
- package/src/modules/data/data.replication.js +83 -0
- package/src/modules/data/data.routes.js +2183 -1879
- package/src/modules/mongodb.js +76 -73
- package/src/modules/user.js +7 -1
- package/src/modules/worker-script-runner.js +97 -0
- package/src/modules/workflow.js +177 -52
- package/src/packs.js +5701 -5701
- package/src/providers.js +298 -297
- package/test/assistant.test.js +207 -206
- package/test/data.integration.test.js +1425 -1416
- package/test/import_export.integration.test.js +210 -210
- package/test/workflow.actions.integration.test.js +487 -475
- package/test/workflow.integration.test.js +332 -329
package/test/assistant.test.js
CHANGED
|
@@ -1,207 +1,208 @@
|
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let
|
|
22
|
-
let
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{ name: '
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
expect(
|
|
78
|
-
expect(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
{ action: '
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
expect(result.
|
|
105
|
-
expect(result.dataResult
|
|
106
|
-
expect(result.dataResult.
|
|
107
|
-
expect(result.dataResult.data
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
expect(result.
|
|
128
|
-
expect(result.confirmationRequest
|
|
129
|
-
expect(result.confirmationRequest.
|
|
130
|
-
expect(result.confirmationRequest.params.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
expect(dataInDb
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
expect(result.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
filter:
|
|
177
|
-
|
|
178
|
-
"$
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
{ "$
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
+
findFirstAvailableProvider: vi.fn().mockResolvedValue({ provider: 'OpenAI', apiKey: 'mock-api-key' }),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// On importe la version mockée de getAIProvider
|
|
19
|
+
import { getAIProvider, findFirstAvailableProvider } from '../src/modules/assistant/providers.js';
|
|
20
|
+
|
|
21
|
+
let testUser;
|
|
22
|
+
let modelsCollection;
|
|
23
|
+
let datasCollection;
|
|
24
|
+
|
|
25
|
+
const modelName = generateUniqueName('productAssistant');
|
|
26
|
+
|
|
27
|
+
describe('Assistant Module Unit Tests', () => {
|
|
28
|
+
|
|
29
|
+
beforeAll(async () => {
|
|
30
|
+
Config.Set("modules", ["mongodb", "data", "user", "assistant"]);
|
|
31
|
+
await initEngine(); // Cette ligne ne devrait plus planter.
|
|
32
|
+
testUser = {
|
|
33
|
+
username: generateUniqueName('testUserAssistant'),
|
|
34
|
+
email: generateUniqueName('test') + '@example.com',
|
|
35
|
+
lang: 'fr'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
modelsCollection = getCollection('models');
|
|
39
|
+
datasCollection = await getCollectionForUser(testUser);
|
|
40
|
+
|
|
41
|
+
// Create a model for testing
|
|
42
|
+
const productModelDef = {
|
|
43
|
+
name: modelName,
|
|
44
|
+
description: 'A model for testing',
|
|
45
|
+
_user: testUser.username,
|
|
46
|
+
fields: [
|
|
47
|
+
{ name: 'name', type: 'string', required: true },
|
|
48
|
+
{ name: 'price', type: 'number' }
|
|
49
|
+
]
|
|
50
|
+
};
|
|
51
|
+
await modelsCollection.insertOne(productModelDef);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
beforeEach(async () => {
|
|
55
|
+
// Clear mocks before each test
|
|
56
|
+
vi.clearAllMocks();
|
|
57
|
+
// Clean data, but keep the model
|
|
58
|
+
await datasCollection.deleteMany({ _user: testUser.username });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Helper to create a mock LLM stream
|
|
62
|
+
const createMockStream = (content) => {
|
|
63
|
+
return (async function* () {
|
|
64
|
+
yield { content };
|
|
65
|
+
})();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
it('should return a simple text message from the AI', async () => {
|
|
69
|
+
const mockLLM = {
|
|
70
|
+
stream: vi.fn().mockReturnValue(createMockStream('Bonjour, ceci est un test.')),
|
|
71
|
+
};
|
|
72
|
+
getAIProvider.mockResolvedValue(mockLLM);
|
|
73
|
+
|
|
74
|
+
const params = { message: 'Dis bonjour', history: [] };
|
|
75
|
+
const result = await handleChatRequest(params, testUser);
|
|
76
|
+
|
|
77
|
+
expect(getAIProvider).toHaveBeenCalled();
|
|
78
|
+
expect(mockLLM.stream).toHaveBeenCalled();
|
|
79
|
+
expect(result).toEqual({
|
|
80
|
+
success: true,
|
|
81
|
+
displayMessage: 'Bonjour, ceci est un test.'
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should handle a search action', async () => {
|
|
86
|
+
|
|
87
|
+
// Configure le retour du mock pour ce test spécifique
|
|
88
|
+
const searchDataSpy = vi.spyOn(dataOperations, 'searchData').mockResolvedValue({ data: [{ name: 'Test Widget', price: 100 }], count: 1 });
|
|
89
|
+
|
|
90
|
+
const aiResponse = JSON.stringify([
|
|
91
|
+
{ action: 'search_models', params: { query: modelName } },
|
|
92
|
+
{ action: 'search', params: { model: modelName, filter: {} } }
|
|
93
|
+
]);
|
|
94
|
+
const mockLLM = {
|
|
95
|
+
stream: vi.fn().mockReturnValue(createMockStream(aiResponse))
|
|
96
|
+
};
|
|
97
|
+
getAIProvider.mockResolvedValue(mockLLM);
|
|
98
|
+
|
|
99
|
+
const params = { message: `cherche les produits`, history: [] };
|
|
100
|
+
const result = await handleChatRequest(params, testUser);
|
|
101
|
+
|
|
102
|
+
expect(searchDataSpy).toHaveBeenCalled();
|
|
103
|
+
|
|
104
|
+
expect(result.success).toBe(true);
|
|
105
|
+
expect(result.dataResult).toBeDefined();
|
|
106
|
+
expect(result.dataResult.model).toBe(modelName);
|
|
107
|
+
expect(result.dataResult.data).toHaveLength(1);
|
|
108
|
+
expect(result.dataResult.data[0].name).toBe('Test Widget');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should request confirmation for a "post" action', async () => {
|
|
112
|
+
const aiResponse = JSON.stringify({
|
|
113
|
+
action: 'post',
|
|
114
|
+
params: {
|
|
115
|
+
model: modelName,
|
|
116
|
+
data: { name: 'New Gadget', price: 19.99 }
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
const mockLLM = {
|
|
120
|
+
stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
|
|
121
|
+
};
|
|
122
|
+
getAIProvider.mockResolvedValue(mockLLM);
|
|
123
|
+
|
|
124
|
+
const params = { message: 'Crée un produit', history: [] };
|
|
125
|
+
const result = await handleChatRequest(params, testUser);
|
|
126
|
+
|
|
127
|
+
expect(result.success).toBe(true);
|
|
128
|
+
expect(result.confirmationRequest).toBeDefined();
|
|
129
|
+
expect(result.confirmationRequest.action).toBe('post');
|
|
130
|
+
expect(result.confirmationRequest.params.model).toBe(modelName);
|
|
131
|
+
expect(result.confirmationRequest.params.data.name).toBe('New Gadget');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should execute a confirmed "post" action', async () => {
|
|
135
|
+
const confirmedAction = {
|
|
136
|
+
action: 'post',
|
|
137
|
+
params: {
|
|
138
|
+
model: modelName,
|
|
139
|
+
data: { name: 'Confirmed Gadget', price: 50 }
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const params = { message: '', confirmedAction };
|
|
144
|
+
const result = await handleChatRequest(params, testUser);
|
|
145
|
+
|
|
146
|
+
expect(result.success).toBe(true);
|
|
147
|
+
// --- CORRECTION ---
|
|
148
|
+
// On vérifie le message de succès spécifique à la création, qui est plus informatif.
|
|
149
|
+
expect(result.displayMessage).toContain("Élément créé avec l'ID:");
|
|
150
|
+
|
|
151
|
+
// Verify the data was actually inserted
|
|
152
|
+
const dataInDb = await datasCollection.findOne({ name: 'Confirmed Gadget' });
|
|
153
|
+
expect(dataInDb).not.toBeNull();
|
|
154
|
+
expect(dataInDb.price).toBe(50);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should handle malformed JSON from AI by returning it as a text message', async () => {
|
|
158
|
+
const aiResponse = `{ "action": "search", "params": { "model": "${modelName}" }`; // Missing closing brace
|
|
159
|
+
const mockLLM = {
|
|
160
|
+
stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
|
|
161
|
+
};
|
|
162
|
+
getAIProvider.mockResolvedValue(mockLLM);
|
|
163
|
+
|
|
164
|
+
const params = { message: 'test', history: [] };
|
|
165
|
+
const result = await handleChatRequest(params, testUser);
|
|
166
|
+
|
|
167
|
+
expect(result.success).toBe(true);
|
|
168
|
+
expect(result.displayMessage).toBe(aiResponse);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should correct a malformed filter from the AI', async () => {
|
|
172
|
+
const aiResponse = JSON.stringify([{
|
|
173
|
+
action: 'search',
|
|
174
|
+
params: {
|
|
175
|
+
model: modelName,
|
|
176
|
+
// This filter is malformed: multiple operators at the root level
|
|
177
|
+
filter: {
|
|
178
|
+
"$gt": ["$price", 50],
|
|
179
|
+
"$lt": ["$price", 200]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}]);
|
|
183
|
+
const mockLLM = {
|
|
184
|
+
stream: vi.fn().mockReturnValue(createMockStream(aiResponse)),
|
|
185
|
+
};
|
|
186
|
+
getAIProvider.mockResolvedValue(mockLLM);
|
|
187
|
+
|
|
188
|
+
// Configure le retour du mock pour ce test spécifique
|
|
189
|
+
const searchDataSpy = vi.spyOn(dataOperations, 'searchData').mockResolvedValue({ data: [], count: 0 });
|
|
190
|
+
|
|
191
|
+
const params = { message: 'cherche produits entre 50 et 200', history: [] };
|
|
192
|
+
// On peut maintenant utiliser la fonction handleChatRequest importée normalement
|
|
193
|
+
await handleChatRequest(params, testUser);
|
|
194
|
+
|
|
195
|
+
// On s'attend à ce que le filtre ait été encapsulé dans un $and
|
|
196
|
+
const expectedFilter = {
|
|
197
|
+
"$and": [
|
|
198
|
+
{ "$gt": ["$price", 50] },
|
|
199
|
+
{ "$lt": ["$price", 200] }
|
|
200
|
+
]
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
expect(searchDataSpy).toHaveBeenCalledWith(expect.objectContaining({ filter: expectedFilter }), expect.anything());
|
|
204
|
+
|
|
205
|
+
// On nettoie l'espion après le test
|
|
206
|
+
searchDataSpy.mockRestore();
|
|
207
|
+
});
|
|
207
208
|
});
|