@6thbridge/utils 1.5.5 → 1.6.1
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/lib/database/MongoDBRepository.d.ts +1 -1
- package/lib/database/MongoDBRepository.js +1 -4
- package/lib/database/MongoDBRepository.js.map +1 -1
- package/lib/database/MongoDBTypeRepository.js +1 -5
- package/lib/database/MongoDBTypeRepository.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utils/database.d.ts +2 -0
- package/lib/utils/database.js +11 -1
- package/lib/utils/database.js.map +1 -1
- package/lib/utils/database.spec.js +214 -221
- package/lib/utils/database.spec.js.map +1 -1
- package/package.json +1 -1
package/lib/utils/database.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare class ObjectToMongoDBQueryOption {
|
|
|
8
8
|
dateFormat?: string | string[];
|
|
9
9
|
searchFields?: string[];
|
|
10
10
|
enableParentClientSearch?: boolean;
|
|
11
|
+
populate?: string[];
|
|
11
12
|
}
|
|
12
13
|
export declare class ObjectToMongoDBQueryResult {
|
|
13
14
|
page?: number;
|
|
@@ -15,6 +16,7 @@ export declare class ObjectToMongoDBQueryResult {
|
|
|
15
16
|
expand?: string[];
|
|
16
17
|
filter: any;
|
|
17
18
|
sort: any;
|
|
19
|
+
populate?: Record<string, number>;
|
|
18
20
|
}
|
|
19
21
|
export declare class DatabaseUtil {
|
|
20
22
|
static resolveObjectToMongoDBQuery(requestQuery: Record<string, any>, options?: ObjectToMongoDBQueryOption): ObjectToMongoDBQueryResult;
|
package/lib/utils/database.js
CHANGED
|
@@ -12,6 +12,7 @@ const DEFAULT_QUERY_OPTIONS = {
|
|
|
12
12
|
dateField: 'createdAt',
|
|
13
13
|
dateFormat: ['X', 'YYYY-MM-DD', 'YYYY-MM-DDTHH:mm:ss.SSSZ', 'DD-MM-YYYY'],
|
|
14
14
|
enableParentClientSearch: false,
|
|
15
|
+
populate: [],
|
|
15
16
|
};
|
|
16
17
|
/**
|
|
17
18
|
* @description resolveRequestQueryToMongoDBQuery
|
|
@@ -23,6 +24,7 @@ class ObjectToMongoDBQueryOption {
|
|
|
23
24
|
this.dateField = 'createdAt';
|
|
24
25
|
this.dateFormat = ['X', "YYYY-MM-DD", "YYYY-MM-DDTHH:mm:ss.SSSZ", "DD-MM-YYYY"];
|
|
25
26
|
this.enableParentClientSearch = false;
|
|
27
|
+
this.populate = [];
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
exports.ObjectToMongoDBQueryOption = ObjectToMongoDBQueryOption;
|
|
@@ -32,6 +34,7 @@ class ObjectToMongoDBQueryResult {
|
|
|
32
34
|
this.limit = 50;
|
|
33
35
|
this.filter = {};
|
|
34
36
|
this.sort = { _id: -1 };
|
|
37
|
+
this.populate = {};
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
exports.ObjectToMongoDBQueryResult = ObjectToMongoDBQueryResult;
|
|
@@ -56,7 +59,7 @@ const addOrFilterGroup = (filter, conditions) => {
|
|
|
56
59
|
};
|
|
57
60
|
class DatabaseUtil {
|
|
58
61
|
static resolveObjectToMongoDBQuery(requestQuery, options) {
|
|
59
|
-
var _a, _b;
|
|
62
|
+
var _a, _b, _c;
|
|
60
63
|
const resolvedOptions = Object.assign(Object.assign({}, DEFAULT_QUERY_OPTIONS), options);
|
|
61
64
|
const response = {
|
|
62
65
|
page: 1,
|
|
@@ -64,6 +67,7 @@ class DatabaseUtil {
|
|
|
64
67
|
filter: {},
|
|
65
68
|
expand: [],
|
|
66
69
|
sort: { _id: -1 },
|
|
70
|
+
populate: undefined,
|
|
67
71
|
};
|
|
68
72
|
for (const key of Object.keys(requestQuery)) {
|
|
69
73
|
if (key === 'page') {
|
|
@@ -113,6 +117,12 @@ class DatabaseUtil {
|
|
|
113
117
|
}
|
|
114
118
|
response.filter[key] = requestQuery[key];
|
|
115
119
|
}
|
|
120
|
+
if ((_c = resolvedOptions.populate) === null || _c === void 0 ? void 0 : _c.length) {
|
|
121
|
+
response.populate = {};
|
|
122
|
+
for (const populate of resolvedOptions.populate) {
|
|
123
|
+
response.populate[populate] = 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
116
126
|
return response;
|
|
117
127
|
}
|
|
118
128
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/utils/database.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAC1B,uFAA+D;AAC/D,
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/utils/database.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,277 +3,270 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const database_1 = require("./database");
|
|
7
6
|
const dayjs_1 = __importDefault(require("dayjs"));
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
it('should return default values when empty request query is provided', () => {
|
|
25
|
-
const requestQuery = {};
|
|
26
|
-
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery(requestQuery);
|
|
27
|
-
expect(result).toEqual({
|
|
7
|
+
const customParseFormat_1 = __importDefault(require("dayjs/plugin/customParseFormat"));
|
|
8
|
+
const database_1 = require("./database");
|
|
9
|
+
dayjs_1.default.extend(customParseFormat_1.default);
|
|
10
|
+
const expectRegex = (value, source, flags = 'gi') => {
|
|
11
|
+
expect(value).toBeInstanceOf(RegExp);
|
|
12
|
+
expect(value.source).toBe(source);
|
|
13
|
+
expect(value.flags).toBe(flags);
|
|
14
|
+
};
|
|
15
|
+
const expectDayBounds = (value, expected, format = 'DD-MM-YYYY', bound = 'startOf') => {
|
|
16
|
+
const expectedDate = (0, dayjs_1.default)(expected, format)[bound]('day').toDate();
|
|
17
|
+
expect(value).toEqual(expectedDate);
|
|
18
|
+
};
|
|
19
|
+
describe('DatabaseUtil.resolveObjectToMongoDBQuery', () => {
|
|
20
|
+
describe('base query controls', () => {
|
|
21
|
+
it('returns the default query shape for an empty request query', () => {
|
|
22
|
+
expect(database_1.DatabaseUtil.resolveObjectToMongoDBQuery({})).toEqual({
|
|
28
23
|
page: 1,
|
|
29
24
|
limit: 50,
|
|
30
25
|
filter: {},
|
|
31
26
|
expand: [],
|
|
32
|
-
sort: { _id: -1 }
|
|
27
|
+
sort: { _id: -1 },
|
|
28
|
+
populate: undefined,
|
|
33
29
|
});
|
|
34
30
|
});
|
|
35
|
-
it('
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
expect(result.page).toBe(NaN);
|
|
50
|
-
expect(result.limit).toBe(NaN);
|
|
51
|
-
});
|
|
52
|
-
it('should parse sort parameters correctly with provided sortValue', () => {
|
|
53
|
-
const requestQuery = { sort: 'name,1' };
|
|
54
|
-
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery(requestQuery);
|
|
55
|
-
expect(result.sort).toEqual({ name: '1' });
|
|
31
|
+
it('parses page, limit, sort, and expand controls without adding them to the filter', () => {
|
|
32
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
33
|
+
page: '2',
|
|
34
|
+
limit: '25',
|
|
35
|
+
sort: 'createdAt,1',
|
|
36
|
+
expand: 'author,comments',
|
|
37
|
+
});
|
|
38
|
+
expect(result).toMatchObject({
|
|
39
|
+
page: 2,
|
|
40
|
+
limit: 25,
|
|
41
|
+
sort: { createdAt: '1' },
|
|
42
|
+
expand: ['author', 'comments'],
|
|
43
|
+
filter: {},
|
|
44
|
+
});
|
|
56
45
|
});
|
|
57
|
-
it('
|
|
58
|
-
const
|
|
59
|
-
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery(requestQuery);
|
|
46
|
+
it('uses descending sort when only a sort field is provided', () => {
|
|
47
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({ sort: 'name' });
|
|
60
48
|
expect(result.sort).toEqual({ name: -1 });
|
|
61
49
|
});
|
|
62
|
-
it('
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
50
|
+
it('keeps parseInt results for invalid numeric controls', () => {
|
|
51
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
52
|
+
page: 'not-a-page',
|
|
53
|
+
limit: 'not-a-limit',
|
|
54
|
+
});
|
|
55
|
+
expect(result.page).toBeNaN();
|
|
56
|
+
expect(result.limit).toBeNaN();
|
|
66
57
|
});
|
|
67
|
-
it('
|
|
68
|
-
const
|
|
69
|
-
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery(requestQuery);
|
|
58
|
+
it('splits an empty expand value using native string split behavior', () => {
|
|
59
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({ expand: '' });
|
|
70
60
|
expect(result.expand).toEqual(['']);
|
|
71
61
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
62
|
+
});
|
|
63
|
+
describe('date filters', () => {
|
|
64
|
+
it('adds dateFrom as an inclusive start-of-day lower bound on createdAt by default', () => {
|
|
65
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({ dateFrom: '01-01-2023' });
|
|
66
|
+
expectDayBounds(result.filter.createdAt.$gte, '01-01-2023');
|
|
67
|
+
expect(result.filter.createdAt.$lte).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
it('adds dateTo as an inclusive end-of-day upper bound on createdAt by default', () => {
|
|
70
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({ dateTo: '31-01-2023' });
|
|
71
|
+
expect(result.filter.createdAt.$gte).toBeUndefined();
|
|
72
|
+
expectDayBounds(result.filter.createdAt.$lte, '31-01-2023', 'DD-MM-YYYY', 'endOf');
|
|
76
73
|
});
|
|
77
|
-
it('
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
it('combines dateFrom and dateTo on the same date field', () => {
|
|
75
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
76
|
+
dateFrom: '01-01-2023',
|
|
77
|
+
dateTo: '31-01-2023',
|
|
78
|
+
});
|
|
79
|
+
expectDayBounds(result.filter.createdAt.$gte, '01-01-2023');
|
|
80
|
+
expectDayBounds(result.filter.createdAt.$lte, '31-01-2023', 'DD-MM-YYYY', 'endOf');
|
|
81
81
|
});
|
|
82
|
-
it('
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
it('uses a custom date field when provided', () => {
|
|
83
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
84
|
+
dateFrom: '01-01-2023',
|
|
85
|
+
dateTo: '31-01-2023',
|
|
86
|
+
}, { dateField: 'updatedAt' });
|
|
87
|
+
expect(result.filter.createdAt).toBeUndefined();
|
|
88
|
+
expectDayBounds(result.filter.updatedAt.$gte, '01-01-2023');
|
|
89
|
+
expectDayBounds(result.filter.updatedAt.$lte, '31-01-2023', 'DD-MM-YYYY', 'endOf');
|
|
87
90
|
});
|
|
88
|
-
it('
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
it('uses a custom date format when provided', () => {
|
|
92
|
+
const options = { dateFormat: 'YYYY/MM/DD' };
|
|
93
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
94
|
+
dateFrom: '2023/01/01',
|
|
95
|
+
dateTo: '2023/01/31',
|
|
96
|
+
}, options);
|
|
97
|
+
expectDayBounds(result.filter.createdAt.$gte, '2023/01/01', options.dateFormat);
|
|
98
|
+
expectDayBounds(result.filter.createdAt.$lte, '2023/01/31', options.dateFormat, 'endOf');
|
|
94
99
|
});
|
|
95
|
-
it(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
it.each([
|
|
101
|
+
['unix timestamp seconds', '1672531200'],
|
|
102
|
+
['ISO date', '2023-01-01'],
|
|
103
|
+
['ISO timestamp with offset', '2023-01-01T13:30:00.000+00:00'],
|
|
104
|
+
['DD-MM-YYYY date', '01-01-2023'],
|
|
105
|
+
])('parses the default %s date format', (_label, dateFrom) => {
|
|
106
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({ dateFrom });
|
|
107
|
+
expect(result.filter.createdAt.$gte).toBeInstanceOf(Date);
|
|
108
|
+
expect(result.filter.createdAt.$gte.toString()).not.toBe('Invalid Date');
|
|
99
109
|
});
|
|
100
|
-
it('
|
|
101
|
-
const
|
|
102
|
-
|
|
110
|
+
it('ignores empty date boundaries', () => {
|
|
111
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
112
|
+
dateFrom: '',
|
|
113
|
+
dateTo: '',
|
|
114
|
+
});
|
|
103
115
|
expect(result.filter.createdAt).toBeUndefined();
|
|
104
116
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const sanitizedQuery = 'testquery';
|
|
117
|
+
});
|
|
118
|
+
describe('search filters', () => {
|
|
119
|
+
it('creates a text search filter when no search fields are configured', () => {
|
|
120
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({ q: 'test-query' });
|
|
110
121
|
expect(result.filter.$text).toEqual({
|
|
111
122
|
$search: expect.any(RegExp),
|
|
112
|
-
$caseSensitive: false
|
|
123
|
+
$caseSensitive: false,
|
|
113
124
|
});
|
|
114
|
-
|
|
115
|
-
const regex = result.filter.$text.$search;
|
|
116
|
-
expect(regex.source).toBe(sanitizedQuery);
|
|
117
|
-
expect(regex.flags).toBe('gi');
|
|
125
|
+
expectRegex(result.filter.$text.$search, 'testquery');
|
|
118
126
|
});
|
|
119
|
-
it('
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
it('sanitizes special characters from text search input', () => {
|
|
128
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
129
|
+
q: '+test-query with $pecial ch@racters!',
|
|
130
|
+
});
|
|
131
|
+
expectRegex(result.filter.$text.$search, 'testquery with pecial chracters');
|
|
124
132
|
});
|
|
125
|
-
it('
|
|
126
|
-
const
|
|
127
|
-
const options = {
|
|
128
|
-
searchFields: ['name', 'description', 'tags']
|
|
129
|
-
};
|
|
130
|
-
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery(requestQuery, options);
|
|
131
|
-
// Should sanitize the query
|
|
132
|
-
const sanitizedQuery = 'testquery';
|
|
133
|
+
it('creates a field search $or group when search fields are configured', () => {
|
|
134
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({ q: 'Harry Potter' }, { searchFields: ['title', 'description', 'tags'] });
|
|
133
135
|
expect(result.filter.$or).toEqual([
|
|
134
|
-
{
|
|
136
|
+
{ title: expect.any(RegExp) },
|
|
135
137
|
{ description: expect.any(RegExp) },
|
|
136
|
-
{ tags: expect.any(RegExp) }
|
|
138
|
+
{ tags: expect.any(RegExp) },
|
|
137
139
|
]);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const field = Object.keys(item)[0];
|
|
141
|
-
const regex = item[field];
|
|
142
|
-
expect(regex.source).toBe(sanitizedQuery);
|
|
143
|
-
expect(regex.flags).toBe('gi');
|
|
140
|
+
result.filter.$or.forEach((condition) => {
|
|
141
|
+
expectRegex(condition[Object.keys(condition)[0]], 'Harry Potter');
|
|
144
142
|
});
|
|
143
|
+
expect(result.filter.$text).toBeUndefined();
|
|
145
144
|
});
|
|
146
|
-
it('
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
searchFields: ['name']
|
|
151
|
-
};
|
|
152
|
-
// First add an $or condition
|
|
153
|
-
const initialResult = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({ $or: existingOr }, options);
|
|
154
|
-
// Then process the search query
|
|
155
|
-
const finalResult = database_1.DatabaseUtil.resolveObjectToMongoDBQuery(requestQuery, options);
|
|
156
|
-
expect(finalResult.filter.$or.length).toBe(1);
|
|
157
|
-
});
|
|
158
|
-
it('should add other query parameters to filter directly', () => {
|
|
159
|
-
const requestQuery = { status: 'active', category: 'books', isPublished: true };
|
|
160
|
-
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery(requestQuery);
|
|
161
|
-
expect(result.filter.status).toBe('active');
|
|
162
|
-
expect(result.filter.category).toBe('books');
|
|
163
|
-
expect(result.filter.isPublished).toBe(true);
|
|
145
|
+
it('does not add an $or group for search when searchFields is empty', () => {
|
|
146
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({ q: 'Harry Potter' }, { searchFields: [] });
|
|
147
|
+
expect(result.filter.$or).toBeUndefined();
|
|
148
|
+
expectRegex(result.filter.$text.$search, 'Harry Potter');
|
|
164
149
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
expand: 'author,comments',
|
|
171
|
-
dateFrom: '01-01-2023',
|
|
172
|
-
dateTo: '31-01-2023',
|
|
173
|
-
q: 'Harry Potter',
|
|
174
|
-
status: 'published',
|
|
175
|
-
category: 'fiction'
|
|
176
|
-
};
|
|
177
|
-
const options = {
|
|
178
|
-
searchFields: ['title', 'description']
|
|
179
|
-
};
|
|
180
|
-
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery(requestQuery, options);
|
|
181
|
-
expect(result.page).toBe(2);
|
|
182
|
-
expect(result.limit).toBe(25);
|
|
183
|
-
expect(result.sort).toEqual({ createdAt: '-1' });
|
|
184
|
-
expect(result.expand).toEqual(['author', 'comments']);
|
|
185
|
-
expect(result.filter.createdAt.$gte).toEqual((0, dayjs_1.default)(requestQuery.dateFrom, 'DD-MM-YYYY').startOf('day').toDate());
|
|
186
|
-
expect(result.filter.createdAt.$lte).toEqual((0, dayjs_1.default)(requestQuery.dateTo, 'DD-MM-YYYY').endOf('day').toDate());
|
|
187
|
-
expect(result.filter.$or).toHaveLength(2);
|
|
188
|
-
expect(result.filter.status).toBe('published');
|
|
189
|
-
expect(result.filter.category).toBe('fiction');
|
|
150
|
+
});
|
|
151
|
+
describe('parent client search', () => {
|
|
152
|
+
it('passes clientId through as a direct filter when parent client search is disabled', () => {
|
|
153
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({ clientId: 'client-1' });
|
|
154
|
+
expect(result.filter).toEqual({ clientId: 'client-1' });
|
|
190
155
|
});
|
|
191
|
-
it('
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
};
|
|
197
|
-
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery(requestQuery);
|
|
198
|
-
expect(result.filter.status).toBe(null);
|
|
199
|
-
expect(result.filter.category).toBe(undefined);
|
|
200
|
-
expect(result.filter.tags).toEqual([]);
|
|
156
|
+
it('expands clientId into clientId or parentClientId when enabled', () => {
|
|
157
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({ clientId: 'client-1' }, { enableParentClientSearch: true });
|
|
158
|
+
expect(result.filter).toEqual({
|
|
159
|
+
$or: [{ clientId: 'client-1' }, { parentClientId: 'client-1' }],
|
|
160
|
+
});
|
|
201
161
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
it('should enable search by clientId', () => {
|
|
205
|
-
const requestQuery = {
|
|
206
|
-
page: '2',
|
|
207
|
-
limit: '25',
|
|
208
|
-
sort: 'createdAt,-1',
|
|
209
|
-
expand: 'author,comments',
|
|
210
|
-
dateFrom: '01-01-2023',
|
|
211
|
-
dateTo: '31-01-2023',
|
|
162
|
+
it('combines parent client search and field search with $and when q is processed first', () => {
|
|
163
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
212
164
|
q: 'Harry Potter',
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
165
|
+
clientId: 'client-1',
|
|
166
|
+
}, {
|
|
167
|
+
enableParentClientSearch: true,
|
|
168
|
+
searchFields: ['title', 'description'],
|
|
169
|
+
});
|
|
170
|
+
expect(result.filter.$or).toBeUndefined();
|
|
171
|
+
expect(result.filter.$and).toEqual([
|
|
172
|
+
{
|
|
173
|
+
$or: [{ title: expect.any(RegExp) }, { description: expect.any(RegExp) }],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
$or: [{ clientId: 'client-1' }, { parentClientId: 'client-1' }],
|
|
177
|
+
},
|
|
178
|
+
]);
|
|
225
179
|
});
|
|
226
|
-
it('
|
|
227
|
-
const
|
|
180
|
+
it('combines parent client search and field search with $and when clientId is processed first', () => {
|
|
181
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
182
|
+
clientId: 'client-1',
|
|
228
183
|
q: 'Harry Potter',
|
|
229
|
-
|
|
230
|
-
};
|
|
231
|
-
const options = {
|
|
184
|
+
}, {
|
|
232
185
|
enableParentClientSearch: true,
|
|
233
|
-
searchFields: ['title', 'description']
|
|
234
|
-
};
|
|
235
|
-
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery(requestQuery, options);
|
|
186
|
+
searchFields: ['title', 'description'],
|
|
187
|
+
});
|
|
236
188
|
expect(result.filter.$or).toBeUndefined();
|
|
237
189
|
expect(result.filter.$and).toEqual([
|
|
238
190
|
{
|
|
239
|
-
$or: [
|
|
240
|
-
{ title: expect.any(RegExp) },
|
|
241
|
-
{ description: expect.any(RegExp) }
|
|
242
|
-
]
|
|
191
|
+
$or: [{ clientId: 'client-1' }, { parentClientId: 'client-1' }],
|
|
243
192
|
},
|
|
244
193
|
{
|
|
245
|
-
$or: [
|
|
246
|
-
|
|
247
|
-
{ parentClientId: '1234567890' }
|
|
248
|
-
]
|
|
249
|
-
}
|
|
194
|
+
$or: [{ title: expect.any(RegExp) }, { description: expect.any(RegExp) }],
|
|
195
|
+
},
|
|
250
196
|
]);
|
|
251
197
|
});
|
|
252
|
-
it('
|
|
253
|
-
const
|
|
254
|
-
clientId: '
|
|
255
|
-
q: 'Harry Potter'
|
|
256
|
-
|
|
257
|
-
|
|
198
|
+
it('appends additional $or groups to an existing $and group', () => {
|
|
199
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
200
|
+
clientId: 'client-1',
|
|
201
|
+
q: 'Harry Potter',
|
|
202
|
+
accountId: 'account-1',
|
|
203
|
+
}, {
|
|
258
204
|
enableParentClientSearch: true,
|
|
259
|
-
searchFields: ['title',
|
|
260
|
-
};
|
|
261
|
-
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery(requestQuery, options);
|
|
262
|
-
expect(result.filter.$or).toBeUndefined();
|
|
205
|
+
searchFields: ['title'],
|
|
206
|
+
});
|
|
263
207
|
expect(result.filter.$and).toEqual([
|
|
264
208
|
{
|
|
265
|
-
$or: [
|
|
266
|
-
{ clientId: '1234567890' },
|
|
267
|
-
{ parentClientId: '1234567890' }
|
|
268
|
-
]
|
|
209
|
+
$or: [{ clientId: 'client-1' }, { parentClientId: 'client-1' }],
|
|
269
210
|
},
|
|
270
211
|
{
|
|
271
|
-
$or: [
|
|
272
|
-
|
|
273
|
-
{ description: expect.any(RegExp) }
|
|
274
|
-
]
|
|
275
|
-
}
|
|
212
|
+
$or: [{ title: expect.any(RegExp) }],
|
|
213
|
+
},
|
|
276
214
|
]);
|
|
215
|
+
expect(result.filter.accountId).toBe('account-1');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
describe('populate and direct filters', () => {
|
|
219
|
+
it('maps configured populate fields to include flags', () => {
|
|
220
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({}, { populate: ['client', 'wallet'] });
|
|
221
|
+
expect(result.populate).toEqual({
|
|
222
|
+
client: 1,
|
|
223
|
+
wallet: 1,
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
it('passes unreserved query parameters through to the filter unchanged', () => {
|
|
227
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
228
|
+
status: 'active',
|
|
229
|
+
category: undefined,
|
|
230
|
+
isPublished: true,
|
|
231
|
+
metadata: { source: 'api' },
|
|
232
|
+
tags: [],
|
|
233
|
+
});
|
|
234
|
+
expect(result.filter).toEqual({
|
|
235
|
+
status: 'active',
|
|
236
|
+
category: undefined,
|
|
237
|
+
isPublished: true,
|
|
238
|
+
metadata: { source: 'api' },
|
|
239
|
+
tags: [],
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
it('resolves a mixed request query', () => {
|
|
243
|
+
const result = database_1.DatabaseUtil.resolveObjectToMongoDBQuery({
|
|
244
|
+
page: '2',
|
|
245
|
+
limit: '25',
|
|
246
|
+
sort: 'createdAt,-1',
|
|
247
|
+
expand: 'author,comments',
|
|
248
|
+
dateFrom: '01-01-2023',
|
|
249
|
+
dateTo: '31-01-2023',
|
|
250
|
+
q: 'Harry Potter',
|
|
251
|
+
status: 'published',
|
|
252
|
+
category: 'fiction',
|
|
253
|
+
}, {
|
|
254
|
+
searchFields: ['title', 'description'],
|
|
255
|
+
populate: ['author'],
|
|
256
|
+
});
|
|
257
|
+
expect(result.page).toBe(2);
|
|
258
|
+
expect(result.limit).toBe(25);
|
|
259
|
+
expect(result.sort).toEqual({ createdAt: '-1' });
|
|
260
|
+
expect(result.expand).toEqual(['author', 'comments']);
|
|
261
|
+
expectDayBounds(result.filter.createdAt.$gte, '01-01-2023');
|
|
262
|
+
expectDayBounds(result.filter.createdAt.$lte, '31-01-2023', 'DD-MM-YYYY', 'endOf');
|
|
263
|
+
expect(result.filter.$or).toEqual([
|
|
264
|
+
{ title: expect.any(RegExp) },
|
|
265
|
+
{ description: expect.any(RegExp) },
|
|
266
|
+
]);
|
|
267
|
+
expect(result.filter.status).toBe('published');
|
|
268
|
+
expect(result.filter.category).toBe('fiction');
|
|
269
|
+
expect(result.populate).toEqual({ author: 1 });
|
|
277
270
|
});
|
|
278
271
|
});
|
|
279
272
|
});
|