@reldens/storage 0.88.0 → 0.90.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.
Files changed (56) hide show
  1. package/.claude/driver-methods-complete-analysis.md +1978 -0
  2. package/.claude/test-architecture.md +973 -0
  3. package/CLAUDE.md +132 -3
  4. package/bin/reldens-storage.js +1 -1
  5. package/lib/base-driver.js +8 -0
  6. package/lib/entities-generator.js +1 -3
  7. package/lib/entity-templates/entities-translations.template +3 -0
  8. package/lib/entity-templates/mikro-orm-model.template +2 -1
  9. package/lib/generators/entities-generation.js +12 -1
  10. package/lib/generators/entities-translations-generation.js +40 -4
  11. package/lib/generators/models-generation.js +126 -5
  12. package/lib/mikro-orm/mikro-orm-data-server.js +24 -10
  13. package/lib/mikro-orm/mikro-orm-driver.js +184 -116
  14. package/lib/objection-js/objection-js-driver.js +111 -19
  15. package/lib/prisma/prisma-data-server.js +3 -2
  16. package/lib/prisma/prisma-driver.js +12 -10
  17. package/lib/prisma/prisma-filter-processor.js +193 -146
  18. package/lib/prisma/prisma-metadata-loader.js +0 -4
  19. package/lib/prisma/prisma-relation-resolver.js +292 -267
  20. package/lib/prisma/prisma-type-caster.js +280 -260
  21. package/package.json +15 -6
  22. package/tests/.env.test.example +7 -0
  23. package/tests/fixtures/categories-fixtures.js +164 -0
  24. package/tests/fixtures/expected-entities/entities/test-categories-entity.js +70 -0
  25. package/tests/fixtures/expected-entities/entities/test-products-entity.js +95 -0
  26. package/tests/fixtures/expected-entities/entities/test-reviews-entity.js +84 -0
  27. package/tests/fixtures/expected-entities/entities-config.js +17 -0
  28. package/tests/fixtures/expected-entities/entities-translations.js +53 -0
  29. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +23 -0
  30. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +57 -0
  31. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +79 -0
  32. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +70 -0
  33. package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +23 -0
  34. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-categories-model.js +33 -0
  35. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +42 -0
  36. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-reviews-model.js +33 -0
  37. package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +23 -0
  38. package/tests/fixtures/expected-entities-prisma/models/prisma/test-categories-model.js +43 -0
  39. package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +50 -0
  40. package/tests/fixtures/expected-entities-prisma/models/prisma/test-reviews-model.js +46 -0
  41. package/tests/fixtures/products-fixtures.js +119 -0
  42. package/tests/fixtures/reviews-fixtures.js +42 -0
  43. package/tests/fixtures/sql/test-schema.sql +59 -0
  44. package/tests/integration/test-drivers.js +257 -0
  45. package/tests/integration/test-nested-filters.js +408 -0
  46. package/tests/integration/test-relations.js +349 -0
  47. package/tests/run-tests.js +213 -0
  48. package/tests/unit/test-drivers.js +89 -0
  49. package/tests/unit/test-entities-generator.js +533 -0
  50. package/tests/unit/test-entity-manager.js +128 -0
  51. package/tests/unit/test-type-mapper.js +232 -0
  52. package/tests/utils/custom-reporter.js +73 -0
  53. package/tests/utils/driver-registry.js +144 -0
  54. package/tests/utils/prisma-subprocess-worker.js +150 -0
  55. package/tests/utils/test-helpers.js +726 -0
  56. package/tests/utils/test-runner.js +63 -0
@@ -0,0 +1,257 @@
1
+ /**
2
+ *
3
+ * Reldens - Drivers Integration Test
4
+ * Tests CRUD operations across all three storage drivers
5
+ *
6
+ */
7
+
8
+ const { TestRunner, assert } = require('../utils/test-runner');
9
+ const { TestHelpers } = require('../utils/test-helpers');
10
+ const { CategoriesFixtures } = require('../fixtures/categories-fixtures');
11
+ const { ProductsFixtures } = require('../fixtures/products-fixtures');
12
+ const { ReviewsFixtures } = require('../fixtures/reviews-fixtures');
13
+
14
+ class DriversTest
15
+ {
16
+
17
+ constructor(dataServer, repos, driverName)
18
+ {
19
+ this.dataServer = dataServer;
20
+ this.categoriesRepo = repos.testCategories;
21
+ this.productsRepo = repos.testProducts;
22
+ this.reviewsRepo = repos.testReviews;
23
+ this.driverName = driverName;
24
+ this.runner = new TestRunner();
25
+ }
26
+
27
+ async run()
28
+ {
29
+ this.runner.suite('Driver: '+this.driverName);
30
+ await this.testCreateOperations();
31
+ await this.testUpdateOperations();
32
+ await this.testQueryOperations();
33
+ await this.testDeleteOperations();
34
+ await this.testReviewsCrud();
35
+ return this.runner.getResults();
36
+ }
37
+
38
+ async testCreateOperations()
39
+ {
40
+ this.runner.group('CREATE Operations');
41
+ await TestHelpers.cleanDatabase(this.dataServer);
42
+ await this.categoriesRepo.create(CategoriesFixtures.category_create_single);
43
+ let categoryJson = await this.categoriesRepo.create(CategoriesFixtures.category_create_json);
44
+ await this.productsRepo.create({...ProductsFixtures.product_create_json, category_id: categoryJson.id});
45
+ let categoryEnum = await this.categoriesRepo.create(CategoriesFixtures.category_create_enum);
46
+ await this.productsRepo.create({...ProductsFixtures.product_create_enum, category_id: categoryEnum.id});
47
+ await this.categoriesRepo.create(CategoriesFixtures.category_unique_test);
48
+ await this.runner.test('should create single record', async () => {
49
+ let created = await this.categoriesRepo.loadOne({slug: 'electronics'});
50
+ assert.ok(created);
51
+ assert.ok(created.id);
52
+ assert.strictEqual(created.name, 'Electronics');
53
+ });
54
+ await this.runner.test('should create record with JSON field', async () => {
55
+ let category = await this.categoriesRepo.loadOne({slug: 'books'});
56
+ let product = await this.productsRepo.loadOne({sku: 'LAPTOP-PRO-15'});
57
+ assert.ok(category);
58
+ assert.ok(product);
59
+ assert.ok(product.metadata);
60
+ });
61
+ await this.runner.test('should create record with ENUM field', async () => {
62
+ let category = await this.categoriesRepo.loadOne({slug: 'clothing'});
63
+ let product = await this.productsRepo.loadOne({sku: 'TSHIRT-CLASSIC'});
64
+ assert.ok(category);
65
+ assert.ok(product);
66
+ assert.strictEqual(product.status, 'published');
67
+ });
68
+ await this.runner.test('should enforce UNIQUE constraints', async () => {
69
+ let categoryData = CategoriesFixtures.category_unique_test;
70
+ try {
71
+ await this.categoriesRepo.create(categoryData);
72
+ assert.fail('Should have thrown duplicate key error');
73
+ } catch(error){
74
+ assert.ok(error);
75
+ }
76
+ });
77
+ await this.runner.test('should enforce FOREIGN KEY constraints', async () => {
78
+ let productData = ProductsFixtures.product_fk_test;
79
+ try {
80
+ await this.productsRepo.create(productData);
81
+ assert.fail('Should have thrown foreign key error');
82
+ } catch(error){
83
+ assert.ok(error);
84
+ }
85
+ });
86
+ }
87
+
88
+ async testUpdateOperations()
89
+ {
90
+ this.runner.group('UPDATE Operations');
91
+ await TestHelpers.cleanDatabase(this.dataServer);
92
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
93
+ CategoriesFixtures.category_update_by_id
94
+ ]);
95
+ await this.runner.test('should update record by ID', async () => {
96
+ let updated = await this.categoriesRepo.updateById(1100, {name: 'Updated Name'});
97
+ assert.ok(updated);
98
+ assert.strictEqual(updated.name, 'Updated Name');
99
+ });
100
+ await this.runner.test('should update record by filters', async () => {
101
+ let updated = await this.categoriesRepo.updateBy('id', 1100, {name: 'Updated via filters'});
102
+ assert.ok(updated);
103
+ });
104
+ await this.runner.test('should update record by single field', async () => {
105
+ let updated = await this.categoriesRepo.updateBy('id', 1100, {name: 'Updated via field'});
106
+ assert.ok(updated);
107
+ });
108
+ await this.runner.test('should upsert when record does not exist', async () => {
109
+ let newData = CategoriesFixtures.category_upsert_new;
110
+ let result = await this.categoriesRepo.upsert(newData, {id: newData.id});
111
+ assert.ok(result);
112
+ });
113
+ await this.runner.test('should upsert when record exists', async () => {
114
+ let result = await this.categoriesRepo.upsert({id: 1100}, {name: 'Upserted Name'});
115
+ assert.ok(result);
116
+ });
117
+ }
118
+
119
+ async testQueryOperations()
120
+ {
121
+ this.runner.group('QUERY Operations');
122
+ await TestHelpers.cleanDatabase(this.dataServer);
123
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
124
+ CategoriesFixtures.category_query_1,
125
+ CategoriesFixtures.category_query_2,
126
+ CategoriesFixtures.category_query_3
127
+ ]);
128
+ await this.runner.test('should load all records', async () => {
129
+ let all = await this.categoriesRepo.loadAll();
130
+ assert.strictEqual(all.length, 3);
131
+ });
132
+ await this.runner.test('should load records by filters', async () => {
133
+ let filtered = await this.categoriesRepo.load({name: 'Query Test 1'});
134
+ assert.strictEqual(filtered.length, 1);
135
+ assert.strictEqual(filtered[0].id, 1300);
136
+ });
137
+ await this.runner.test('should load records by single field', async () => {
138
+ let result = await this.categoriesRepo.loadBy('name', 'Query Test 2');
139
+ assert.strictEqual(result.length, 1);
140
+ assert.strictEqual(result[0].id, 1301);
141
+ });
142
+ await this.runner.test('should load record by ID', async () => {
143
+ let record = await this.categoriesRepo.loadById(1300);
144
+ assert.ok(record);
145
+ assert.strictEqual(record.id, 1300);
146
+ });
147
+ await this.runner.test('should load records by multiple IDs', async () => {
148
+ let records = await this.categoriesRepo.loadByIds([1300, 1301]);
149
+ assert.strictEqual(records.length, 2);
150
+ });
151
+ await this.runner.test('should load one record', async () => {
152
+ let record = await this.categoriesRepo.loadOne({name: 'Query Test 1'});
153
+ assert.ok(record);
154
+ assert.strictEqual(record.id, 1300);
155
+ });
156
+ await this.runner.test('should load one record by field', async () => {
157
+ let record = await this.categoriesRepo.loadOneBy('name', 'Query Test 3');
158
+ assert.ok(record);
159
+ assert.strictEqual(record.id, 1302);
160
+ });
161
+ await this.runner.test('should count records with filters', async () => {
162
+ let count = await this.categoriesRepo.count({name: 'Query Test 1'});
163
+ assert.strictEqual(count, 1);
164
+ });
165
+ await this.runner.test('should apply limit', async () => {
166
+ this.categoriesRepo.limit = 2;
167
+ let records = await this.categoriesRepo.load();
168
+ assert.strictEqual(records.length, 2);
169
+ this.categoriesRepo.limit = 0;
170
+ });
171
+ await this.runner.test('should apply offset', async () => {
172
+ this.categoriesRepo.offset = 1;
173
+ this.categoriesRepo.limit = 2;
174
+ let records = await this.categoriesRepo.load();
175
+ assert.strictEqual(records.length, 2);
176
+ this.categoriesRepo.offset = 0;
177
+ this.categoriesRepo.limit = 0;
178
+ });
179
+ await this.runner.test('should apply sortBy and sortDirection', async () => {
180
+ this.categoriesRepo.sortBy = 'name';
181
+ this.categoriesRepo.sortDirection = 'DESC';
182
+ let records = await this.categoriesRepo.load();
183
+ assert.strictEqual(records.length, 3);
184
+ assert.strictEqual(records[0].name, 'Query Test 3');
185
+ assert.strictEqual(records[1].name, 'Query Test 2');
186
+ assert.strictEqual(records[2].name, 'Query Test 1');
187
+ this.categoriesRepo.sortBy = false;
188
+ this.categoriesRepo.sortDirection = 'ASC';
189
+ });
190
+ }
191
+
192
+ async testDeleteOperations()
193
+ {
194
+ this.runner.group('DELETE Operations');
195
+ await TestHelpers.cleanDatabase(this.dataServer);
196
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
197
+ CategoriesFixtures.category_delete_by_id,
198
+ CategoriesFixtures.category_delete_by_filters
199
+ ]);
200
+ await this.runner.test('should delete record by ID', async () => {
201
+ let result = await this.categoriesRepo.deleteById(1200);
202
+ assert.ok(result);
203
+ let remaining = await this.categoriesRepo.loadAll();
204
+ assert.strictEqual(remaining.length, 1);
205
+ });
206
+ await this.runner.test('should delete records by filters', async () => {
207
+ let result = await this.categoriesRepo.delete({name: 'Art'});
208
+ assert.ok(result);
209
+ let remaining = await this.categoriesRepo.loadAll();
210
+ assert.strictEqual(remaining.length, 0);
211
+ });
212
+ }
213
+
214
+ async testReviewsCrud()
215
+ {
216
+ this.runner.group('REVIEWS CRUD Operations');
217
+ await TestHelpers.cleanDatabase(this.dataServer);
218
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
219
+ CategoriesFixtures.category_reviews_crud
220
+ ]);
221
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_products', [
222
+ ProductsFixtures.product_reviews_crud
223
+ ]);
224
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_reviews', [
225
+ ReviewsFixtures.review_crud_1
226
+ ]);
227
+ await this.runner.test('should create review record', async () => {
228
+ let created = await this.reviewsRepo.loadById(3400);
229
+ assert.ok(created);
230
+ assert.strictEqual(created.product_id, 2400);
231
+ });
232
+ await this.runner.test('should load review by ID', async () => {
233
+ let record = await this.reviewsRepo.loadById(3400);
234
+ assert.ok(record);
235
+ assert.strictEqual(record.id, 3400);
236
+ });
237
+ await this.runner.test('should update review by ID', async () => {
238
+ let updated = await this.reviewsRepo.updateById(3400, {rating: 5});
239
+ assert.ok(updated);
240
+ assert.strictEqual(updated.rating, 5);
241
+ });
242
+ await this.runner.test('should delete review by ID', async () => {
243
+ let result = await this.reviewsRepo.deleteById(3400);
244
+ assert.ok(result);
245
+ });
246
+ await this.runner.test('should load reviews by product_id', async () => {
247
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_reviews', [
248
+ ReviewsFixtures.review_crud_1
249
+ ]);
250
+ let reviews = await this.reviewsRepo.loadBy('product_id', 2400);
251
+ assert.strictEqual(reviews.length, 1);
252
+ });
253
+ }
254
+
255
+ }
256
+
257
+ module.exports = DriversTest;
@@ -0,0 +1,408 @@
1
+ /**
2
+ *
3
+ * Reldens - Nested Filters Integration Test
4
+ * Tests complex filter syntax across all three storage drivers with an actual database
5
+ *
6
+ */
7
+
8
+ const { TestRunner, assert } = require('../utils/test-runner');
9
+ const { TestHelpers } = require('../utils/test-helpers');
10
+ const { CategoriesFixtures } = require('../fixtures/categories-fixtures');
11
+ const { ProductsFixtures } = require('../fixtures/products-fixtures');
12
+
13
+ class NestedFiltersTest
14
+ {
15
+
16
+ constructor(dataServer, repos, driverName)
17
+ {
18
+ this.dataServer = dataServer;
19
+ this.categoriesRepo = repos.testCategories;
20
+ this.productsRepo = repos.testProducts;
21
+ this.driverName = driverName;
22
+ this.runner = new TestRunner();
23
+ }
24
+
25
+ async run()
26
+ {
27
+ this.runner.suite('Nested Filters - Driver: '+this.driverName);
28
+ await this.testAndOperator();
29
+ await this.testOrOperator();
30
+ await this.testNestedAndOr();
31
+ await this.testNotOperator();
32
+ await this.testInOperator();
33
+ await this.testLikeOperator();
34
+ await this.testComparisonOperators();
35
+ await this.testComplexNestedScenarios();
36
+ await this.testEdgeCases();
37
+ return this.runner.getResults();
38
+ }
39
+
40
+ async testAndOperator()
41
+ {
42
+ this.runner.group('AND Operator');
43
+ await TestHelpers.cleanDatabase(this.dataServer);
44
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
45
+ CategoriesFixtures.category_filters_1,
46
+ CategoriesFixtures.category_filters_2,
47
+ CategoriesFixtures.category_filters_3
48
+ ]);
49
+ await this.runner.test('should filter with simple AND condition', async () => {
50
+ let results = await this.categoriesRepo.load({
51
+ AND: [
52
+ {is_active: 1},
53
+ {display_order: 1}
54
+ ]
55
+ });
56
+ assert.ok(results);
57
+ assert.strictEqual(results.length, 1);
58
+ assert.strictEqual(results[0].display_order, 1);
59
+ assert.strictEqual(results[0].is_active, 1);
60
+ });
61
+ await this.runner.test('should filter with AND containing multiple conditions', async () => {
62
+ let results = await this.categoriesRepo.load({
63
+ AND: [
64
+ {is_active: 1},
65
+ {display_order: {operator: 'gte', value: 1}}
66
+ ]
67
+ });
68
+ assert.ok(results);
69
+ assert.strictEqual(results.length, 2);
70
+ });
71
+ await this.runner.test('should return empty when AND conditions do not match', async () => {
72
+ let results = await this.categoriesRepo.load({
73
+ AND: [
74
+ {is_active: 1},
75
+ {slug: 'nonexistent'}
76
+ ]
77
+ });
78
+ assert.ok(results);
79
+ assert.strictEqual(results.length, 0);
80
+ });
81
+ }
82
+
83
+ async testOrOperator()
84
+ {
85
+ this.runner.group('OR Operator');
86
+ await TestHelpers.cleanDatabase(this.dataServer);
87
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
88
+ CategoriesFixtures.category_filters_1,
89
+ CategoriesFixtures.category_filters_2,
90
+ CategoriesFixtures.category_filters_3
91
+ ]);
92
+ await this.runner.test('should filter with simple OR condition', async () => {
93
+ let results = await this.categoriesRepo.load({
94
+ OR: [
95
+ {slug: 'filters-test-1'},
96
+ {slug: 'filters-test-2'}
97
+ ]
98
+ });
99
+ assert.ok(results);
100
+ assert.strictEqual(results.length, 2);
101
+ });
102
+ await this.runner.test('should filter with OR containing operator conditions', async () => {
103
+ let results = await this.categoriesRepo.load({
104
+ OR: [
105
+ {display_order: {operator: 'lt', value: 2}},
106
+ {is_active: 0}
107
+ ]
108
+ });
109
+ assert.ok(results);
110
+ assert.strictEqual(results.length, 2);
111
+ });
112
+ await this.runner.test('should return all matching records with OR', async () => {
113
+ let results = await this.categoriesRepo.load({
114
+ OR: [
115
+ {is_active: 1},
116
+ {is_active: 0}
117
+ ]
118
+ });
119
+ assert.ok(results);
120
+ assert.strictEqual(results.length, 3);
121
+ });
122
+ }
123
+
124
+ async testNestedAndOr()
125
+ {
126
+ this.runner.group('Nested AND/OR');
127
+ await TestHelpers.cleanDatabase(this.dataServer);
128
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
129
+ CategoriesFixtures.category_filters_1,
130
+ CategoriesFixtures.category_filters_2,
131
+ CategoriesFixtures.category_filters_3
132
+ ]);
133
+ await this.runner.test('should filter with OR inside AND', async () => {
134
+ let results = await this.categoriesRepo.load({
135
+ AND: [
136
+ {is_active: 1},
137
+ {
138
+ OR: [
139
+ {display_order: 1},
140
+ {display_order: 2}
141
+ ]
142
+ }
143
+ ]
144
+ });
145
+ assert.ok(results);
146
+ assert.strictEqual(results.length, 2);
147
+ });
148
+ await this.runner.test('should filter with AND inside OR', async () => {
149
+ let results = await this.categoriesRepo.load({
150
+ OR: [
151
+ {
152
+ AND: [
153
+ {is_active: 1},
154
+ {display_order: 1}
155
+ ]
156
+ },
157
+ {is_active: 0}
158
+ ]
159
+ });
160
+ assert.ok(results);
161
+ assert.strictEqual(results.length, 2);
162
+ });
163
+ await this.runner.test('should handle deep nesting of AND/OR', async () => {
164
+ let results = await this.categoriesRepo.load({
165
+ AND: [
166
+ {
167
+ OR: [
168
+ {display_order: 1},
169
+ {display_order: 2}
170
+ ]
171
+ },
172
+ {
173
+ OR: [
174
+ {is_active: 1},
175
+ {is_active: 0}
176
+ ]
177
+ }
178
+ ]
179
+ });
180
+ assert.ok(results);
181
+ assert.strictEqual(results.length, 2);
182
+ });
183
+ }
184
+
185
+ async testNotOperator()
186
+ {
187
+ this.runner.group('NOT Operator');
188
+ await TestHelpers.cleanDatabase(this.dataServer);
189
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
190
+ CategoriesFixtures.category_filters_1,
191
+ CategoriesFixtures.category_filters_2,
192
+ CategoriesFixtures.category_filters_3
193
+ ]);
194
+ await this.runner.test('should filter with NOT operator', async () => {
195
+ let results = await this.categoriesRepo.load({
196
+ is_active: {operator: 'not', value: 0}
197
+ });
198
+ assert.ok(results);
199
+ assert.strictEqual(results.length, 2);
200
+ for(let category of results){
201
+ assert.strictEqual(category.is_active, 1);
202
+ }
203
+ });
204
+ await this.runner.test('should filter with NOT inside AND', async () => {
205
+ let results = await this.categoriesRepo.load({
206
+ AND: [
207
+ {is_active: {operator: 'not', value: 0}},
208
+ {display_order: 1}
209
+ ]
210
+ });
211
+ assert.ok(results);
212
+ assert.strictEqual(results.length, 1);
213
+ });
214
+ }
215
+
216
+ async testInOperator()
217
+ {
218
+ this.runner.group('IN Operator');
219
+ await TestHelpers.cleanDatabase(this.dataServer);
220
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
221
+ CategoriesFixtures.category_filters_1,
222
+ CategoriesFixtures.category_filters_2,
223
+ CategoriesFixtures.category_filters_3
224
+ ]);
225
+ await this.runner.test('should filter with IN operator', async () => {
226
+ let results = await this.categoriesRepo.load({
227
+ display_order: {operator: 'in', value: [1, 2]}
228
+ });
229
+ assert.ok(results);
230
+ assert.strictEqual(results.length, 2);
231
+ });
232
+ await this.runner.test('should filter with IN operator on string field', async () => {
233
+ let results = await this.categoriesRepo.load({
234
+ slug: {operator: 'in', value: ['filters-test-1', 'filters-test-2']}
235
+ });
236
+ assert.ok(results);
237
+ assert.strictEqual(results.length, 2);
238
+ });
239
+ await this.runner.test('should return empty with IN operator when no match', async () => {
240
+ let results = await this.categoriesRepo.load({
241
+ display_order: {operator: 'in', value: [99, 100]}
242
+ });
243
+ assert.ok(results);
244
+ assert.strictEqual(results.length, 0);
245
+ });
246
+ }
247
+
248
+ async testLikeOperator()
249
+ {
250
+ this.runner.group('LIKE Operator');
251
+ await TestHelpers.cleanDatabase(this.dataServer);
252
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
253
+ CategoriesFixtures.category_filters_1,
254
+ CategoriesFixtures.category_filters_2
255
+ ]);
256
+ await this.runner.test('should filter with LIKE operator', async () => {
257
+ let results = await this.categoriesRepo.load({
258
+ name: {operator: 'like', value: '%Filters%'}
259
+ });
260
+ assert.ok(results);
261
+ assert.ok(results.length > 0);
262
+ assert.ok(results[0].name.includes('Filters'));
263
+ });
264
+ await this.runner.test('should filter with LIKE and wildcards', async () => {
265
+ let results = await this.categoriesRepo.load({
266
+ slug: {operator: 'like', value: '%filters%'}
267
+ });
268
+ assert.ok(results);
269
+ assert.ok(results.length > 0);
270
+ });
271
+ await this.runner.test('should work with LIKE inside AND', async () => {
272
+ let results = await this.categoriesRepo.load({
273
+ AND: [
274
+ {is_active: 1},
275
+ {name: {operator: 'like', value: '%Filters%'}}
276
+ ]
277
+ });
278
+ assert.ok(results);
279
+ assert.ok(results.length > 0);
280
+ });
281
+ }
282
+
283
+ async testComparisonOperators()
284
+ {
285
+ this.runner.group('Comparison Operators');
286
+ await TestHelpers.cleanDatabase(this.dataServer);
287
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
288
+ CategoriesFixtures.category_filters_1,
289
+ CategoriesFixtures.category_filters_2,
290
+ CategoriesFixtures.category_filters_3
291
+ ]);
292
+ await this.runner.test('should filter with gt operator', async () => {
293
+ let results = await this.categoriesRepo.load({
294
+ display_order: {operator: 'gt', value: 1}
295
+ });
296
+ assert.ok(results);
297
+ assert.strictEqual(results.length, 2);
298
+ });
299
+ await this.runner.test('should filter with gte operator', async () => {
300
+ let results = await this.categoriesRepo.load({
301
+ display_order: {operator: 'gte', value: 2}
302
+ });
303
+ assert.ok(results);
304
+ assert.strictEqual(results.length, 2);
305
+ });
306
+ await this.runner.test('should filter with lt operator', async () => {
307
+ let results = await this.categoriesRepo.load({
308
+ display_order: {operator: 'lt', value: 3}
309
+ });
310
+ assert.ok(results);
311
+ assert.strictEqual(results.length, 2);
312
+ });
313
+ await this.runner.test('should filter with lte operator', async () => {
314
+ let results = await this.categoriesRepo.load({
315
+ display_order: {operator: 'lte', value: 2}
316
+ });
317
+ assert.ok(results);
318
+ assert.strictEqual(results.length, 2);
319
+ });
320
+ }
321
+
322
+ async testComplexNestedScenarios()
323
+ {
324
+ this.runner.group('Complex Nested Scenarios');
325
+ await TestHelpers.cleanDatabase(this.dataServer);
326
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
327
+ CategoriesFixtures.category_filters_1
328
+ ]);
329
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_products', [
330
+ ProductsFixtures.product_filters_1,
331
+ ProductsFixtures.product_filters_2
332
+ ]);
333
+ await this.runner.test('should handle complex filter with multiple operators', async () => {
334
+ let results = await this.productsRepo.load({
335
+ AND: [
336
+ {status: 'published'},
337
+ {
338
+ OR: [
339
+ {price: {operator: 'lt', value: 1000}},
340
+ {stock_quantity: {operator: 'gte', value: 50}}
341
+ ]
342
+ }
343
+ ]
344
+ });
345
+ assert.ok(results);
346
+ assert.ok(results.length > 0);
347
+ });
348
+ await this.runner.test('should handle filter with IN inside OR', async () => {
349
+ let results = await this.productsRepo.load({
350
+ OR: [
351
+ {status: {operator: 'in', value: ['published', 'draft']}},
352
+ {stock_quantity: {operator: 'gt', value: 0}}
353
+ ]
354
+ });
355
+ assert.ok(results);
356
+ assert.strictEqual(results.length, 2);
357
+ });
358
+ await this.runner.test('should handle nested AND/OR with LIKE and comparison operators', async () => {
359
+ let results = await this.productsRepo.load({
360
+ AND: [
361
+ {
362
+ OR: [
363
+ {name: {operator: 'like', value: '%Phone%'}},
364
+ {name: {operator: 'like', value: '%Product%'}}
365
+ ]
366
+ },
367
+ {price: {operator: 'gte', value: 0}}
368
+ ]
369
+ });
370
+ assert.ok(results);
371
+ assert.ok(results.length > 0);
372
+ });
373
+ }
374
+
375
+ async testEdgeCases()
376
+ {
377
+ this.runner.group('Edge Cases');
378
+ await TestHelpers.cleanDatabase(this.dataServer);
379
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
380
+ CategoriesFixtures.category_filters_1
381
+ ]);
382
+ await this.runner.test('should handle empty AND array', async () => {
383
+ let results = await this.categoriesRepo.load({AND: []});
384
+ assert.ok(results);
385
+ });
386
+ await this.runner.test('should handle empty OR array', async () => {
387
+ let results = await this.categoriesRepo.load({OR: []});
388
+ assert.ok(results);
389
+ });
390
+ await this.runner.test('should handle single condition in AND', async () => {
391
+ let results = await this.categoriesRepo.load({
392
+ AND: [{is_active: 1}]
393
+ });
394
+ assert.ok(results);
395
+ assert.strictEqual(results.length, 1);
396
+ });
397
+ await this.runner.test('should handle single condition in OR', async () => {
398
+ let results = await this.categoriesRepo.load({
399
+ OR: [{is_active: 1}]
400
+ });
401
+ assert.ok(results);
402
+ assert.strictEqual(results.length, 1);
403
+ });
404
+ }
405
+
406
+ }
407
+
408
+ module.exports = NestedFiltersTest;