@formio/appserver 2.0.0-rc.1 → 2.0.0-rc.4

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.
Files changed (46) hide show
  1. package/README.md +12 -6
  2. package/lib/actions/index.d.ts +11 -11
  3. package/lib/actions/login/LoginAction.d.ts +3 -2
  4. package/lib/actions/login/LoginAction.spec.d.ts +1 -0
  5. package/lib/actions/login/LoginAction.spec.js +15 -0
  6. package/lib/actions/role/RoleAction.spec.d.ts +1 -0
  7. package/lib/actions/role/RoleAction.spec.js +15 -0
  8. package/lib/actions/save/SaveAction.spec.d.ts +1 -0
  9. package/lib/actions/save/SaveAction.spec.js +15 -0
  10. package/lib/modules/auth/auth.spec.d.ts +1 -0
  11. package/lib/modules/auth/auth.spec.js +93 -0
  12. package/lib/modules/index.d.ts +1 -1
  13. package/lib/modules/index.js +2 -2
  14. package/lib/modules/mongodb/db.d.ts +128 -0
  15. package/lib/modules/mongodb/db.js +706 -0
  16. package/lib/modules/mongodb/db.spec.d.ts +1 -0
  17. package/lib/modules/mongodb/db.spec.js +714 -0
  18. package/lib/server.d.ts +3 -6
  19. package/lib/server.js +7 -12
  20. package/package.json +20 -20
  21. package/lib/prepare/access.d.ts +0 -3
  22. package/lib/prepare/access.js +0 -62
  23. package/lib/prepare/bcrypt.d.ts +0 -3
  24. package/lib/prepare/bcrypt.js +0 -25
  25. package/lib/prepare/index.d.ts +0 -11
  26. package/lib/prepare/index.js +0 -60
  27. package/lib/prepare/password.d.ts +0 -3
  28. package/lib/prepare/password.js +0 -27
  29. package/lib/prepare/persist.d.ts +0 -3
  30. package/lib/prepare/persist.js +0 -24
  31. package/lib/prepare/protect.d.ts +0 -3
  32. package/lib/prepare/protect.js +0 -28
  33. package/lib/prepare/reference.d.ts +0 -3
  34. package/lib/prepare/reference.js +0 -71
  35. package/lib/types/action.d.ts +0 -7
  36. package/lib/types/action.js +0 -2
  37. package/lib/types/form.d.ts +0 -4
  38. package/lib/types/form.js +0 -2
  39. package/lib/types/index.d.ts +0 -5
  40. package/lib/types/index.js +0 -21
  41. package/lib/types/project.d.ts +0 -4
  42. package/lib/types/project.js +0 -2
  43. package/lib/types/server.d.ts +0 -102
  44. package/lib/types/server.js +0 -2
  45. package/lib/types/submission.d.ts +0 -47
  46. package/lib/types/submission.js +0 -2
@@ -0,0 +1,714 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const chai_1 = require("chai");
13
+ const db_1 = require("./db");
14
+ const mongodb_memory_server_1 = require("mongodb-memory-server");
15
+ describe('Database', () => {
16
+ let mongod;
17
+ let database;
18
+ let config;
19
+ before(() => __awaiter(void 0, void 0, void 0, function* () {
20
+ mongod = yield mongodb_memory_server_1.MongoMemoryServer.create();
21
+ const uri = mongod.getUri();
22
+ config = {
23
+ url: uri,
24
+ config: {}
25
+ };
26
+ database = new db_1.Database(config);
27
+ }));
28
+ after(() => __awaiter(void 0, void 0, void 0, function* () {
29
+ // Close the database connection
30
+ if (database) {
31
+ yield database.close();
32
+ }
33
+ // Then stop the MongoDB memory server
34
+ if (mongod) {
35
+ yield mongod.stop();
36
+ }
37
+ }));
38
+ describe('constructor', () => {
39
+ it('should initialize with default values', () => {
40
+ const db = new db_1.Database(config);
41
+ (0, chai_1.expect)(db.db).to.be.null;
42
+ (0, chai_1.expect)(db.currentCollectionName).to.equal('submissions');
43
+ (0, chai_1.expect)(db.currentCollection).to.be.null;
44
+ (0, chai_1.expect)(db.defaultCollection).to.be.null;
45
+ (0, chai_1.expect)(db.prefix).to.equal('');
46
+ });
47
+ });
48
+ describe('ObjectId', () => {
49
+ it('should handle null/undefined input', () => {
50
+ (0, chai_1.expect)(database.ObjectId(null)).to.be.null;
51
+ (0, chai_1.expect)(database.ObjectId(undefined)).to.be.null;
52
+ });
53
+ it('should handle $in queries', () => {
54
+ const input = { $in: ['507f1f77bcf86cd799439011', '507f1f77bcf86cd799439012'] };
55
+ const result = database.ObjectId(input);
56
+ (0, chai_1.expect)(result).to.have.property('$in');
57
+ (0, chai_1.expect)(result.$in).to.be.an('array');
58
+ });
59
+ it('should return original value for invalid ObjectId strings', () => {
60
+ const invalidId = 'invalid-id';
61
+ const result = database.ObjectId(invalidId);
62
+ (0, chai_1.expect)(result).to.equal(invalidId);
63
+ });
64
+ });
65
+ describe('collectionName', () => {
66
+ it('should return collection name without prefix', () => {
67
+ database.prefix = '';
68
+ (0, chai_1.expect)(database.collectionName('test')).to.equal('test');
69
+ });
70
+ it('should return collection name with prefix', () => {
71
+ database.prefix = 'myapp.';
72
+ (0, chai_1.expect)(database.collectionName('test')).to.equal('myapp.test');
73
+ });
74
+ });
75
+ describe('connect', () => {
76
+ it('should connect to database', () => __awaiter(void 0, void 0, void 0, function* () {
77
+ yield database.connect();
78
+ (0, chai_1.expect)(database.db).to.not.be.null;
79
+ (0, chai_1.expect)(database.defaultCollection).to.not.be.null;
80
+ }));
81
+ it('should set prefix when provided', () => __awaiter(void 0, void 0, void 0, function* () {
82
+ const db = new db_1.Database(config);
83
+ yield db.connect('testapp');
84
+ (0, chai_1.expect)(db.prefix).to.equal('testapp.');
85
+ }));
86
+ });
87
+ describe('collection', () => {
88
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
89
+ if (!database.db) {
90
+ yield database.connect();
91
+ }
92
+ }));
93
+ it('should return collection when db is connected', () => {
94
+ const collection = database.collection('test');
95
+ (0, chai_1.expect)(collection).to.not.be.null;
96
+ });
97
+ it('should return null when db is not connected', () => {
98
+ const db = new db_1.Database(config);
99
+ const collection = db.collection('test');
100
+ (0, chai_1.expect)(collection).to.be.null;
101
+ });
102
+ });
103
+ describe('CRUD Operations', () => {
104
+ let scope;
105
+ let testCollection;
106
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
107
+ if (!database.db) {
108
+ yield database.connect();
109
+ }
110
+ // Setup a basic scope for testing
111
+ scope = {
112
+ form: {
113
+ _id: '507f1f77bcf86cd799439011',
114
+ components: []
115
+ },
116
+ project: {
117
+ _id: '507f1f77bcf86cd799439012'
118
+ }
119
+ };
120
+ testCollection = yield database.formCollection(scope);
121
+ }));
122
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
123
+ // Clean up test data after each test
124
+ if (testCollection) {
125
+ yield testCollection.deleteMany({});
126
+ }
127
+ }));
128
+ describe('create', () => {
129
+ it('should create a new record with basic data', () => __awaiter(void 0, void 0, void 0, function* () {
130
+ const recordData = {
131
+ data: { name: 'Test User', email: 'test@example.com' },
132
+ metadata: { source: 'test' }
133
+ };
134
+ const result = yield database.create(scope, recordData);
135
+ (0, chai_1.expect)(result).to.not.be.null;
136
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result._id).to.exist;
137
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.data.name).to.equal('Test User');
138
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.data.email).to.equal('test@example.com');
139
+ // Verify using raw MongoDB find
140
+ const rawRecord = yield testCollection.findOne({ _id: result === null || result === void 0 ? void 0 : result._id });
141
+ (0, chai_1.expect)(rawRecord).to.not.be.null;
142
+ (0, chai_1.expect)(rawRecord.data.name).to.equal('Test User');
143
+ (0, chai_1.expect)(rawRecord.data.email).to.equal('test@example.com');
144
+ (0, chai_1.expect)(rawRecord.metadata.source).to.equal('test');
145
+ }));
146
+ it('should set timestamps and system fields', () => __awaiter(void 0, void 0, void 0, function* () {
147
+ const beforeCreate = new Date();
148
+ const recordData = {
149
+ data: { content: 'Test content' }
150
+ };
151
+ const result = yield database.create(scope, recordData);
152
+ const afterCreate = new Date();
153
+ (0, chai_1.expect)(result).to.not.be.null;
154
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.created).to.be.a('date');
155
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.modified).to.be.a('date');
156
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.deleted).to.be.null;
157
+ // Verify using raw MongoDB find
158
+ const rawRecord = yield testCollection.findOne({ _id: result === null || result === void 0 ? void 0 : result._id });
159
+ (0, chai_1.expect)(rawRecord.created.getTime()).to.be.at.least(beforeCreate.getTime());
160
+ (0, chai_1.expect)(rawRecord.created.getTime()).to.be.at.most(afterCreate.getTime());
161
+ (0, chai_1.expect)(rawRecord.form.toString()).to.equal(scope.form._id);
162
+ (0, chai_1.expect)(rawRecord.project.toString()).to.equal(scope.project._id);
163
+ (0, chai_1.expect)(rawRecord.deleted).to.be.null;
164
+ }));
165
+ it('should create multiple records', () => __awaiter(void 0, void 0, void 0, function* () {
166
+ const records = [
167
+ { data: { name: 'User 1', type: 'first' } },
168
+ { data: { name: 'User 2', type: 'second' } },
169
+ { data: { name: 'User 3', type: 'third' } }
170
+ ];
171
+ const results = [];
172
+ for (const recordData of records) {
173
+ const result = yield database.create(scope, recordData);
174
+ results.push(result);
175
+ }
176
+ (0, chai_1.expect)(results).to.have.length(3);
177
+ // Verify all records exist using raw MongoDB find
178
+ const allRecords = yield testCollection.find({}).toArray();
179
+ (0, chai_1.expect)(allRecords).to.have.length(3);
180
+ const names = allRecords.map((record) => record.data.name).sort();
181
+ (0, chai_1.expect)(names).to.deep.equal(['User 1', 'User 2', 'User 3']);
182
+ }));
183
+ });
184
+ describe('read', () => {
185
+ let createdRecords;
186
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
187
+ // Create test data
188
+ const testData = [
189
+ { data: { name: 'John Doe', age: 30, active: 1 } },
190
+ { data: { name: 'Jane Smith', age: 25, active: 0 } },
191
+ { data: { name: 'Bob Johnson', age: 35, active: 1 } }
192
+ ];
193
+ createdRecords = [];
194
+ for (const recordData of testData) {
195
+ const result = yield database.create(scope, recordData);
196
+ createdRecords.push(result);
197
+ }
198
+ }));
199
+ it('should find single record by id string', () => __awaiter(void 0, void 0, void 0, function* () {
200
+ const targetId = createdRecords[0]._id.toString();
201
+ const result = yield database.read(scope, targetId);
202
+ (0, chai_1.expect)(result).to.not.be.null;
203
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result._id.toString()).to.equal(targetId);
204
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.data.name).to.equal('John Doe');
205
+ // Verify against raw MongoDB find
206
+ const rawRecord = yield testCollection.findOne({ _id: createdRecords[0]._id });
207
+ (0, chai_1.expect)(rawRecord).to.not.be.null;
208
+ (0, chai_1.expect)(rawRecord.data.name).to.equal(result === null || result === void 0 ? void 0 : result.data.name);
209
+ }));
210
+ it('should return null for non-existent id', () => __awaiter(void 0, void 0, void 0, function* () {
211
+ const nonExistentId = '507f1f77bcf86cd799439999';
212
+ const result = yield database.read(scope, nonExistentId);
213
+ (0, chai_1.expect)(result).to.be.null;
214
+ // Verify against raw MongoDB find
215
+ const rawRecord = yield testCollection.findOne({ _id: database.ObjectId(nonExistentId) });
216
+ (0, chai_1.expect)(rawRecord).to.be.null;
217
+ }));
218
+ it('should return null for invalid id format', () => __awaiter(void 0, void 0, void 0, function* () {
219
+ const invalidId = 'invalid-id-format';
220
+ const result = yield database.read(scope, invalidId);
221
+ (0, chai_1.expect)(result).to.be.null;
222
+ // Verify against raw MongoDB find - should not find anything
223
+ const rawRecord = yield testCollection.findOne({ _id: invalidId });
224
+ (0, chai_1.expect)(rawRecord).to.be.null;
225
+ }));
226
+ it('should handle ObjectId conversion correctly', () => __awaiter(void 0, void 0, void 0, function* () {
227
+ const targetRecord = createdRecords[1];
228
+ const idString = targetRecord._id.toString();
229
+ const result = yield database.read(scope, idString);
230
+ (0, chai_1.expect)(result).to.not.be.null;
231
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result._id.toString()).to.equal(idString);
232
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.data.name).to.equal('Jane Smith');
233
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.data.age).to.equal(25);
234
+ // Verify against raw MongoDB find
235
+ const rawRecord = yield testCollection.findOne({ _id: targetRecord._id });
236
+ (0, chai_1.expect)(rawRecord.data.name).to.equal('Jane Smith');
237
+ (0, chai_1.expect)(rawRecord.data.age).to.equal(25);
238
+ }));
239
+ it('should return null when database is not connected', () => __awaiter(void 0, void 0, void 0, function* () {
240
+ const db = new db_1.Database(config);
241
+ const result = yield db.read(scope, createdRecords[0]._id.toString());
242
+ (0, chai_1.expect)(result).to.be.null;
243
+ }));
244
+ });
245
+ describe('update', () => {
246
+ let testRecord;
247
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
248
+ // Create a test record
249
+ const recordData = {
250
+ data: { name: 'Original Name', status: 'draft', version: 1 }
251
+ };
252
+ testRecord = yield database.create(scope, recordData);
253
+ }));
254
+ it('should update a single record by id string', () => __awaiter(void 0, void 0, void 0, function* () {
255
+ const updateData = {
256
+ data: { name: 'Updated Name', status: 'published', version: 2 }
257
+ };
258
+ yield new Promise(resolve => setTimeout(resolve, 10)); // Ensure timestamp difference
259
+ const result = yield database.update(scope, testRecord._id.toString(), updateData);
260
+ (0, chai_1.expect)(result).to.not.be.null;
261
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.data.name).to.equal('Updated Name');
262
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.data.status).to.equal('published');
263
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.data.version).to.equal(2);
264
+ // Verify against raw MongoDB find
265
+ const rawRecord = yield testCollection.findOne({ _id: testRecord._id });
266
+ (0, chai_1.expect)(rawRecord.data.name).to.equal('Updated Name');
267
+ (0, chai_1.expect)(rawRecord.data.status).to.equal('published');
268
+ (0, chai_1.expect)(rawRecord.data.version).to.equal(2);
269
+ (0, chai_1.expect)(rawRecord.modified.getTime()).to.be.greaterThan(rawRecord.created.getTime());
270
+ }));
271
+ it('should return null for non-existent id', () => __awaiter(void 0, void 0, void 0, function* () {
272
+ const updateData = { data: { status: 'updated' } };
273
+ const nonExistentId = '507f1f77bcf86cd799439999';
274
+ const result = yield database.update(scope, nonExistentId, updateData);
275
+ (0, chai_1.expect)(result).to.be.null;
276
+ // Verify original record unchanged
277
+ const rawRecord = yield testCollection.findOne({ _id: testRecord._id });
278
+ (0, chai_1.expect)(rawRecord.data.name).to.equal('Original Name');
279
+ (0, chai_1.expect)(rawRecord.data.status).to.equal('draft');
280
+ }));
281
+ it('should return null for invalid id format', () => __awaiter(void 0, void 0, void 0, function* () {
282
+ const updateData = { data: { status: 'updated' } };
283
+ const invalidId = 'invalid-id-format';
284
+ const result = yield database.update(scope, invalidId, updateData);
285
+ (0, chai_1.expect)(result).to.be.null;
286
+ // Verify original record unchanged
287
+ const rawRecord = yield testCollection.findOne({ _id: testRecord._id });
288
+ (0, chai_1.expect)(rawRecord.data.name).to.equal('Original Name');
289
+ }));
290
+ it('should update timestamps correctly', () => __awaiter(void 0, void 0, void 0, function* () {
291
+ const originalModified = testRecord.modified;
292
+ const updateData = {
293
+ data: { status: 'updated' }
294
+ };
295
+ // Wait a moment to ensure timestamp difference
296
+ yield new Promise(resolve => setTimeout(resolve, 10));
297
+ const result = yield database.update(scope, testRecord._id.toString(), updateData);
298
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.modified.getTime()).to.be.greaterThan(originalModified.getTime());
299
+ // Verify against raw MongoDB find
300
+ const rawRecord = yield testCollection.findOne({ _id: testRecord._id });
301
+ (0, chai_1.expect)(rawRecord.modified.getTime()).to.be.greaterThan(originalModified.getTime());
302
+ }));
303
+ it('should return null when database is not connected', () => __awaiter(void 0, void 0, void 0, function* () {
304
+ const db = new db_1.Database(config);
305
+ const updateData = { data: { status: 'updated' } };
306
+ const result = yield db.update(scope, testRecord._id.toString(), updateData);
307
+ (0, chai_1.expect)(result).to.be.null;
308
+ }));
309
+ });
310
+ describe('delete', () => {
311
+ let testRecords;
312
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
313
+ // Create test records
314
+ const testData = [
315
+ { data: { name: 'Record 1', category: 'temp' } },
316
+ { data: { name: 'Record 2', category: 'temp' } },
317
+ { data: { name: 'Record 3', category: 'permanent' } }
318
+ ];
319
+ testRecords = [];
320
+ for (const recordData of testData) {
321
+ const result = yield database.create(scope, recordData);
322
+ testRecords.push(result);
323
+ }
324
+ }));
325
+ it('should soft delete a single record by id string', () => __awaiter(void 0, void 0, void 0, function* () {
326
+ const targetRecord = testRecords[0];
327
+ const result = yield database.delete(scope, targetRecord._id.toString());
328
+ (0, chai_1.expect)(result).to.be.true;
329
+ // Verify against raw MongoDB find
330
+ const rawRecord = yield testCollection.findOne({ _id: targetRecord._id });
331
+ (0, chai_1.expect)(rawRecord.deleted).to.not.be.null;
332
+ (0, chai_1.expect)(rawRecord.deleted).to.be.a('date');
333
+ // Verify record still exists in database (soft delete)
334
+ const allRecords = yield testCollection.find({}).toArray();
335
+ (0, chai_1.expect)(allRecords).to.have.length(3);
336
+ }));
337
+ it('should hard delete a single record when specified', () => __awaiter(void 0, void 0, void 0, function* () {
338
+ const targetRecord = testRecords[0];
339
+ const result = yield database.delete(scope, targetRecord._id.toString(), true);
340
+ (0, chai_1.expect)(result).to.be.true;
341
+ // Verify record is completely removed from database
342
+ const rawRecord = yield testCollection.findOne({ _id: targetRecord._id });
343
+ (0, chai_1.expect)(rawRecord).to.be.null;
344
+ // Verify total count decreased
345
+ const allRecords = yield testCollection.find({}).toArray();
346
+ (0, chai_1.expect)(allRecords).to.have.length(2);
347
+ }));
348
+ it('should return false for non-existent id', () => __awaiter(void 0, void 0, void 0, function* () {
349
+ const nonExistentId = '507f1f77bcf86cd799439999';
350
+ const result = yield database.delete(scope, nonExistentId);
351
+ (0, chai_1.expect)(result).to.be.false;
352
+ // Verify no records were affected
353
+ const allRecords = yield testCollection.find({}).toArray();
354
+ (0, chai_1.expect)(allRecords).to.have.length(3);
355
+ allRecords.forEach((record) => {
356
+ (0, chai_1.expect)(record.deleted).to.be.null;
357
+ });
358
+ }));
359
+ it('should return false for invalid id format', () => __awaiter(void 0, void 0, void 0, function* () {
360
+ const invalidId = 'invalid-id-format';
361
+ const result = yield database.delete(scope, invalidId);
362
+ (0, chai_1.expect)(result).to.be.false;
363
+ // Verify no records were affected
364
+ const allRecords = yield testCollection.find({}).toArray();
365
+ (0, chai_1.expect)(allRecords).to.have.length(3);
366
+ }));
367
+ it('should handle deletion of already deleted record', () => __awaiter(void 0, void 0, void 0, function* () {
368
+ // First, soft delete a record
369
+ const targetRecord = testRecords[0];
370
+ const firstDelete = yield database.delete(scope, targetRecord._id.toString());
371
+ (0, chai_1.expect)(firstDelete).to.be.true;
372
+ // Try to delete it again
373
+ const secondDelete = yield database.delete(scope, targetRecord._id.toString());
374
+ (0, chai_1.expect)(secondDelete).to.be.false; // Should be false
375
+ // Verify it's still soft deleted (not hard deleted)
376
+ const rawRecord = yield testCollection.findOne({ _id: targetRecord._id });
377
+ (0, chai_1.expect)(rawRecord).to.not.be.null;
378
+ (0, chai_1.expect)(rawRecord.deleted).to.not.be.null;
379
+ }));
380
+ it('should hard delete an already soft deleted record', () => __awaiter(void 0, void 0, void 0, function* () {
381
+ const targetRecord = testRecords[0];
382
+ // First, soft delete the record
383
+ const softDelete = yield database.delete(scope, targetRecord._id.toString());
384
+ (0, chai_1.expect)(softDelete).to.be.true;
385
+ // Then hard delete it
386
+ const hardDelete = yield database.delete(scope, targetRecord._id.toString(), true);
387
+ (0, chai_1.expect)(hardDelete).to.be.true;
388
+ // Verify record is completely removed from database
389
+ const rawRecord = yield testCollection.findOne({ _id: targetRecord._id });
390
+ (0, chai_1.expect)(rawRecord).to.be.null;
391
+ // Verify total count decreased
392
+ const allRecords = yield testCollection.find({}).toArray();
393
+ (0, chai_1.expect)(allRecords).to.have.length(2);
394
+ }));
395
+ it('should update modified timestamp on soft delete', () => __awaiter(void 0, void 0, void 0, function* () {
396
+ const targetRecord = testRecords[0];
397
+ const originalModified = targetRecord.modified;
398
+ // Wait a moment to ensure timestamp difference
399
+ yield new Promise(resolve => setTimeout(resolve, 10));
400
+ const result = yield database.delete(scope, targetRecord._id.toString());
401
+ (0, chai_1.expect)(result).to.be.true;
402
+ // Verify against raw MongoDB find
403
+ const rawRecord = yield testCollection.findOne({ _id: targetRecord._id });
404
+ (0, chai_1.expect)(rawRecord.modified.getTime()).to.be.greaterThan(originalModified.getTime());
405
+ }));
406
+ it('should return false when database is not connected', () => __awaiter(void 0, void 0, void 0, function* () {
407
+ const db = new db_1.Database(config);
408
+ const result = yield db.delete(scope, testRecords[0]._id.toString());
409
+ (0, chai_1.expect)(result).to.be.false;
410
+ }));
411
+ });
412
+ describe('CRUD Integration Tests', () => {
413
+ it('should perform complete CRUD lifecycle', () => __awaiter(void 0, void 0, void 0, function* () {
414
+ // CREATE
415
+ const createData = {
416
+ data: { name: 'CRUD Test User', email: 'crud@test.com', status: 'new' }
417
+ };
418
+ const created = yield database.create(scope, createData);
419
+ (0, chai_1.expect)(created).to.not.be.null;
420
+ // Verify creation with raw MongoDB
421
+ let rawRecord = yield testCollection.findOne({ _id: created === null || created === void 0 ? void 0 : created._id });
422
+ (0, chai_1.expect)(rawRecord.data.name).to.equal('CRUD Test User');
423
+ // READ
424
+ const readResult = yield database.read(scope, created === null || created === void 0 ? void 0 : created._id.toString());
425
+ (0, chai_1.expect)(readResult).to.not.be.null;
426
+ (0, chai_1.expect)(readResult === null || readResult === void 0 ? void 0 : readResult.data.name).to.equal('CRUD Test User');
427
+ // UPDATE
428
+ const updateData = {
429
+ data: { name: 'Updated CRUD User', status: 'modified' }
430
+ };
431
+ const updated = yield database.update(scope, created === null || created === void 0 ? void 0 : created._id.toString(), updateData);
432
+ (0, chai_1.expect)(updated).to.not.be.null;
433
+ (0, chai_1.expect)(updated === null || updated === void 0 ? void 0 : updated.data.name).to.equal('Updated CRUD User');
434
+ // Verify update with raw MongoDB
435
+ rawRecord = yield testCollection.findOne({ _id: created === null || created === void 0 ? void 0 : created._id });
436
+ (0, chai_1.expect)(rawRecord.data.name).to.equal('Updated CRUD User');
437
+ (0, chai_1.expect)(rawRecord.data.status).to.equal('modified');
438
+ // DELETE (soft)
439
+ const deleted = yield database.delete(scope, created === null || created === void 0 ? void 0 : created._id.toString());
440
+ (0, chai_1.expect)(deleted).to.be.true;
441
+ // Verify soft delete with raw MongoDB
442
+ rawRecord = yield testCollection.findOne({ _id: created === null || created === void 0 ? void 0 : created._id });
443
+ (0, chai_1.expect)(rawRecord).to.not.be.null;
444
+ (0, chai_1.expect)(rawRecord.deleted).to.not.be.null;
445
+ // HARD DELETE
446
+ const hardDeleted = yield database.delete(scope, created === null || created === void 0 ? void 0 : created._id.toString(), true);
447
+ (0, chai_1.expect)(hardDeleted).to.be.true;
448
+ // Verify hard delete with raw MongoDB
449
+ rawRecord = yield testCollection.findOne({ _id: created === null || created === void 0 ? void 0 : created._id });
450
+ (0, chai_1.expect)(rawRecord).to.be.null;
451
+ }));
452
+ });
453
+ describe('index', () => {
454
+ let testRecords;
455
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
456
+ // Create a comprehensive dataset for testing
457
+ const testData = [
458
+ { data: { name: 'Alice Johnson', age: 28, department: 'Engineering', active: true, salary: 75000 } },
459
+ { data: { name: 'Bob Smith', age: 35, department: 'Marketing', active: true, salary: 65000 } },
460
+ { data: { name: 'Carol Davis', age: 42, department: 'Engineering', active: false, salary: 85000 } },
461
+ { data: { name: 'David Wilson', age: 29, department: 'Sales', active: true, salary: 55000 } },
462
+ { data: { name: 'Eve Brown', age: 31, department: 'Engineering', active: true, salary: 78000 } },
463
+ { data: { name: 'Frank Miller', age: 45, department: 'Marketing', active: false, salary: 70000 } },
464
+ { data: { name: 'Grace Lee', age: 26, department: 'Sales', active: true, salary: 52000 } },
465
+ { data: { name: 'Henry Taylor', age: 38, department: 'Engineering', active: true, salary: 82000 } },
466
+ { data: { name: 'Ivy Chen', age: 33, department: 'Marketing', active: true, salary: 68000 } },
467
+ { data: { name: 'Jack White', age: 27, department: 'Sales', active: false, salary: 49000 } }
468
+ ];
469
+ testRecords = [];
470
+ for (const recordData of testData) {
471
+ const result = yield database.create(scope, recordData);
472
+ testRecords.push(result);
473
+ }
474
+ // Wait a moment to ensure all records are created with different timestamps
475
+ yield new Promise(resolve => setTimeout(resolve, 10));
476
+ }));
477
+ it('should return all records with default parameters', () => __awaiter(void 0, void 0, void 0, function* () {
478
+ var _a, _b;
479
+ const result = yield database.index(scope);
480
+ (0, chai_1.expect)(result).to.not.be.null;
481
+ (0, chai_1.expect)((_a = result === null || result === void 0 ? void 0 : result.items) === null || _a === void 0 ? void 0 : _a.length).to.be.at.most(10); // Default limit might be 10
482
+ // Verify against raw MongoDB find (matching the filters applied by the query method)
483
+ const rawQuery = {
484
+ deleted: null,
485
+ form: database.ObjectId(scope.form._id),
486
+ project: database.ObjectId(scope.project._id)
487
+ };
488
+ // Apply the same default limit and sort as the index method
489
+ const rawRecords = yield testCollection.find(rawQuery).sort({ created: -1 }).limit(10).toArray();
490
+ (0, chai_1.expect)(rawRecords).to.have.length(10);
491
+ // Compare first few records (accounting for potential default limit)
492
+ const compareLength = Math.min((_b = result === null || result === void 0 ? void 0 : result.items) === null || _b === void 0 ? void 0 : _b.length, rawRecords.length);
493
+ (0, chai_1.expect)(result.items.length).to.equal(compareLength);
494
+ // Verify that the returned records match the raw query results in content and order
495
+ for (let i = 0; i < compareLength; i++) {
496
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.items[i].data.name).to.equal(rawRecords[i].data.name);
497
+ (0, chai_1.expect)(result === null || result === void 0 ? void 0 : result.items[i].data.department).to.equal(rawRecords[i].data.department);
498
+ }
499
+ }));
500
+ it('should handle query filtering', () => __awaiter(void 0, void 0, void 0, function* () {
501
+ const query = { 'data.department': 'Engineering' };
502
+ const result = yield database.index(scope, query);
503
+ (0, chai_1.expect)(result).to.not.be.null;
504
+ (0, chai_1.expect)(result.items.length).to.equal(4); // Should match 4 engineering records
505
+ // Verify all returned records match the query
506
+ result.items.forEach((record) => {
507
+ (0, chai_1.expect)(record.data.department).to.equal('Engineering');
508
+ });
509
+ // Verify against raw MongoDB find
510
+ const rawRecords = yield testCollection.find(query).sort({ created: -1 }).toArray();
511
+ (0, chai_1.expect)(rawRecords).to.have.length(4);
512
+ (0, chai_1.expect)(result.items.length).to.equal(rawRecords.length);
513
+ // Compare records
514
+ for (let i = 0; i < result.items.length; i++) {
515
+ (0, chai_1.expect)(result.items[i]._id.toString()).to.equal(rawRecords[i]._id.toString());
516
+ (0, chai_1.expect)(result.items[i].data.department).to.equal('Engineering');
517
+ }
518
+ }));
519
+ it('should handle complex query filtering', () => __awaiter(void 0, void 0, void 0, function* () {
520
+ const query = {
521
+ 'data.active': true,
522
+ 'data.salary': { $gte: 70000 }
523
+ };
524
+ const result = yield database.index(scope, query);
525
+ (0, chai_1.expect)(result).to.not.be.null;
526
+ // Verify all returned records match the complex query
527
+ result.items.forEach((record) => {
528
+ (0, chai_1.expect)(record.data.active).to.be.true;
529
+ (0, chai_1.expect)(record.data.salary).to.be.at.least(70000);
530
+ });
531
+ // Verify against raw MongoDB find
532
+ const rawRecords = yield testCollection.find(query).sort({ created: -1 }).toArray();
533
+ (0, chai_1.expect)(result.items.length).to.equal(rawRecords.length);
534
+ // Compare records
535
+ for (let i = 0; i < result.items.length; i++) {
536
+ (0, chai_1.expect)(result.items[i]._id.toString()).to.equal(rawRecords[i]._id.toString());
537
+ (0, chai_1.expect)(result.items[i].data.active).to.be.true;
538
+ (0, chai_1.expect)(result.items[i].data.salary).to.be.at.least(70000);
539
+ }
540
+ }));
541
+ it('should handle limit parameter', () => __awaiter(void 0, void 0, void 0, function* () {
542
+ const limit = 3;
543
+ const result = yield database.index(scope, { limit });
544
+ (0, chai_1.expect)(result).to.not.be.null;
545
+ (0, chai_1.expect)(result.items.length).to.equal(limit);
546
+ // Verify against raw MongoDB find with limit
547
+ const rawRecords = yield testCollection.find({}).sort({ created: -1 }).limit(limit).toArray();
548
+ (0, chai_1.expect)(rawRecords).to.have.length(limit);
549
+ (0, chai_1.expect)(result.items.length).to.equal(rawRecords.length);
550
+ // Compare records
551
+ for (let i = 0; i < result.items.length; i++) {
552
+ (0, chai_1.expect)(result.items[i]._id.toString()).to.equal(rawRecords[i]._id.toString());
553
+ }
554
+ }));
555
+ it('should handle skip parameter for pagination', () => __awaiter(void 0, void 0, void 0, function* () {
556
+ const limit = 3;
557
+ const skip = 2;
558
+ const result = yield database.index(scope, { limit, skip });
559
+ (0, chai_1.expect)(result).to.not.be.null;
560
+ (0, chai_1.expect)(result.items.length).to.equal(limit);
561
+ // Verify against raw MongoDB find with skip and limit
562
+ const rawRecords = yield testCollection.find({}).sort({ created: -1 }).skip(skip).limit(limit).toArray();
563
+ (0, chai_1.expect)(rawRecords).to.have.length(limit);
564
+ (0, chai_1.expect)(result.items.length).to.equal(rawRecords.length);
565
+ // Compare records
566
+ for (let i = 0; i < result.items.length; i++) {
567
+ (0, chai_1.expect)(result.items[i]._id.toString()).to.equal(rawRecords[i]._id.toString());
568
+ }
569
+ }));
570
+ it('should handle custom sort parameter', () => __awaiter(void 0, void 0, void 0, function* () {
571
+ const sort = { 'data.age': 1 }; // Sort by age ascending
572
+ const result = yield database.index(scope, { sort, limit: 10, skip: 0 });
573
+ (0, chai_1.expect)(result).to.not.be.null;
574
+ // Verify sorting
575
+ for (let i = 1; i < result.items.length; i++) {
576
+ (0, chai_1.expect)(result.items[i].data.age).to.be.at.least(result.items[i - 1].data.age);
577
+ }
578
+ // Verify against raw MongoDB find with custom sort
579
+ const rawRecords = yield testCollection.find({}).sort(sort).toArray();
580
+ (0, chai_1.expect)(result.items.length).to.be.at.most(rawRecords.length);
581
+ // Compare first records (accounting for potential limit)
582
+ const compareLength = Math.min(result.items.length, rawRecords.length);
583
+ for (let i = 0; i < compareLength; i++) {
584
+ (0, chai_1.expect)(result.items[i]._id.toString()).to.equal(rawRecords[i]._id.toString());
585
+ (0, chai_1.expect)(result.items[i].data.age).to.equal(rawRecords[i].data.age);
586
+ }
587
+ }));
588
+ it('should handle select parameter for field projection', () => __awaiter(void 0, void 0, void 0, function* () {
589
+ const select = { 'data.name': 1, 'data.department': 1 };
590
+ const result = yield database.index(scope, { limit: 10, skip: 0, sort: { created: -1 }, select });
591
+ (0, chai_1.expect)(result).to.not.be.null;
592
+ // Verify field projection (only selected fields should be present in data)
593
+ result.items.forEach((record) => {
594
+ (0, chai_1.expect)(record.data.name).to.exist;
595
+ (0, chai_1.expect)(record.data.department).to.exist;
596
+ // Other fields should not be present (depending on implementation)
597
+ // Note: MongoDB projection behavior may vary based on implementation
598
+ });
599
+ // Verify against raw MongoDB find with projection
600
+ const rawRecords = yield testCollection.find({}, { projection: select }).sort({ created: -1 }).toArray();
601
+ (0, chai_1.expect)(result.items.length).to.be.at.most(rawRecords.length);
602
+ }));
603
+ it('should handle empty query results', () => __awaiter(void 0, void 0, void 0, function* () {
604
+ const query = { 'data.department': 'NonExistentDepartment' };
605
+ const result = yield database.index(scope, query);
606
+ (0, chai_1.expect)(result).to.not.be.null;
607
+ (0, chai_1.expect)(result.items.length).to.equal(0);
608
+ // Verify against raw MongoDB find
609
+ const rawRecords = yield testCollection.find(query).toArray();
610
+ (0, chai_1.expect)(rawRecords).to.have.length(0);
611
+ (0, chai_1.expect)(result.items.length).to.equal(rawRecords.length);
612
+ }));
613
+ it('should exclude soft deleted records by default', () => __awaiter(void 0, void 0, void 0, function* () {
614
+ // Soft delete one record
615
+ yield database.delete(scope, testRecords[0]._id.toString());
616
+ const result = yield database.index(scope);
617
+ (0, chai_1.expect)(result).to.not.be.null;
618
+ (0, chai_1.expect)(result.items.length).to.equal(9); // Should be one less
619
+ // Verify deleted record is not included
620
+ const deletedRecordInResult = result.items.find((record) => record._id.toString() === testRecords[0]._id.toString());
621
+ (0, chai_1.expect)(deletedRecordInResult).to.be.undefined;
622
+ // Verify against raw MongoDB find (excluding deleted)
623
+ const rawRecords = yield testCollection.find({ deleted: null }).sort({ created: -1 }).toArray();
624
+ (0, chai_1.expect)(rawRecords).to.have.length(9);
625
+ (0, chai_1.expect)(result.items.length).to.be.at.most(rawRecords.length);
626
+ }));
627
+ it('should handle includeDeleted parameter if available', () => __awaiter(void 0, void 0, void 0, function* () {
628
+ // Soft delete one record
629
+ yield database.delete(scope, testRecords[0]._id.toString());
630
+ // This test assumes the index method has an includeDeleted parameter
631
+ // Adjust based on actual implementation
632
+ const result = yield database.index(scope, { limit: 10, skip: 0, sort: { created: -1 }, select: {}, includeDeleted: true });
633
+ (0, chai_1.expect)(result).to.not.be.null;
634
+ (0, chai_1.expect)(result.items.length).to.equal(10); // Should include deleted record
635
+ // Verify against raw MongoDB find (including deleted)
636
+ const rawRecords = yield testCollection.find({}).sort({ created: -1 }).toArray();
637
+ (0, chai_1.expect)(rawRecords).to.have.length(10);
638
+ (0, chai_1.expect)(result.items.length).to.be.at.most(rawRecords.length);
639
+ }));
640
+ it('should handle pagination correctly across multiple pages', () => __awaiter(void 0, void 0, void 0, function* () {
641
+ const pageSize = 3;
642
+ const pages = [];
643
+ // Get first 3 pages
644
+ for (let page = 0; page < 3; page++) {
645
+ const skip = page * pageSize;
646
+ const result = yield database.index(scope, { limit: pageSize, skip });
647
+ pages.push(result);
648
+ }
649
+ // Verify no duplicate records across pages
650
+ const allIds = pages.flatMap(page => page.items).map((record) => record._id.toString());
651
+ const uniqueIds = [...new Set(allIds)];
652
+ (0, chai_1.expect)(allIds.length).to.equal(uniqueIds.length);
653
+ // Verify against raw MongoDB pagination
654
+ const rawPage1 = yield testCollection.find({}).sort({ created: -1 }).limit(pageSize).toArray();
655
+ const rawPage2 = yield testCollection.find({}).sort({ created: -1 }).skip(pageSize).limit(pageSize).toArray();
656
+ const rawPage3 = yield testCollection.find({}).sort({ created: -1 }).skip(pageSize * 2).limit(pageSize).toArray();
657
+ // Compare first page
658
+ (0, chai_1.expect)(pages[0].items.length).to.equal(rawPage1.length);
659
+ for (let i = 0; i < pages[0].items.length; i++) {
660
+ (0, chai_1.expect)(pages[0].items[i]._id.toString()).to.equal(rawPage1[i]._id.toString());
661
+ }
662
+ // Compare second page
663
+ (0, chai_1.expect)(pages[1].items.length).to.equal(rawPage2.length);
664
+ for (let i = 0; i < pages[1].items.length; i++) {
665
+ (0, chai_1.expect)(pages[1].items[i]._id.toString()).to.equal(rawPage2[i]._id.toString());
666
+ }
667
+ }));
668
+ it('should return empty array when database is not connected', () => __awaiter(void 0, void 0, void 0, function* () {
669
+ const db = new db_1.Database(config);
670
+ const result = yield db.index(scope);
671
+ (0, chai_1.expect)(result.items).to.be.an('array');
672
+ (0, chai_1.expect)(result.items.length).to.equal(0);
673
+ }));
674
+ it('should handle ObjectId queries correctly', () => __awaiter(void 0, void 0, void 0, function* () {
675
+ const targetRecord = testRecords[0];
676
+ const query = { _id: targetRecord._id };
677
+ const result = yield database.index(scope, query);
678
+ (0, chai_1.expect)(result).to.not.be.null;
679
+ (0, chai_1.expect)(result.items.length).to.equal(1);
680
+ (0, chai_1.expect)(result.items[0]._id.toString()).to.equal(targetRecord._id.toString());
681
+ // Verify against raw MongoDB find
682
+ const rawRecord = yield testCollection.findOne({ _id: targetRecord._id });
683
+ (0, chai_1.expect)(rawRecord).to.not.be.null;
684
+ (0, chai_1.expect)(result.items[0].data.name).to.equal(rawRecord.data.name);
685
+ }));
686
+ it('should handle regex queries', () => __awaiter(void 0, void 0, void 0, function* () {
687
+ const query = { 'data.name': { $regex: /^A/, $options: 'i' } }; // Names starting with 'A'
688
+ const result = yield database.index(scope, query);
689
+ (0, chai_1.expect)(result).to.not.be.null;
690
+ (0, chai_1.expect)(result.items.length).to.equal(1); // Should match 'Alice Johnson'
691
+ (0, chai_1.expect)(result.items[0].data.name).to.equal('Alice Johnson');
692
+ // Verify against raw MongoDB find
693
+ const rawRecords = yield testCollection.find(query).sort({ created: -1 }).toArray();
694
+ (0, chai_1.expect)(rawRecords).to.have.length(1);
695
+ (0, chai_1.expect)(result.items.length).to.equal(rawRecords.length);
696
+ (0, chai_1.expect)(result.items[0].data.name).to.equal(rawRecords[0].data.name);
697
+ }));
698
+ it('should handle $in queries', () => __awaiter(void 0, void 0, void 0, function* () {
699
+ const query = { 'data.department': { $in: ['Engineering', 'Sales'] } };
700
+ const result = yield database.index(scope, query);
701
+ (0, chai_1.expect)(result).to.not.be.null;
702
+ (0, chai_1.expect)(result.items.length).to.equal(7); // 4 Engineering + 3 Sales
703
+ // Verify all returned records match the query
704
+ result.items.forEach((record) => {
705
+ (0, chai_1.expect)(['Engineering', 'Sales']).to.include(record.data.department);
706
+ });
707
+ // Verify against raw MongoDB find
708
+ const rawRecords = yield testCollection.find(query).sort({ created: -1 }).toArray();
709
+ (0, chai_1.expect)(rawRecords).to.have.length(7);
710
+ (0, chai_1.expect)(result.items.length).to.equal(rawRecords.length);
711
+ }));
712
+ });
713
+ });
714
+ });