@objectql/server 1.7.3 → 1.8.0

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/test/rest.test.ts CHANGED
@@ -19,6 +19,24 @@ class MockDriver implements Driver {
19
19
  async find(objectName: string, query: any) {
20
20
  let items = this.data[objectName] || [];
21
21
 
22
+ // Apply filters if provided
23
+ if (query && query.filters) {
24
+ const filters = query.filters;
25
+ if (typeof filters === 'object') {
26
+ const filterKeys = Object.keys(filters);
27
+ if (filterKeys.length > 0) {
28
+ items = items.filter(item => {
29
+ for (const [key, value] of Object.entries(filters)) {
30
+ if (item[key] !== value) {
31
+ return false;
32
+ }
33
+ }
34
+ return true;
35
+ });
36
+ }
37
+ }
38
+ }
39
+
22
40
  // Apply skip and limit if provided
23
41
  if (query) {
24
42
  if (query.skip) {
@@ -74,6 +92,80 @@ class MockDriver implements Driver {
74
92
  return (this.data[objectName] || []).length;
75
93
  }
76
94
 
95
+ async createMany(objectName: string, data: any[]) {
96
+ const newItems = data.map(item => ({
97
+ _id: String(this.nextId++),
98
+ ...item
99
+ }));
100
+ if (!this.data[objectName]) {
101
+ this.data[objectName] = [];
102
+ }
103
+ this.data[objectName].push(...newItems);
104
+ return newItems;
105
+ }
106
+
107
+ async updateMany(objectName: string, filters: any, data: any) {
108
+ const items = this.data[objectName] || [];
109
+ let count = 0;
110
+
111
+ // NOTE: Simplified filter implementation for testing purposes only.
112
+ // Production drivers should implement full ObjectQL filter evaluation
113
+ // with support for operators like ["<", value], [">=", value], etc.
114
+ // This mock only supports exact-match comparison on object properties.
115
+ items.forEach((item, index) => {
116
+ let matches = true;
117
+ if (filters && typeof filters === 'object') {
118
+ const filterKeys = Object.keys(filters);
119
+ // If no filter keys, match nothing (not everything)
120
+ if (filterKeys.length === 0) {
121
+ matches = false;
122
+ } else {
123
+ for (const [key, value] of Object.entries(filters)) {
124
+ if (item[key] !== value) {
125
+ matches = false;
126
+ break;
127
+ }
128
+ }
129
+ }
130
+ }
131
+ if (matches) {
132
+ this.data[objectName][index] = { ...item, ...data };
133
+ count++;
134
+ }
135
+ });
136
+
137
+ return count;
138
+ }
139
+
140
+ async deleteMany(objectName: string, filters: any) {
141
+ const items = this.data[objectName] || [];
142
+ const initialLength = items.length;
143
+
144
+ // NOTE: Simplified filter implementation for testing purposes only.
145
+ // Production drivers should implement full ObjectQL filter evaluation.
146
+ // This mock only supports exact-match comparison on object properties.
147
+ this.data[objectName] = items.filter(item => {
148
+ let matches = true;
149
+ if (filters && typeof filters === 'object') {
150
+ const filterKeys = Object.keys(filters);
151
+ // If no filter keys, match nothing (not everything)
152
+ if (filterKeys.length === 0) {
153
+ matches = false;
154
+ } else {
155
+ for (const [key, value] of Object.entries(filters)) {
156
+ if (item[key] !== value) {
157
+ matches = false;
158
+ break;
159
+ }
160
+ }
161
+ }
162
+ }
163
+ return !matches; // Keep items that don't match
164
+ });
165
+
166
+ return initialLength - this.data[objectName].length;
167
+ }
168
+
77
169
  async execute(sql: string) {}
78
170
  }
79
171
 
@@ -201,4 +293,148 @@ describe('REST API Adapter', () => {
201
293
  expect(response.body.meta.page).toBe(2);
202
294
  expect(response.body.meta.has_next).toBe(false);
203
295
  });
296
+
297
+ // Bulk operations tests
298
+ describe('Bulk Operations', () => {
299
+ it('should handle POST /api/data/:object with array - Create many records', async () => {
300
+ const response = await request(server)
301
+ .post('/api/data/user')
302
+ .send([
303
+ { name: 'User1', email: 'user1@example.com' },
304
+ { name: 'User2', email: 'user2@example.com' },
305
+ { name: 'User3', email: 'user3@example.com' }
306
+ ])
307
+ .set('Accept', 'application/json');
308
+
309
+ expect(response.status).toBe(201);
310
+ expect(response.body.items).toBeDefined();
311
+ expect(response.body.items).toHaveLength(3);
312
+ expect(response.body.count).toBe(3);
313
+ expect(response.body['@type']).toBe('user');
314
+ expect(response.body.items[0].name).toBe('User1');
315
+ expect(response.body.items[0]._id).toBeDefined();
316
+ });
317
+
318
+ it('should handle POST /api/data/:object/bulk-update - Update many records', async () => {
319
+ // First create some users
320
+ await request(server)
321
+ .post('/api/data/user')
322
+ .send([
323
+ { name: 'TestUser1', email: 'test1@example.com', role: 'user' },
324
+ { name: 'TestUser2', email: 'test2@example.com', role: 'user' }
325
+ ]);
326
+
327
+ // Now update all users with role 'user'
328
+ const response = await request(server)
329
+ .post('/api/data/user/bulk-update')
330
+ .send({
331
+ filters: { role: 'user' },
332
+ data: { role: 'admin' }
333
+ })
334
+ .set('Accept', 'application/json');
335
+
336
+ expect(response.status).toBe(200);
337
+ expect(response.body.count).toBeGreaterThan(0);
338
+ expect(response.body['@type']).toBe('user');
339
+
340
+ // Verify the records were actually updated
341
+ const verifyResponse = await request(server)
342
+ .get('/api/data/user')
343
+ .set('Accept', 'application/json');
344
+
345
+ const adminUsers = verifyResponse.body.items.filter((u: any) => u.role === 'admin');
346
+ expect(adminUsers.length).toBeGreaterThan(0);
347
+ });
348
+
349
+ it('should handle POST /api/data/:object/bulk-delete - Delete many records', async () => {
350
+ // First create some users
351
+ await request(server)
352
+ .post('/api/data/user')
353
+ .send([
354
+ { name: 'ToDelete1', email: 'delete1@example.com', status: 'inactive' },
355
+ { name: 'ToDelete2', email: 'delete2@example.com', status: 'inactive' }
356
+ ]);
357
+
358
+ // Now delete all inactive users
359
+ const response = await request(server)
360
+ .post('/api/data/user/bulk-delete')
361
+ .send({
362
+ filters: { status: 'inactive' }
363
+ })
364
+ .set('Accept', 'application/json');
365
+
366
+ expect(response.status).toBe(200);
367
+ expect(response.body.count).toBeGreaterThan(0);
368
+ expect(response.body['@type']).toBe('user');
369
+
370
+ // Verify the records were actually deleted
371
+ const verifyResponse = await request(server)
372
+ .get('/api/data/user')
373
+ .set('Accept', 'application/json');
374
+
375
+ const inactiveUsers = verifyResponse.body.items.filter((u: any) => u.status === 'inactive');
376
+ expect(inactiveUsers.length).toBe(0);
377
+ });
378
+
379
+ // Edge case tests
380
+ it('should handle createMany with empty array', async () => {
381
+ const response = await request(server)
382
+ .post('/api/data/user')
383
+ .send([])
384
+ .set('Accept', 'application/json');
385
+
386
+ expect(response.status).toBe(201);
387
+ expect(response.body.items).toHaveLength(0);
388
+ expect(response.body.count).toBe(0);
389
+ });
390
+
391
+ it('should handle updateMany with no matching records', async () => {
392
+ const response = await request(server)
393
+ .post('/api/data/user/bulk-update')
394
+ .send({
395
+ filters: { role: 'nonexistent' },
396
+ data: { role: 'admin' }
397
+ })
398
+ .set('Accept', 'application/json');
399
+
400
+ expect(response.status).toBe(200);
401
+ expect(response.body.count).toBe(0);
402
+ });
403
+
404
+ it('should handle deleteMany with no matching records', async () => {
405
+ const response = await request(server)
406
+ .post('/api/data/user/bulk-delete')
407
+ .send({
408
+ filters: { status: 'nonexistent' }
409
+ })
410
+ .set('Accept', 'application/json');
411
+
412
+ expect(response.status).toBe(200);
413
+ expect(response.body.count).toBe(0);
414
+ });
415
+
416
+ it('should return error for updateMany without data field', async () => {
417
+ const response = await request(server)
418
+ .post('/api/data/user/bulk-update')
419
+ .send({
420
+ filters: { role: 'user' }
421
+ // Missing data field
422
+ })
423
+ .set('Accept', 'application/json');
424
+
425
+ expect(response.status).toBe(400);
426
+ expect(response.body.error.code).toBe('INVALID_REQUEST');
427
+ });
428
+
429
+ it('should return error for createMany with non-array', async () => {
430
+ const response = await request(server)
431
+ .post('/api/data/user')
432
+ .send({ name: 'NotAnArray' }) // Send object instead of array when bulk create expected
433
+ .set('Accept', 'application/json');
434
+
435
+ // Should still work as single create
436
+ expect(response.status).toBe(201);
437
+ expect(response.body._id).toBeDefined();
438
+ });
439
+ });
204
440
  });