data-primals-engine 1.5.1 → 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.
@@ -2,7 +2,7 @@ import {getDefaultForType, getFieldValueHash} from "../../data.js";
2
2
  import {Event} from "../../events.js";
3
3
  import {getCollectionForUser, isObjectId} from "../mongodb.js";
4
4
  import {ObjectId} from "mongodb";
5
- import {isPlainObject} from "../../core.js";
5
+ import {isPlainObject, parseSafeJSON} from "../../core.js";
6
6
  import {dataTypes, getModel, searchData} from "./data.operations.js";
7
7
  import {validateModelData} from "./data.validation.js";
8
8
  import i18n from "../../i18n.js";
@@ -12,7 +12,7 @@ import {
12
12
  maxModelsPerUser, maxPackData, maxPackPreviewData
13
13
  } from "../../constants.js";
14
14
  import {datasCollection, getCollection, getCollectionForUser, isObjectId, modelsCollection} from "../mongodb.js";
15
- import {countKeys, safeAssignObject, uuidv4} from "../../core.js";
15
+ import {countKeys, parseSafeJSON, safeAssignObject, uuidv4} from "../../core.js";
16
16
  import {Event} from "../../events.js";
17
17
  import fs from "node:fs";
18
18
  import i18n from "../../i18n.js";
@@ -1192,7 +1192,7 @@ export async function registerRoutes(defaultEngine){
1192
1192
  totalCount = result.length > 0 ? result[0]['count'] : 0;
1193
1193
  } catch (totalError) {
1194
1194
  if (totalError instanceof SyntaxError) {
1195
- console.error(`>>> ERREUR JSON.parse (totalMatchFormula) pour KPI ${id}:`, kpiDef.totalMatchFormula, totalError);
1195
+ console.error(`>>> ERREUR parseSafeJSON (totalMatchFormula) pour KPI ${id}:`, kpiDef.totalMatchFormula, totalError);
1196
1196
  } else {
1197
1197
  console.error(`>>> ERREUR Aggregate (totalCount) pour KPI ${id}:`, totalError);
1198
1198
  }
@@ -1206,7 +1206,7 @@ export async function registerRoutes(defaultEngine){
1206
1206
  const parsedMatch = kpiDef.matchFormula || {}; // Assurer un objet vide
1207
1207
  matchFilter = { ...matchFilter, ...parsedMatch };
1208
1208
  } catch (matchError) {
1209
- console.error(`>>> ERREUR JSON.parse (matchFormula) pour KPI ${id}:`, kpiDef.matchFormula, matchError);
1209
+ console.error(`>>> ERREUR parseSafeJSON (matchFormula) pour KPI ${id}:`, kpiDef.matchFormula, matchError);
1210
1210
  throw matchError; // Relancer
1211
1211
  }
1212
1212
  }
@@ -19,6 +19,7 @@ import {Config} from "../config.js";
19
19
 
20
20
  export const userInitiator = async (req, res, next) => {
21
21
 
22
+ const engine = req.app.get('engine');
22
23
  const lang = getAPILang(req.query.lang || req.headers['Accept-Language']);
23
24
 
24
25
  req.lang = lang;
@@ -19,7 +19,7 @@ import {getEnv, getSmtpConfig} from "./user.js";
19
19
  import {getHost} from "../constants.js";
20
20
  import {providers} from "./assistant/constants.js";
21
21
  import {getAIProvider} from "./assistant/assistant.js";
22
- import { safeAssignObject} from "../core.js";
22
+ import {parseSafeJSON, safeAssignObject} from "../core.js";
23
23
  import {Config} from "../config.js";
24
24
 
25
25
  let logger = null;
@@ -1655,7 +1655,7 @@ async function executeGenerateAIContentAction(action, context, user) {
1655
1655
  }
1656
1656
 
1657
1657
  // 2. Initialize the LLM client with LangChain
1658
- let llm = getAIProvider(aiProvider, aiModel, apiKey);
1658
+ let llm = await getAIProvider(aiProvider, aiModel, apiKey);
1659
1659
  if( !llm ) {
1660
1660
  const message = `Failed to initialize AI client for ${aiProvider}: ${initError.message}`;
1661
1661
  logger.error(`[AI Action] ${message}`);
@@ -1,4 +1,5 @@
1
1
  import OpenAI from "openai";
2
+ import {parseSafeJSON} from "./core.js";
2
3
 
3
4
 
4
5
  let engine = null;
@@ -132,8 +133,8 @@ export const openaiJobModel = async (lang, txt, history, existingModels = []) =>
132
133
  const aiResponse = completion.choices[0].message.content;
133
134
 
134
135
  try {
135
- // JSON.parse fonctionnera que le contenu soit un objet ou un tableau
136
- return JSON.parse(aiResponse); // On retourne directement le résultat parsé
136
+ // parseSafeJSON fonctionnera que le contenu soit un objet ou un tableau
137
+ return parseSafeJSON(aiResponse); // On retourne directement le résultat parsé
137
138
  } catch (e) {
138
139
  console.error("Erreur de parsing du JSON de l'IA:", e);
139
140
  throw new Error("Réponse invalide de l'IA.");
package/src/sso.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import passport from 'passport';
2
2
  import session from 'express-session';
3
- import { Behaviour } from './gameObject.js';
3
+ import {Behaviour, Logger} from './gameObject.js';
4
4
  import { cookiesSecret } from "./constants.js";
5
5
  import {UserProvider} from "./providers.js";
6
6
  import {getCollection} from "./modules/mongodb.js";
@@ -110,7 +110,7 @@ export class Sso extends Behaviour {
110
110
  throw new Error("PassportAuth component requires an ssoUserProvider to be initialized.");
111
111
  }
112
112
  this.#ssoUserProvider = ssoUserProvider;
113
- this.#logger = this.gameObject.getComponent('Logger'); // Assumant que Logger est un composant
113
+ this.#logger = this.gameObject.getComponent(Logger); // Assumant que Logger est un composant
114
114
 
115
115
  const app = this.gameObject; // this.gameObject est l'instance de l'engine (Express app)
116
116
 
@@ -1,5 +1,5 @@
1
1
  import { parentPort } from 'worker_threads';
2
- import { parse } from 'csv-parse/sync'; // On utilise la version synchrone, c'est ok dans un worker
2
+ import { parse } from 'csv-parse/sync';
3
3
 
4
4
  parentPort.on('message', ({ action, payload }) => {
5
5
  try {
@@ -1,193 +1,265 @@
1
- // __tests__/data.history.integration.test.js
2
- import { ObjectId } from 'mongodb';
3
- import { expect, describe, it, beforeAll, afterAll, beforeEach } from 'vitest';
4
- import { Config } from '../src/config.js';
5
- import { sleep } from '../src/core.js';
6
- import { insertData, editData, deleteModels } from '../src/index.js';
7
- import { getCollection, getCollectionForUser } from '../src/modules/mongodb.js';
8
- import { generateUniqueName, initEngine } from "../src/setenv.js";
9
- import {purgeData} from "../src/modules/data/data.history.js";
10
- import {MongoDatabase} from "../src/engine.js";
11
-
12
- let engine;
13
- let testUser;
14
- let historyCollection;
15
- let datasCollection;
16
- let modelsCollection;
17
-
18
- describe('Data History Module Integration Tests', () => {
19
-
20
- beforeAll(async () => {
21
- // IMPORTANT: Add the history module to the engine configuration for tests
22
- Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow", "user", "assistant"]);
23
- engine = await initEngine();
24
-
25
- // Setup a single user for all tests in this suite
26
- testUser = {
27
- username: generateUniqueName('testUserHistory'),
28
- userPlan: 'free',
29
- email: generateUniqueName('test') + '@example.com'
30
- };
31
-
32
- // Initialize collection instances
33
- historyCollection = getCollection('history');
34
- datasCollection = await getCollectionForUser(testUser);
35
- modelsCollection = getCollection('models');
36
- });
37
-
38
- afterAll(async () =>{
39
- await purgeData(testUser);
40
- await datasCollection.drop();
41
- })
42
-
43
- // Clean up collections before each test to ensure isolation
44
- beforeEach(async () => {
45
- await historyCollection.deleteMany({ 'user.username': testUser.username });
46
- await datasCollection.deleteMany({ _user: testUser.username });
47
- await modelsCollection.deleteMany({ _user: testUser.username });
48
- });
49
-
50
- afterAll(async () => {
51
- // Final cleanup
52
- await historyCollection.deleteMany({ 'user.username': testUser.username });
53
- await datasCollection.deleteMany({ _user: testUser.username });
54
- await modelsCollection.deleteMany({ _user: testUser.username });
55
- });
56
-
57
- it('should create a full snapshot history record on document creation', async () => {
58
- // 1. Define and create a model with history enabled
59
- const modelName = generateUniqueName('productHistory');
60
- const productModelDef = {
61
- name: modelName,
62
- description:"",
63
- _user: testUser.username,
64
- history: {
65
- enabled: true,
66
- // For creation, all fields are snapshotted regardless of this config
67
- fields: {
68
- price: true,
69
- stock: true
70
- }
71
- },
72
- fields: [
73
- { name: 'name', type: 'string', required: true },
74
- { name: 'price', type: 'number' },
75
- { name: 'stock', type: 'number' },
76
- { name: 'description', type: 'string' }
77
- ]
78
- };
79
- await modelsCollection.insertOne(productModelDef);
80
-
81
- // 2. Insert a new document
82
- const initialData = {
83
- name: 'Super Widget',
84
- price: 99.99,
85
- stock: 100,
86
- description: 'A very super widget.'
87
- };
88
- const insertResult = await insertData(modelName, initialData, {}, testUser);
89
- expect(insertResult.success).toBe(true);
90
- const docId = new ObjectId(insertResult.insertedIds[0]);
91
-
92
- // 3. Verify the history record
93
- const historyRecord = await historyCollection.findOne({ documentId: new ObjectId(docId) });
94
-
95
- expect(historyRecord).not.toBeNull();
96
- expect(historyRecord.documentId.toString()).toBe(docId.toString());
97
- expect(historyRecord.model).toBe(modelName);
98
- expect(historyRecord.version).toBe(1);
99
- expect(historyRecord.operation).toBe('create');
100
- expect(historyRecord.user.username).toBe(testUser.username);
101
-
102
- // 4. Verify the snapshot
103
- expect(historyRecord.snapshot).not.toBeNull();
104
- expect(historyRecord.snapshot.name).toBe('Super Widget');
105
- expect(historyRecord.snapshot.price).toBe(99.99);
106
- expect(historyRecord.snapshot.stock).toBe(100);
107
- expect(historyRecord.snapshot.description).toBe('A very super widget.');
108
- expect(historyRecord.changes).toBeUndefined(); // No 'changes' field on creation
109
- });
110
-
111
- it('should create a diff history record on document update for historized fields only', async () => {
112
- // 1. Define and create a model with specific history fields
113
- const modelName = generateUniqueName('productHistorySelective');
114
- const productModelDef = {
115
- name: modelName,
116
- description:"",
117
- _user: testUser.username,
118
- history: {
119
- enabled: true,
120
- fields: {
121
- price: true, // Track this
122
- stock: true // Track this
123
- // 'description' is NOT tracked
124
- }
125
- },
126
- fields: [
127
- { name: 'name', type: 'string', required: true },
128
- { name: 'price', type: 'number' },
129
- { name: 'stock', type: 'number' },
130
- { name: 'description', type: 'string' }
131
- ]
132
- };
133
- await modelsCollection.insertOne(productModelDef);
134
-
135
- // 2. Insert the initial document
136
- const initialData = { name: 'Selective Widget', price: 50, stock: 200, description: 'Initial description.' };
137
- const insertResult = await insertData(modelName, initialData, {}, testUser);
138
- const docId = new ObjectId(insertResult.insertedIds[0]);
139
-
140
- // 3. Edit the document: change one historized field and one non-historized field
141
- const updateData = { price: 55.5, description: 'Updated description.' };
142
- const editResult = await editData(modelName, { _id: docId }, updateData, {}, testUser);
143
- expect(editResult.success).toBe(true);
144
-
145
- // 4. Verify the new history record (v2)
146
- const historyRecord = await historyCollection.findOne({ documentId: docId, version: 2 });
147
-
148
- expect(historyRecord).not.toBeNull();
149
- expect(historyRecord.operation).toBe('update');
150
- expect(historyRecord.snapshot).toBeUndefined(); // No 'snapshot' on update
151
-
152
- // 5. Verify the 'changes' object
153
- const changes = historyRecord.changes;
154
- expect(changes).not.toBeNull();
155
- expect(changes.price).toBeDefined();
156
- expect(changes.price.from).toBe(50);
157
- expect(changes.price.to).toBe(55.5);
158
- expect(changes.description).toBeUndefined();
159
- expect(changes.stock).toBeUndefined();
160
- });
161
-
162
- it('should NOT create a history record if only non-historized fields are updated', async () => {
163
- // 1. Setup model
164
- const modelName = generateUniqueName('productHistoryNoOp');
165
- const productModelDef = {
166
- name: modelName,
167
- description:"",
168
- _user: testUser.username,
169
- history: { enabled: true, fields: { price: true } },
170
- fields: [
171
- { name: 'name', type: 'string', required: true },
172
- { name: 'price', type: 'number' },
173
- { name: 'description', type: 'string' }
174
- ]
175
- };
176
- await modelsCollection.insertOne(productModelDef);
177
-
178
- // 2. Insert initial document
179
- const initialData = { name: 'No-Op Widget', price: 10, description: 'Initial.' };
180
- const insertResult = await insertData(modelName, initialData, {}, testUser);
181
- const docId = new ObjectId(insertResult.insertedIds[0]);
182
-
183
- // 3. Edit ONLY a non-historized field
184
- const updateData = { description: 'This change should not be recorded.' };
185
- await editData(modelName, { _id: docId }, updateData, {}, testUser);
186
-
187
- await sleep(2000);
188
-
189
- // 4. Verify that NO new history record was created (only the 'create' record exists)
190
- const historyCount = await historyCollection.countDocuments({ documentId: docId });
191
- expect(historyCount).toBe(1);
192
- });
1
+ // __tests__/data.history.integration.test.js
2
+ import { ObjectId } from 'mongodb';
3
+ import { expect, describe, it, beforeAll, afterAll, beforeEach } from 'vitest';
4
+ import { handleGetHistoryRequest } from '../src/modules/data/data.history.js';
5
+ import { Config } from '../src/config.js';
6
+ import { sleep } from '../src/core.js';
7
+ import { insertData, editData, deleteModels } from '../src/index.js';
8
+ import { getCollection, getCollectionForUser } from '../src/modules/mongodb.js';
9
+ import { generateUniqueName, initEngine } from "../src/setenv.js";
10
+ import {purgeData} from "../src/modules/data/data.history.js";
11
+ import {MongoDatabase} from "../src/engine.js";
12
+
13
+ let engine;
14
+ let testUser;
15
+ let historyCollection;
16
+ let datasCollection;
17
+ let modelsCollection;
18
+
19
+ describe('Data History Module Integration Tests', () => {
20
+
21
+ beforeAll(async () => {
22
+ // IMPORTANT: Add the history module to the engine configuration for tests
23
+ Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow", "user", "assistant"]);
24
+ engine = await initEngine();
25
+
26
+ // Setup a single user for all tests in this suite
27
+ testUser = {
28
+ username: generateUniqueName('testUserHistory'),
29
+ userPlan: 'free',
30
+ email: generateUniqueName('test') + '@example.com'
31
+ };
32
+
33
+ // Initialize collection instances
34
+ historyCollection = getCollection('history');
35
+ datasCollection = await getCollectionForUser(testUser);
36
+ modelsCollection = getCollection('models');
37
+ });
38
+
39
+ afterAll(async () =>{
40
+ await purgeData(testUser);
41
+ await datasCollection.drop();
42
+ })
43
+
44
+ // Clean up collections before each test to ensure isolation
45
+ beforeEach(async () => {
46
+ await historyCollection.deleteMany({ 'user.username': testUser.username });
47
+ await datasCollection.deleteMany({ _user: testUser.username });
48
+ await modelsCollection.deleteMany({ _user: testUser.username });
49
+ });
50
+
51
+ afterAll(async () => {
52
+ // Final cleanup
53
+ await historyCollection.deleteMany({ 'user.username': testUser.username });
54
+ await datasCollection.deleteMany({ _user: testUser.username });
55
+ await modelsCollection.deleteMany({ _user: testUser.username });
56
+ });
57
+
58
+ it('should create a full snapshot history record on document creation', async () => {
59
+ // 1. Define and create a model with history enabled
60
+ const modelName = generateUniqueName('productHistory');
61
+ const productModelDef = {
62
+ name: modelName,
63
+ description:"",
64
+ _user: testUser.username,
65
+ history: {
66
+ enabled: true,
67
+ // For creation, all fields are snapshotted regardless of this config
68
+ fields: {
69
+ price: true,
70
+ stock: true
71
+ }
72
+ },
73
+ fields: [
74
+ { name: 'name', type: 'string', required: true },
75
+ { name: 'price', type: 'number' },
76
+ { name: 'stock', type: 'number' },
77
+ { name: 'description', type: 'string' }
78
+ ]
79
+ };
80
+ await modelsCollection.insertOne(productModelDef);
81
+
82
+ // 2. Insert a new document
83
+ const initialData = {
84
+ name: 'Super Widget',
85
+ price: 99.99,
86
+ stock: 100,
87
+ description: 'A very super widget.'
88
+ };
89
+ const insertResult = await insertData(modelName, initialData, {}, testUser);
90
+ expect(insertResult.success).toBe(true);
91
+ const docId = new ObjectId(insertResult.insertedIds[0]);
92
+
93
+ // 3. Verify the history record
94
+ const historyRecord = await historyCollection.findOne({ documentId: new ObjectId(docId) });
95
+
96
+ expect(historyRecord).not.toBeNull();
97
+ expect(historyRecord.documentId.toString()).toBe(docId.toString());
98
+ expect(historyRecord.model).toBe(modelName);
99
+ expect(historyRecord.version).toBe(1);
100
+ expect(historyRecord.operation).toBe('create');
101
+ expect(historyRecord.user.username).toBe(testUser.username);
102
+
103
+ // 4. Verify the snapshot
104
+ expect(historyRecord.snapshot).not.toBeNull();
105
+ expect(historyRecord.snapshot.name).toBe('Super Widget');
106
+ expect(historyRecord.snapshot.price).toBe(99.99);
107
+ expect(historyRecord.snapshot.stock).toBe(100);
108
+ expect(historyRecord.snapshot.description).toBe('A very super widget.');
109
+ expect(historyRecord.changes).toBeUndefined(); // No 'changes' field on creation
110
+ });
111
+
112
+ it('should create a diff history record on document update for historized fields only', async () => {
113
+ // 1. Define and create a model with specific history fields
114
+ const modelName = generateUniqueName('productHistorySelective');
115
+ const productModelDef = {
116
+ name: modelName,
117
+ description:"",
118
+ _user: testUser.username,
119
+ history: {
120
+ enabled: true,
121
+ fields: {
122
+ price: true, // Track this
123
+ stock: true // Track this
124
+ // 'description' is NOT tracked
125
+ }
126
+ },
127
+ fields: [
128
+ { name: 'name', type: 'string', required: true },
129
+ { name: 'price', type: 'number' },
130
+ { name: 'stock', type: 'number' },
131
+ { name: 'description', type: 'string' }
132
+ ]
133
+ };
134
+ await modelsCollection.insertOne(productModelDef);
135
+
136
+ // 2. Insert the initial document
137
+ const initialData = { name: 'Selective Widget', price: 50, stock: 200, description: 'Initial description.' };
138
+ const insertResult = await insertData(modelName, initialData, {}, testUser);
139
+ const docId = new ObjectId(insertResult.insertedIds[0]);
140
+
141
+ // 3. Edit the document: change one historized field and one non-historized field
142
+ const updateData = { price: 55.5, description: 'Updated description.' };
143
+ const editResult = await editData(modelName, { _id: docId }, updateData, {}, testUser);
144
+ expect(editResult.success).toBe(true);
145
+
146
+ // 4. Verify the new history record (v2)
147
+ const historyRecord = await historyCollection.findOne({ documentId: docId, version: 2 });
148
+
149
+ expect(historyRecord).not.toBeNull();
150
+ expect(historyRecord.operation).toBe('update');
151
+ expect(historyRecord.snapshot).toBeUndefined(); // No 'snapshot' on update
152
+
153
+ // 5. Verify the 'changes' object
154
+ const changes = historyRecord.changes;
155
+ expect(changes).not.toBeNull();
156
+ expect(changes.price).toBeDefined();
157
+ expect(changes.price.from).toBe(50);
158
+ expect(changes.price.to).toBe(55.5);
159
+ expect(changes.description).toBeUndefined();
160
+ expect(changes.stock).toBeUndefined();
161
+ });
162
+
163
+ it('should NOT create a history record if only non-historized fields are updated', async () => {
164
+ // 1. Setup model
165
+ const modelName = generateUniqueName('productHistoryNoOp');
166
+ const productModelDef = {
167
+ name: modelName,
168
+ description:"",
169
+ _user: testUser.username,
170
+ history: { enabled: true, fields: { price: true } },
171
+ fields: [
172
+ { name: 'name', type: 'string', required: true },
173
+ { name: 'price', type: 'number' },
174
+ { name: 'description', type: 'string' }
175
+ ]
176
+ };
177
+ await modelsCollection.insertOne(productModelDef);
178
+
179
+ // 2. Insert initial document
180
+ const initialData = { name: 'No-Op Widget', price: 10, description: 'Initial.' };
181
+ const insertResult = await insertData(modelName, initialData, {}, testUser);
182
+ const docId = new ObjectId(insertResult.insertedIds[0]);
183
+
184
+ // 3. Edit ONLY a non-historized field
185
+ const updateData = { description: 'This change should not be recorded.' };
186
+ await editData(modelName, { _id: docId }, updateData, {}, testUser);
187
+
188
+ await sleep(2000);
189
+
190
+ // 4. Verify that NO new history record was created (only the 'create' record exists)
191
+ const historyCount = await historyCollection.countDocuments({ documentId: docId });
192
+ expect(historyCount).toBe(1);
193
+ });
194
+
195
+ it('should filter history records by date range', async () => {
196
+ // 1. Setup model
197
+ const modelName = generateUniqueName('productHistoryDateFilter');
198
+ const productModelDef = {
199
+ name: modelName,
200
+ description: "",
201
+ _user: testUser.username,
202
+ history: { enabled: true },
203
+ fields: [{ name: 'name', type: 'string' }]
204
+ };
205
+ await modelsCollection.insertOne(productModelDef);
206
+
207
+ // 2. Insert initial document
208
+ const insertResult = await insertData(modelName, { name: 'Time-traveling Widget' }, {}, testUser);
209
+ const docId = new ObjectId(insertResult.insertedIds[0]);
210
+
211
+ // 3. Create history entries at different times
212
+ // To simulate different timestamps, we'll manually insert history records
213
+ // as editData() would create them too close together in time.
214
+ await historyCollection.updateOne({ documentId: docId, version: 1 }, { $set: { timestamp: new Date('2023-01-10T10:00:00Z') } });
215
+
216
+ await historyCollection.insertOne({
217
+ documentId: docId, model: modelName, version: 2, operation: 'update',
218
+ timestamp: new Date('2023-02-15T12:00:00Z'), user: { username: testUser.username }, changes: { name: { from: 'v1', to: 'v2' } }
219
+ });
220
+ await historyCollection.insertOne({
221
+ documentId: docId, model: modelName, version: 3, operation: 'update',
222
+ timestamp: new Date('2023-02-20T14:00:00Z'), user: { username: testUser.username }, changes: { name: { from: 'v2', to: 'v3' } }
223
+ });
224
+ await historyCollection.insertOne({
225
+ documentId: docId, model: modelName, version: 4, operation: 'update',
226
+ timestamp: new Date('2023-03-05T16:00:00Z'), user: { username: testUser.username }, changes: { name: { from: 'v3', to: 'v4' } }
227
+ });
228
+
229
+ // Mock Express req/res objects
230
+ const mockReq = (query) => ({
231
+ params: { modelName, recordId: docId.toString() },
232
+ query,
233
+ me: testUser
234
+ });
235
+ const mockRes = () => {
236
+ const res = {};
237
+ res.status = (code) => { res.statusCode = code; return res; };
238
+ res.json = (data) => { res.body = data; return res; };
239
+ return res;
240
+ };
241
+
242
+ // 4. Test cases
243
+ // Case A: Filter for February
244
+ let req = mockReq({ startDate: '2023-02-01', endDate: '2023-02-28' });
245
+ let res = mockRes();
246
+ await handleGetHistoryRequest(req, res);
247
+ expect(res.body.success).toBe(true);
248
+ expect(res.body.count).toBe(2);
249
+ expect(res.body.data.map(d => d._v).sort()).toEqual([2, 3]);
250
+
251
+ // Case B: Filter starting from Feb 15th
252
+ req = mockReq({ startDate: '2023-02-15' });
253
+ res = mockRes();
254
+ await handleGetHistoryRequest(req, res);
255
+ expect(res.body.success).toBe(true);
256
+ expect(res.body.count).toBe(3); // v2, v3, v4
257
+
258
+ // Case C: Filter up to Feb 15th (inclusive)
259
+ req = mockReq({ endDate: '2023-02-15' });
260
+ res = mockRes();
261
+ await handleGetHistoryRequest(req, res);
262
+ expect(res.body.success).toBe(true);
263
+ expect(res.body.count).toBe(2); // v1, v2
264
+ });
193
265
  });