-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
filterQuery.test-d.ts
233 lines (210 loc) · 10.8 KB
/
filterQuery.test-d.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
import { BSONRegExp, Decimal128, ObjectId } from 'bson';
import { expectAssignable, expectNotType, expectType } from 'tsd';
import { Filter, MongoClient } from '../../../../src';
/**
* test the Filter type using collection.find<T>() method
* MongoDB uses Filter type for every method that performs a document search
* for example: findX, updateX, deleteX, distinct, countDocuments
* So we don't add duplicate tests for every collection method and only use find
*/
const client = new MongoClient('');
const db = client.db('test');
/**
* Test the generic Filter using collection.find<T>() method
*/
// a collection model for all possible MongoDB BSON types and TypeScript types
interface PetModel {
_id: ObjectId; // ObjectId field
name?: string; // optional field
family: string; // string field
age: number; // number field
type: 'dog' | 'cat' | 'fish'; // union field
isCute: boolean; // boolean field
bestFriend?: PetModel; // object field (Embedded/Nested Documents)
createdAt: Date; // date field
treats: string[]; // array of string
playTimePercent: Decimal128; // bson Decimal128 type
readonly friends?: ReadonlyArray<PetModel>; // readonly array of objects
playmates?: PetModel[]; // writable array of objects
}
const spot = {
_id: new ObjectId('577fa2d90c4cc47e31cf4b6f'),
name: 'Spot',
family: 'Andersons',
age: 2,
type: 'dog' as const,
isCute: true,
createdAt: new Date(),
treats: ['kibble', 'bone'],
playTimePercent: new Decimal128('0.999999')
};
expectAssignable<PetModel>(spot);
const collectionT = db.collection<PetModel>('test.filterQuery');
// Assert that collection.find uses the Filter helper like so:
const filter: Filter<PetModel> = {};
collectionT.find(filter);
collectionT.find(spot); // a whole model definition is also a valid filter
// Now tests below can directly test the Filter helper, and are implicitly checking collection.find
/**
* test simple field queries e.g. `{ name: 'Spot' }`
*/
/// it should query __string__ fields
expectType<PetModel[]>(await collectionT.find({ name: 'Spot' }).toArray());
// it should query string fields by regex
expectType<PetModel[]>(await collectionT.find({ name: /Blu/i }).toArray());
// it should query string fields by RegExp object, and bson regex
expectType<PetModel[]>(await collectionT.find({ name: new RegExp('MrMeow', 'i') }).toArray());
expectType<PetModel[]>(await collectionT.find({ name: new BSONRegExp('MrMeow', 'i') }).toArray());
/// it should not accept wrong types for string fields
expectNotType<Filter<PetModel>>({ name: 23 });
expectNotType<Filter<PetModel>>({ name: { suffix: 'Jr' } });
expectNotType<Filter<PetModel>>({ name: ['Spot'] });
/// it should query __number__ fields
await collectionT.find({ age: 12 }).toArray();
/// it should not accept wrong types for number fields
expectNotType<Filter<PetModel>>({ age: /12/i }); // it cannot query number fields by regex
expectNotType<Filter<PetModel>>({ age: '23' });
expectNotType<Filter<PetModel>>({ age: { prefix: 43 } });
expectNotType<Filter<PetModel>>({ age: [23, 43] });
/// it should query __nested document__ fields only by exact match
// TODO: we currently cannot enforce field order but field order is important for mongo
await collectionT.find({ bestFriend: spot }).toArray();
/// nested documents query should contain all required fields
expectNotType<Filter<PetModel>>({ bestFriend: { family: 'Andersons' } });
/// it should not accept wrong types for nested document fields
expectNotType<Filter<PetModel>>({ bestFriend: 21 });
expectNotType<Filter<PetModel>>({ bestFriend: 'Andersons' });
expectNotType<Filter<PetModel>>({ bestFriend: [spot] });
expectNotType<Filter<PetModel>>({ bestFriend: [{ family: 'Andersons' }] });
/// it should query __array__ fields by exact match
await collectionT.find({ treats: ['kibble', 'bone'] }).toArray();
/// it should query __array__ fields by element type
expectType<PetModel[]>(await collectionT.find({ treats: 'kibble' }).toArray());
expectType<PetModel[]>(await collectionT.find({ treats: /kibble/i }).toArray());
expectType<PetModel[]>(await collectionT.find({ friends: spot }).toArray());
/// it should not query array fields by wrong types
expectNotType<Filter<PetModel>>({ treats: 12 });
expectNotType<Filter<PetModel>>({ friends: { name: 'not a full model' } });
/// it should accept MongoDB ObjectId and Date as query parameter
await collectionT.find({ createdAt: new Date() }).toArray();
await collectionT.find({ _id: new ObjectId() }).toArray();
/// it should not accept other types for ObjectId and Date
expectNotType<Filter<PetModel>>({ createdAt: { a: 12 } });
expectNotType<Filter<PetModel>>({ createdAt: spot });
expectNotType<Filter<PetModel>>({ _id: '577fa2d90c4cc47e31cf4b6f' });
expectNotType<Filter<PetModel>>({ _id: { a: 12 } });
/**
* test comparison query operators
*/
/// $eq $ne $gt $gte $lt $lte queries should behave exactly like simple queries above
await collectionT.find({ name: { $eq: 'Spot' } }).toArray();
await collectionT.find({ name: { $eq: /Spot/ } }).toArray();
await collectionT.find({ type: { $eq: 'dog' } }).toArray();
await collectionT.find({ age: { $gt: 12, $lt: 13 } }).toArray();
await collectionT.find({ treats: { $eq: 'kibble' } }).toArray();
await collectionT.find({ scores: { $gte: 23 } }).toArray();
await collectionT.find({ createdAt: { $lte: new Date() } }).toArray();
await collectionT.find({ friends: { $ne: spot } }).toArray();
/// it should not accept wrong queries
expectNotType<Filter<PetModel>>({ name: { $ne: 12 } });
expectNotType<Filter<PetModel>>({ gender: { $eq: '123' } });
expectNotType<Filter<PetModel>>({ createdAt: { $lte: '1232' } });
/// it should not accept undefined query selectors in query object
expectNotType<Filter<PetModel>>({ age: { $undefined: 12 } });
/// it should query simple fields using $in and $nin selectors
await collectionT.find({ name: { $in: ['Spot', 'MrMeow', 'Bubbles'] } }).toArray();
await collectionT.find({ age: { $in: [12, 13] } }).toArray();
await collectionT.find({ friends: { $in: [spot] } }).toArray();
await collectionT.find({ createdAt: { $nin: [new Date()] } }).toArray();
/// it should query array fields using $in and $nin selectors
await collectionT.find({ treats: { $in: ['kibble', 'bone', 'tuna'] } }).toArray();
await collectionT.find({ treats: { $in: [/kibble/, /bone/, /tuna/] } }).toArray();
/// it should not accept wrong params for $in and $nin selectors
expectNotType<Filter<PetModel>>({ name: { $in: ['Spot', 32, 42] } });
expectNotType<Filter<PetModel>>({ age: { $in: [/12/, 12] } });
expectNotType<Filter<PetModel>>({ createdAt: { $nin: [12] } });
expectNotType<Filter<PetModel>>({ friends: { $in: [{ name: 'MrMeow' }] } });
expectNotType<Filter<PetModel>>({ treats: { $in: [{ $eq: 21 }] } });
/**
* test logical query operators
*/
/// it should accept any query selector for __$not operator__
await collectionT.find({ name: { $not: { $eq: 'Spot' } } }).toArray();
/// it should accept regex for string fields
await collectionT.find({ name: { $not: /Hi/i } }).toArray();
await collectionT.find({ treats: { $not: /Hi/ } }).toArray();
/// it should not accept simple queries in $not operator
expectNotType<Filter<PetModel>>({ name: { $not: 'Spot' } });
/// it should not accept regex for non strings
expectNotType<Filter<PetModel>>({ age: { $not: /sdsd/ } });
/// it should accept any filter query for __$and, $or, $nor operator__
await collectionT.find({ $and: [{ name: 'Spot' }] }).toArray();
await collectionT.find({ $and: [{ name: 'Spot' }, { age: { $in: [12, 14] } }] }).toArray();
await collectionT.find({ $or: [{ name: /Spot/i }, { treats: 's12' }] }).toArray();
await collectionT.find({ $nor: [{ name: { $ne: 'Spot' } }] }).toArray();
/// it should not accept __$and, $or, $nor operator__ as non-root query
expectNotType<Filter<PetModel>>({ name: { $or: ['Spot', 'Bubbles'] } });
/// it should not accept single objects for __$and, $or, $nor operator__ query
expectNotType<Filter<PetModel>>({ $and: { name: 'Spot' } });
/**
* test 'element' query operators
*/
/// it should query using $exists
await collectionT.find({ name: { $exists: true } }).toArray();
await collectionT.find({ name: { $exists: false } }).toArray();
/// it should not query $exists by wrong values
expectNotType<Filter<PetModel>>({ name: { $exists: '' } });
expectNotType<Filter<PetModel>>({ name: { $exists: 'true' } });
/**
* test evaluation query operators
*/
/// it should query using $regex
await collectionT.find({ name: { $regex: /12/i } }).toArray();
/// it should query using $regex and $options
await collectionT.find({ name: { $regex: /12/, $options: 'i' } }).toArray();
/// it should not accept $regex for none string fields
expectNotType<Filter<PetModel>>({ age: { $regex: /12/ } });
expectNotType<Filter<PetModel>>({ age: { $options: '3' } });
/// it should query using $mod
await collectionT.find({ age: { $mod: [12, 2] } }).toArray();
/// it should not accept $mod for none number fields
expectNotType<Filter<PetModel>>({ name: { $mod: [12, 2] } });
/// it should not accept $mod with less/more than 2 elements
expectNotType<Filter<PetModel>>({ age: { $mod: [12, 2, 2] } });
expectNotType<Filter<PetModel>>({ age: { $mod: [12] } });
expectNotType<Filter<PetModel>>({ age: { $mod: [] } });
/// it should fulltext search using $text
await collectionT.find({ $text: { $search: 'Hello' } }).toArray();
await collectionT.find({ $text: { $search: 'Hello', $caseSensitive: true } }).toArray();
/// it should fulltext search only by string
expectNotType<Filter<PetModel>>({ $text: { $search: 21, $caseSensitive: 'true' } });
expectNotType<Filter<PetModel>>({ $text: { $search: { name: 'MrMeow' } } });
expectNotType<Filter<PetModel>>({ $text: { $search: /regex/g } });
/// it should query using $where
await collectionT.find({ $where: 'function() { return true }' }).toArray();
await collectionT
.find({
$where: function () {
expectType<PetModel>(this);
return this.name === 'MrMeow';
}
})
.toArray();
/// it should not fail when $where is not Function or String
expectNotType<Filter<PetModel>>({ $where: 12 });
expectNotType<Filter<PetModel>>({ $where: /regex/g });
/**
* test array query operators
*/
/// it should query array fields
await collectionT.find({ treats: { $size: 2 } }).toArray();
await collectionT.find({ treats: { $all: ['kibble', 'bone'] } }).toArray();
await collectionT.find({ friends: { $elemMatch: { name: 'MrMeow' } } }).toArray();
await collectionT.find({ playmates: { $elemMatch: { name: 'MrMeow' } } }).toArray();
/// it should not query non array fields
expectNotType<Filter<PetModel>>({ name: { $all: ['world', 'world'] } });
expectNotType<Filter<PetModel>>({ age: { $elemMatch: [1, 2] } });
expectNotType<Filter<PetModel>>({ type: { $size: 2 } });
// dot key case that shows it is assignable even when the referenced key is the wrong type
expectAssignable<Filter<PetModel>>({ 'bestFriend.name': 23 }); // using dot notation permits any type for the key
expectNotType<Filter<PetModel>>({ bestFriend: { name: 23 } });