tangled
alpha
login
or
join now
knotbin.com
/
nozzle
0
fork
atom
Thin MongoDB ODM built for Standard Schema
mongodb
zod
deno
0
fork
atom
overview
issues
pulls
pipelines
add update validation
knotbin.com
3 months ago
85074949
f64e27b8
verified
This commit was signed with the committer's
known signature
.
knotbin.com
SSH Key Fingerprint:
SHA256:cz+cxLxCL/B8cV6riZjeEPSqiRA5+YAQM9XfjxPWTWE=
+656
-923
10 changed files
expand all
collapse all
unified
split
deno.json
deno.lock
model.ts
scripts
test.ts
tests
crud_test.ts
features_test.ts
main_test.ts
mock_test.ts
utils.ts
validation_test.ts
+4
-5
deno.json
···
3
"version": "0.1.0",
4
"exports": "./mod.ts",
5
"license": "MIT",
6
-
"tasks": {
7
-
"test:mock": "deno test tests/mock_test.ts",
8
-
"test:watch": "deno run -A scripts/test.ts --mock --watch"
9
-
},
10
"imports": {
11
"@standard-schema/spec": "jsr:@standard-schema/spec@^1.0.0",
12
-
"mongodb": "npm:mongodb@^6.18.0"
0
0
0
13
}
14
}
···
3
"version": "0.1.0",
4
"exports": "./mod.ts",
5
"license": "MIT",
0
0
0
0
6
"imports": {
7
"@standard-schema/spec": "jsr:@standard-schema/spec@^1.0.0",
8
+
"@std/assert": "jsr:@std/assert@^1.0.16",
9
+
"@zod/zod": "jsr:@zod/zod@^4.1.13",
10
+
"mongodb": "npm:mongodb@^6.18.0",
11
+
"mongodb-memory-server-core": "npm:mongodb-memory-server-core@^10.3.0"
12
}
13
}
+189
deno.lock
···
4
"jsr:@standard-schema/spec@1": "1.0.0",
5
"jsr:@std/assert@*": "1.0.13",
6
"jsr:@std/assert@^1.0.13": "1.0.13",
0
7
"jsr:@std/internal@^1.0.10": "1.0.10",
0
8
"jsr:@std/internal@^1.0.6": "1.0.10",
9
"jsr:@std/testing@*": "1.0.15",
0
0
10
"npm:@types/node@*": "22.15.15",
0
11
"npm:mongodb@^6.18.0": "6.18.0"
12
},
13
"jsr": {
···
20
"jsr:@std/internal@^1.0.6"
21
]
22
},
0
0
0
0
0
0
23
"@std/internal@1.0.10": {
24
"integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7"
25
},
0
0
0
26
"@std/testing@1.0.15": {
27
"integrity": "a490169f5ccb0f3ae9c94fbc69d2cd43603f2cffb41713a85f99bbb0e3087cbc",
28
"dependencies": [
29
"jsr:@std/assert@^1.0.13",
30
"jsr:@std/internal@^1.0.10"
31
]
0
0
0
32
}
33
},
34
"npm": {
···
53
"@types/webidl-conversions"
54
]
55
},
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
56
"bson@6.10.4": {
57
"integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng=="
58
},
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
59
"memory-pager@1.5.0": {
60
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
61
},
···
66
"whatwg-url"
67
]
68
},
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
69
"mongodb@6.18.0": {
70
"integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
71
"dependencies": [
···
74
"mongodb-connection-string-url"
75
]
76
},
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
77
"punycode@2.3.1": {
78
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
0
0
0
0
0
0
0
0
79
},
80
"sparse-bitfield@3.0.3": {
81
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
···
83
"memory-pager"
84
]
85
},
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
86
"tr46@5.1.1": {
87
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
88
"dependencies": [
89
"punycode"
90
]
91
},
0
0
0
92
"undici-types@6.21.0": {
93
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
94
},
···
101
"tr46",
102
"webidl-conversions"
103
]
0
0
0
0
0
0
0
104
}
105
},
106
"workspace": {
107
"dependencies": [
108
"jsr:@standard-schema/spec@1",
0
0
0
109
"npm:mongodb@^6.18.0"
110
]
111
}
···
4
"jsr:@standard-schema/spec@1": "1.0.0",
5
"jsr:@std/assert@*": "1.0.13",
6
"jsr:@std/assert@^1.0.13": "1.0.13",
7
+
"jsr:@std/assert@^1.0.16": "1.0.16",
8
"jsr:@std/internal@^1.0.10": "1.0.10",
9
+
"jsr:@std/internal@^1.0.12": "1.0.12",
10
"jsr:@std/internal@^1.0.6": "1.0.10",
11
"jsr:@std/testing@*": "1.0.15",
12
+
"jsr:@zod/zod@*": "4.1.13",
13
+
"jsr:@zod/zod@^4.1.13": "4.1.13",
14
"npm:@types/node@*": "22.15.15",
15
+
"npm:mongodb-memory-server-core@^10.3.0": "10.3.0",
16
"npm:mongodb@^6.18.0": "6.18.0"
17
},
18
"jsr": {
···
25
"jsr:@std/internal@^1.0.6"
26
]
27
},
28
+
"@std/assert@1.0.16": {
29
+
"integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532",
30
+
"dependencies": [
31
+
"jsr:@std/internal@^1.0.12"
32
+
]
33
+
},
34
"@std/internal@1.0.10": {
35
"integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7"
36
},
37
+
"@std/internal@1.0.12": {
38
+
"integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027"
39
+
},
40
"@std/testing@1.0.15": {
41
"integrity": "a490169f5ccb0f3ae9c94fbc69d2cd43603f2cffb41713a85f99bbb0e3087cbc",
42
"dependencies": [
43
"jsr:@std/assert@^1.0.13",
44
"jsr:@std/internal@^1.0.10"
45
]
46
+
},
47
+
"@zod/zod@4.1.13": {
48
+
"integrity": "fef799152d630583b248645fcac03abedd13e39fd2b752d9466b905d73619bfd"
49
}
50
},
51
"npm": {
···
70
"@types/webidl-conversions"
71
]
72
},
73
+
"agent-base@7.1.4": {
74
+
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="
75
+
},
76
+
"async-mutex@0.5.0": {
77
+
"integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==",
78
+
"dependencies": [
79
+
"tslib"
80
+
]
81
+
},
82
+
"b4a@1.7.3": {
83
+
"integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="
84
+
},
85
+
"bare-events@2.8.2": {
86
+
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="
87
+
},
88
"bson@6.10.4": {
89
"integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng=="
90
},
91
+
"buffer-crc32@0.2.13": {
92
+
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="
93
+
},
94
+
"camelcase@6.3.0": {
95
+
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="
96
+
},
97
+
"commondir@1.0.1": {
98
+
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
99
+
},
100
+
"debug@4.4.3": {
101
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
102
+
"dependencies": [
103
+
"ms"
104
+
]
105
+
},
106
+
"events-universal@1.0.1": {
107
+
"integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
108
+
"dependencies": [
109
+
"bare-events"
110
+
]
111
+
},
112
+
"fast-fifo@1.3.2": {
113
+
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
114
+
},
115
+
"find-cache-dir@3.3.2": {
116
+
"integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
117
+
"dependencies": [
118
+
"commondir",
119
+
"make-dir",
120
+
"pkg-dir"
121
+
]
122
+
},
123
+
"find-up@4.1.0": {
124
+
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
125
+
"dependencies": [
126
+
"locate-path",
127
+
"path-exists"
128
+
]
129
+
},
130
+
"follow-redirects@1.15.11": {
131
+
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="
132
+
},
133
+
"https-proxy-agent@7.0.6": {
134
+
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
135
+
"dependencies": [
136
+
"agent-base",
137
+
"debug"
138
+
]
139
+
},
140
+
"locate-path@5.0.0": {
141
+
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
142
+
"dependencies": [
143
+
"p-locate"
144
+
]
145
+
},
146
+
"make-dir@3.1.0": {
147
+
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
148
+
"dependencies": [
149
+
"semver@6.3.1"
150
+
]
151
+
},
152
"memory-pager@1.5.0": {
153
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
154
},
···
159
"whatwg-url"
160
]
161
},
162
+
"mongodb-memory-server-core@10.3.0": {
163
+
"integrity": "sha512-tp+ZfTBAPqHXjROhAFg6HcVVzXaEhh/iHcbY7QPOIiLwr94OkBFAw4pixyGSfP5wI2SZeEA13lXyRmBAhugWgA==",
164
+
"dependencies": [
165
+
"async-mutex",
166
+
"camelcase",
167
+
"debug",
168
+
"find-cache-dir",
169
+
"follow-redirects",
170
+
"https-proxy-agent",
171
+
"mongodb",
172
+
"new-find-package-json",
173
+
"semver@7.7.3",
174
+
"tar-stream",
175
+
"tslib",
176
+
"yauzl"
177
+
]
178
+
},
179
"mongodb@6.18.0": {
180
"integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
181
"dependencies": [
···
184
"mongodb-connection-string-url"
185
]
186
},
187
+
"ms@2.1.3": {
188
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
189
+
},
190
+
"new-find-package-json@2.0.0": {
191
+
"integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==",
192
+
"dependencies": [
193
+
"debug"
194
+
]
195
+
},
196
+
"p-limit@2.3.0": {
197
+
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
198
+
"dependencies": [
199
+
"p-try"
200
+
]
201
+
},
202
+
"p-locate@4.1.0": {
203
+
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
204
+
"dependencies": [
205
+
"p-limit"
206
+
]
207
+
},
208
+
"p-try@2.2.0": {
209
+
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
210
+
},
211
+
"path-exists@4.0.0": {
212
+
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
213
+
},
214
+
"pend@1.2.0": {
215
+
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
216
+
},
217
+
"pkg-dir@4.2.0": {
218
+
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
219
+
"dependencies": [
220
+
"find-up"
221
+
]
222
+
},
223
"punycode@2.3.1": {
224
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
225
+
},
226
+
"semver@6.3.1": {
227
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
228
+
"bin": true
229
+
},
230
+
"semver@7.7.3": {
231
+
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
232
+
"bin": true
233
},
234
"sparse-bitfield@3.0.3": {
235
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
···
237
"memory-pager"
238
]
239
},
240
+
"streamx@2.23.0": {
241
+
"integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
242
+
"dependencies": [
243
+
"events-universal",
244
+
"fast-fifo",
245
+
"text-decoder"
246
+
]
247
+
},
248
+
"tar-stream@3.1.7": {
249
+
"integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
250
+
"dependencies": [
251
+
"b4a",
252
+
"fast-fifo",
253
+
"streamx"
254
+
]
255
+
},
256
+
"text-decoder@1.2.3": {
257
+
"integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
258
+
"dependencies": [
259
+
"b4a"
260
+
]
261
+
},
262
"tr46@5.1.1": {
263
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
264
"dependencies": [
265
"punycode"
266
]
267
},
268
+
"tslib@2.8.1": {
269
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
270
+
},
271
"undici-types@6.21.0": {
272
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
273
},
···
280
"tr46",
281
"webidl-conversions"
282
]
283
+
},
284
+
"yauzl@3.2.0": {
285
+
"integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==",
286
+
"dependencies": [
287
+
"buffer-crc32",
288
+
"pend"
289
+
]
290
}
291
},
292
"workspace": {
293
"dependencies": [
294
"jsr:@standard-schema/spec@1",
295
+
"jsr:@std/assert@^1.0.16",
296
+
"jsr:@zod/zod@^4.1.13",
297
+
"npm:mongodb-memory-server-core@^10.3.0",
298
"npm:mongodb@^6.18.0"
299
]
300
}
+30
-2
model.ts
···
30
return result.value;
31
}
32
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
33
export class Model<T extends Schema> {
34
private collection: Collection<Infer<T>>;
35
private schema: T;
···
70
query: Filter<Infer<T>>,
71
data: Partial<Infer<T>>,
72
): Promise<UpdateResult> {
73
-
return await this.collection.updateMany(query, { $set: data });
0
74
}
75
76
async updateOne(
77
query: Filter<Infer<T>>,
78
data: Partial<Infer<T>>,
79
): Promise<UpdateResult> {
80
-
return await this.collection.updateOne(query, { $set: data });
0
81
}
82
83
async replaceOne(
···
30
return result.value;
31
}
32
33
+
// Helper function to validate partial update data
34
+
// Uses schema.partial() if available (e.g., Zod)
35
+
function parsePartial<T extends Schema>(
36
+
schema: T,
37
+
data: Partial<Infer<T>>,
38
+
): Partial<Infer<T>> {
39
+
// Get partial schema if available
40
+
const partialSchema = (
41
+
typeof schema === "object" &&
42
+
schema !== null &&
43
+
"partial" in schema &&
44
+
typeof (schema as { partial?: () => unknown }).partial === "function"
45
+
)
46
+
? (schema as { partial: () => T }).partial()
47
+
: schema;
48
+
49
+
const result = partialSchema["~standard"].validate(data);
50
+
if (result instanceof Promise) {
51
+
throw new Error("Async validation not supported");
52
+
}
53
+
if (result.issues) {
54
+
throw new Error(`Update validation failed: ${JSON.stringify(result.issues)}`);
55
+
}
56
+
return result.value as Partial<Infer<T>>;
57
+
}
58
+
59
export class Model<T extends Schema> {
60
private collection: Collection<Infer<T>>;
61
private schema: T;
···
96
query: Filter<Infer<T>>,
97
data: Partial<Infer<T>>,
98
): Promise<UpdateResult> {
99
+
const validatedData = parsePartial(this.schema, data);
100
+
return await this.collection.updateMany(query, { $set: validatedData });
101
}
102
103
async updateOne(
104
query: Filter<Infer<T>>,
105
data: Partial<Infer<T>>,
106
): Promise<UpdateResult> {
107
+
const validatedData = parsePartial(this.schema, data);
108
+
return await this.collection.updateOne(query, { $set: validatedData });
109
}
110
111
async replaceOne(
-235
scripts/test.ts
···
1
-
#!/usr/bin/env -S deno run --allow-run --allow-read
2
-
3
-
/**
4
-
* Test runner script for Nozzle ORM
5
-
*
6
-
* Usage:
7
-
* deno run --allow-run --allow-read scripts/test.ts [options]
8
-
*
9
-
* Options:
10
-
* --mock Run only mock tests (no MongoDB required)
11
-
* --real Run integration tests (requires MongoDB)
12
-
* --all Run all tests (default)
13
-
* --bdd Use BDD-style output for mock tests
14
-
* --filter Filter tests by name pattern
15
-
* --watch Watch for file changes and re-run tests
16
-
* --help Show this help message
17
-
*/
18
-
19
-
interface TestOptions {
20
-
mock?: boolean;
21
-
real?: boolean;
22
-
all?: boolean;
23
-
bdd?: boolean;
24
-
filter?: string;
25
-
watch?: boolean;
26
-
help?: boolean;
27
-
}
28
-
29
-
function parseArgs(): TestOptions {
30
-
const args = Deno.args;
31
-
const options: TestOptions = {};
32
-
33
-
for (let i = 0; i < args.length; i++) {
34
-
const arg = args[i];
35
-
switch (arg) {
36
-
case "--mock":
37
-
options.mock = true;
38
-
break;
39
-
case "--real":
40
-
options.real = true;
41
-
break;
42
-
case "--all":
43
-
options.all = true;
44
-
break;
45
-
case "--bdd":
46
-
options.bdd = true;
47
-
break;
48
-
case "--filter":
49
-
options.filter = args[++i];
50
-
break;
51
-
case "--watch":
52
-
options.watch = true;
53
-
break;
54
-
case "--help":
55
-
options.help = true;
56
-
break;
57
-
}
58
-
}
59
-
60
-
// Default to all tests if no specific option is provided
61
-
if (!options.mock && !options.real && !options.help) {
62
-
options.all = true;
63
-
}
64
-
65
-
return options;
66
-
}
67
-
68
-
function showHelp() {
69
-
console.log(`
70
-
🧪 Nozzle ORM Test Runner
71
-
72
-
Usage:
73
-
deno run --allow-run --allow-read scripts/test.ts [options]
74
-
75
-
Options:
76
-
--mock Run only mock tests (no MongoDB required)
77
-
--real Run integration tests (requires MongoDB)
78
-
--all Run all tests (default)
79
-
--bdd Use BDD-style output for mock tests
80
-
--filter Filter tests by name pattern
81
-
--watch Watch for file changes and re-run tests
82
-
--help Show this help message
83
-
84
-
Examples:
85
-
scripts/test.ts # Run all tests
86
-
scripts/test.ts --mock # Run only mock tests
87
-
scripts/test.ts --real # Run only integration tests
88
-
scripts/test.ts --mock --bdd # Run mock tests with BDD output
89
-
scripts/test.ts --filter "Insert" # Run tests matching "Insert"
90
-
scripts/test.ts --watch --mock # Watch and run mock tests
91
-
92
-
Prerequisites for integration tests:
93
-
- MongoDB running on localhost:27017
94
-
- Or update connection string in tests/main_test.ts
95
-
`);
96
-
}
97
-
98
-
async function runCommand(cmd: string[]) {
99
-
const process = new Deno.Command(cmd[0], {
100
-
args: cmd.slice(1),
101
-
stdout: "inherit",
102
-
stderr: "inherit",
103
-
});
104
-
105
-
const { success, code } = await process.output();
106
-
return { success, code };
107
-
}
108
-
109
-
async function runTests(options: TestOptions) {
110
-
const baseCmd = ["deno", "test"];
111
-
const permissions = [
112
-
"--allow-net",
113
-
"--allow-read",
114
-
"--allow-write",
115
-
"--allow-env",
116
-
"--allow-sys",
117
-
];
118
-
119
-
if (options.help) {
120
-
showHelp();
121
-
return;
122
-
}
123
-
124
-
console.log("🚀 Starting Nozzle Tests...\n");
125
-
126
-
if (options.mock) {
127
-
console.log("📋 Running mock tests (no MongoDB required)...");
128
-
const cmd = [...baseCmd, "tests/mock_test.ts"];
129
-
if (options.bdd) {
130
-
cmd.push("--reporter", "pretty");
131
-
}
132
-
if (options.filter) {
133
-
cmd.push("--filter", options.filter);
134
-
}
135
-
if (options.watch) {
136
-
cmd.push("--watch");
137
-
}
138
-
139
-
const result = await runCommand(cmd);
140
-
if (!result.success) {
141
-
console.error("❌ Mock tests failed");
142
-
Deno.exit(result.code);
143
-
} else {
144
-
console.log("✅ Mock tests passed!");
145
-
}
146
-
}
147
-
148
-
if (options.real) {
149
-
console.log("🗄️ Running integration tests (MongoDB required)...");
150
-
console.log("⚠️ Make sure MongoDB is running on localhost:27017\n");
151
-
152
-
const cmd = [...baseCmd, ...permissions, "tests/main_test.ts"];
153
-
if (options.filter) {
154
-
cmd.push("--filter", options.filter);
155
-
}
156
-
if (options.watch) {
157
-
cmd.push("--watch");
158
-
}
159
-
160
-
const result = await runCommand(cmd);
161
-
if (!result.success) {
162
-
console.error("❌ Integration tests failed");
163
-
if (result.code === 1) {
164
-
console.log("\n💡 Troubleshooting tips:");
165
-
console.log(
166
-
" • Ensure MongoDB is running: brew services start mongodb-community",
167
-
);
168
-
console.log(
169
-
" • Or start with Docker: docker run -p 27017:27017 -d mongo",
170
-
);
171
-
console.log(" • Check connection at: mongodb://localhost:27017");
172
-
}
173
-
Deno.exit(result.code);
174
-
} else {
175
-
console.log("✅ Integration tests passed!");
176
-
}
177
-
}
178
-
179
-
if (options.all) {
180
-
console.log("🎯 Running all tests...\n");
181
-
182
-
// Run mock tests first
183
-
console.log("1️⃣ Running mock tests...");
184
-
const mockCmd = [...baseCmd, "tests/mock_test.ts"];
185
-
if (options.bdd) {
186
-
mockCmd.push("--reporter", "pretty");
187
-
}
188
-
if (options.filter) {
189
-
mockCmd.push("--filter", options.filter);
190
-
}
191
-
192
-
const mockResult = await runCommand(mockCmd);
193
-
if (mockResult.success) {
194
-
console.log("✅ Mock tests passed!\n");
195
-
} else {
196
-
console.error("❌ Mock tests failed\n");
197
-
}
198
-
199
-
// Run integration tests
200
-
console.log("2️⃣ Running integration tests...");
201
-
console.log("⚠️ Make sure MongoDB is running on localhost:27017\n");
202
-
203
-
const integrationCmd = [...baseCmd, ...permissions, "tests/main_test.ts"];
204
-
if (options.filter) {
205
-
integrationCmd.push("--filter", options.filter);
206
-
}
207
-
if (options.watch) {
208
-
integrationCmd.push("--watch");
209
-
}
210
-
211
-
const integrationResult = await runCommand(integrationCmd);
212
-
213
-
if (mockResult.success && integrationResult.success) {
214
-
console.log("\n🎉 All tests passed!");
215
-
} else {
216
-
console.error("\n💥 Some tests failed!");
217
-
if (!integrationResult.success) {
218
-
console.log("\n💡 Integration test troubleshooting:");
219
-
console.log(
220
-
" • Ensure MongoDB is running: brew services start mongodb-community",
221
-
);
222
-
console.log(
223
-
" • Or start with Docker: docker run -p 27017:27017 -d mongo",
224
-
);
225
-
console.log(" • Check connection at: mongodb://localhost:27017");
226
-
}
227
-
Deno.exit(Math.max(mockResult.code, integrationResult.code));
228
-
}
229
-
}
230
-
}
231
-
232
-
if (import.meta.main) {
233
-
const options = parseArgs();
234
-
await runTests(options);
235
-
}
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
+161
tests/crud_test.ts
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
import { assertEquals, assertExists } from "@std/assert";
2
+
import { ObjectId } from "mongodb";
3
+
import {
4
+
cleanupCollection,
5
+
createUserModel,
6
+
setupTestDb,
7
+
teardownTestDb,
8
+
type UserInsert,
9
+
type userSchema,
10
+
} from "./utils.ts";
11
+
import type { Model } from "../mod.ts";
12
+
13
+
let UserModel: Model<typeof userSchema>;
14
+
15
+
Deno.test.beforeAll(async () => {
16
+
await setupTestDb();
17
+
UserModel = createUserModel();
18
+
});
19
+
20
+
Deno.test.beforeEach(async () => {
21
+
await cleanupCollection(UserModel);
22
+
});
23
+
24
+
Deno.test.afterAll(async () => {
25
+
await teardownTestDb();
26
+
});
27
+
28
+
Deno.test({
29
+
name: "CRUD: Insert - should insert a new user successfully",
30
+
async fn() {
31
+
32
+
const newUser: UserInsert = {
33
+
name: "Test User",
34
+
email: "test@example.com",
35
+
age: 25,
36
+
};
37
+
38
+
const insertResult = await UserModel.insertOne(newUser);
39
+
40
+
assertExists(insertResult.insertedId);
41
+
console.log("User inserted with ID:", insertResult.insertedId);
42
+
},
43
+
sanitizeResources: false,
44
+
sanitizeOps: false,
45
+
});
46
+
47
+
Deno.test({
48
+
name: "CRUD: Find - should find the inserted user",
49
+
async fn() {
50
+
51
+
// First insert a user for this test
52
+
const newUser: UserInsert = {
53
+
name: "Find Test User",
54
+
email: "findtest@example.com",
55
+
age: 30,
56
+
};
57
+
const insertResult = await UserModel.insertOne(newUser);
58
+
assertExists(insertResult.insertedId);
59
+
60
+
const foundUser = await UserModel.findOne({
61
+
_id: new ObjectId(insertResult.insertedId),
62
+
});
63
+
64
+
assertExists(foundUser);
65
+
assertEquals(foundUser.email, "findtest@example.com");
66
+
assertEquals(foundUser.name, "Find Test User");
67
+
assertEquals(foundUser.age, 30);
68
+
},
69
+
sanitizeResources: false,
70
+
sanitizeOps: false,
71
+
});
72
+
73
+
Deno.test({
74
+
name: "CRUD: Update - should update user data",
75
+
async fn() {
76
+
77
+
// Insert a user for this test
78
+
const newUser: UserInsert = {
79
+
name: "Update Test User",
80
+
email: "updatetest@example.com",
81
+
age: 25,
82
+
};
83
+
const insertResult = await UserModel.insertOne(newUser);
84
+
assertExists(insertResult.insertedId);
85
+
86
+
// Update the user
87
+
const updateResult = await UserModel.update(
88
+
{ _id: new ObjectId(insertResult.insertedId) },
89
+
{ age: 26 },
90
+
);
91
+
92
+
assertEquals(updateResult.modifiedCount, 1);
93
+
94
+
// Verify the update
95
+
const updatedUser = await UserModel.findOne({
96
+
_id: new ObjectId(insertResult.insertedId),
97
+
});
98
+
99
+
assertExists(updatedUser);
100
+
assertEquals(updatedUser.age, 26);
101
+
},
102
+
sanitizeResources: false,
103
+
sanitizeOps: false,
104
+
});
105
+
106
+
Deno.test({
107
+
name: "CRUD: Delete - should delete user successfully",
108
+
async fn() {
109
+
110
+
// Insert a user for this test
111
+
const newUser: UserInsert = {
112
+
name: "Delete Test User",
113
+
email: "deletetest@example.com",
114
+
age: 35,
115
+
};
116
+
const insertResult = await UserModel.insertOne(newUser);
117
+
assertExists(insertResult.insertedId);
118
+
119
+
// Delete the user
120
+
const deleteResult = await UserModel.delete({
121
+
_id: new ObjectId(insertResult.insertedId),
122
+
});
123
+
124
+
assertEquals(deleteResult.deletedCount, 1);
125
+
126
+
// Verify deletion
127
+
const deletedUser = await UserModel.findOne({
128
+
_id: new ObjectId(insertResult.insertedId),
129
+
});
130
+
131
+
assertEquals(deletedUser, null);
132
+
},
133
+
sanitizeResources: false,
134
+
sanitizeOps: false,
135
+
});
136
+
137
+
Deno.test({
138
+
name: "CRUD: Find Multiple - should find multiple users",
139
+
async fn() {
140
+
141
+
// Insert multiple users
142
+
const users: UserInsert[] = [
143
+
{ name: "User 1", email: "user1@example.com", age: 20 },
144
+
{ name: "User 2", email: "user2@example.com", age: 25 },
145
+
{ name: "User 3", email: "user3@example.com", age: 30 },
146
+
];
147
+
148
+
for (const user of users) {
149
+
await UserModel.insertOne(user);
150
+
}
151
+
152
+
// Find all users with age >= 25
153
+
const foundUsers = await UserModel.find({ age: { $gte: 25 } });
154
+
155
+
assertEquals(foundUsers.length >= 2, true);
156
+
},
157
+
sanitizeResources: false,
158
+
sanitizeOps: false,
159
+
});
160
+
161
+
+53
tests/features_test.ts
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
import { assertEquals, assertExists } from "@std/assert";
2
+
import { ObjectId } from "mongodb";
3
+
import {
4
+
cleanupCollection,
5
+
createUserModel,
6
+
setupTestDb,
7
+
teardownTestDb,
8
+
type UserInsert,
9
+
type userSchema,
10
+
} from "./utils.ts";
11
+
import type { Model } from "../mod.ts";
12
+
13
+
let UserModel: Model<typeof userSchema>;
14
+
15
+
Deno.test.beforeAll(async () => {
16
+
await setupTestDb();
17
+
UserModel = createUserModel();
18
+
});
19
+
20
+
Deno.test.beforeEach(async () => {
21
+
await cleanupCollection(UserModel);
22
+
});
23
+
24
+
Deno.test.afterAll(async () => {
25
+
await teardownTestDb();
26
+
});
27
+
28
+
Deno.test({
29
+
name: "Features: Default Values - should handle default createdAt",
30
+
async fn() {
31
+
32
+
const newUser: UserInsert = {
33
+
name: "Default Test User",
34
+
email: "default@example.com",
35
+
// No createdAt provided - should use default
36
+
};
37
+
38
+
const insertResult = await UserModel.insertOne(newUser);
39
+
assertExists(insertResult.insertedId);
40
+
41
+
const foundUser = await UserModel.findOne({
42
+
_id: new ObjectId(insertResult.insertedId),
43
+
});
44
+
45
+
assertExists(foundUser);
46
+
assertExists(foundUser.createdAt);
47
+
assertEquals(foundUser.createdAt instanceof Date, true);
48
+
},
49
+
sanitizeResources: false,
50
+
sanitizeOps: false,
51
+
});
52
+
53
+
-229
tests/main_test.ts
···
1
-
import { assertEquals, assertExists, assertRejects } from "jsr:@std/assert";
2
-
import { z } from "jsr:@zod/zod";
3
-
import { connect, disconnect, type InsertType, Model } from "../mod.ts";
4
-
import { ObjectId } from "mongodb";
5
-
6
-
const userSchema = z.object({
7
-
name: z.string(),
8
-
email: z.email(),
9
-
age: z.number().int().positive().optional(),
10
-
createdAt: z.date().default(() => new Date()),
11
-
});
12
-
13
-
type UserInsert = InsertType<typeof userSchema>;
14
-
15
-
let UserModel: Model<typeof userSchema>;
16
-
let isSetup = false;
17
-
18
-
async function setup() {
19
-
if (!isSetup) {
20
-
await connect("mongodb://localhost:27017", "mizzleorm_test_db");
21
-
UserModel = new Model("users", userSchema);
22
-
isSetup = true;
23
-
}
24
-
// Clean up before each test
25
-
await UserModel.delete({});
26
-
}
27
-
28
-
async function teardown() {
29
-
if (isSetup) {
30
-
await disconnect();
31
-
isSetup = false;
32
-
}
33
-
}
34
-
35
-
Deno.test({
36
-
name: "Insert - should insert a new user successfully",
37
-
async fn() {
38
-
await setup();
39
-
40
-
const newUser: UserInsert = {
41
-
name: "Test User",
42
-
email: "test@example.com",
43
-
age: 25,
44
-
};
45
-
46
-
const insertResult = await UserModel.insertOne(newUser);
47
-
48
-
assertExists(insertResult.insertedId);
49
-
console.log("User inserted with ID:", insertResult.insertedId);
50
-
},
51
-
sanitizeResources: false,
52
-
sanitizeOps: false,
53
-
});
54
-
55
-
Deno.test({
56
-
name: "Find - should find the inserted user",
57
-
async fn() {
58
-
await setup();
59
-
60
-
// First insert a user for this test
61
-
const newUser: UserInsert = {
62
-
name: "Find Test User",
63
-
email: "findtest@example.com",
64
-
age: 30,
65
-
};
66
-
const insertResult = await UserModel.insertOne(newUser);
67
-
assertExists(insertResult.insertedId);
68
-
69
-
const foundUser = await UserModel.findOne({
70
-
_id: new ObjectId(insertResult.insertedId),
71
-
});
72
-
73
-
assertExists(foundUser);
74
-
assertEquals(foundUser.email, "findtest@example.com");
75
-
assertEquals(foundUser.name, "Find Test User");
76
-
assertEquals(foundUser.age, 30);
77
-
},
78
-
sanitizeResources: false,
79
-
sanitizeOps: false,
80
-
});
81
-
82
-
Deno.test({
83
-
name: "Update - should update user data",
84
-
async fn() {
85
-
await setup();
86
-
87
-
// Insert a user for this test
88
-
const newUser: UserInsert = {
89
-
name: "Update Test User",
90
-
email: "updatetest@example.com",
91
-
age: 25,
92
-
};
93
-
const insertResult = await UserModel.insertOne(newUser);
94
-
assertExists(insertResult.insertedId);
95
-
96
-
// Update the user
97
-
const updateResult = await UserModel.update(
98
-
{ _id: new ObjectId(insertResult.insertedId) },
99
-
{ age: 26 },
100
-
);
101
-
102
-
assertEquals(updateResult.modifiedCount, 1);
103
-
104
-
// Verify the update
105
-
const updatedUser = await UserModel.findOne({
106
-
_id: new ObjectId(insertResult.insertedId),
107
-
});
108
-
109
-
assertExists(updatedUser);
110
-
assertEquals(updatedUser.age, 26);
111
-
},
112
-
sanitizeResources: false,
113
-
sanitizeOps: false,
114
-
});
115
-
116
-
Deno.test({
117
-
name: "Delete - should delete user successfully",
118
-
async fn() {
119
-
await setup();
120
-
121
-
// Insert a user for this test
122
-
const newUser: UserInsert = {
123
-
name: "Delete Test User",
124
-
email: "deletetest@example.com",
125
-
age: 35,
126
-
};
127
-
const insertResult = await UserModel.insertOne(newUser);
128
-
assertExists(insertResult.insertedId);
129
-
130
-
// Delete the user
131
-
const deleteResult = await UserModel.delete({
132
-
_id: new ObjectId(insertResult.insertedId),
133
-
});
134
-
135
-
assertEquals(deleteResult.deletedCount, 1);
136
-
137
-
// Verify deletion
138
-
const deletedUser = await UserModel.findOne({
139
-
_id: new ObjectId(insertResult.insertedId),
140
-
});
141
-
142
-
assertEquals(deletedUser, null);
143
-
},
144
-
sanitizeResources: false,
145
-
sanitizeOps: false,
146
-
});
147
-
148
-
Deno.test({
149
-
name: "Schema Validation - should validate user data",
150
-
async fn() {
151
-
await setup();
152
-
153
-
const invalidUser = {
154
-
name: "Invalid User",
155
-
email: "not-an-email", // Invalid email
156
-
age: -5, // Negative age
157
-
};
158
-
159
-
// This should throw an error due to schema validation
160
-
await assertRejects(
161
-
async () => {
162
-
await UserModel.insertOne(invalidUser as UserInsert);
163
-
},
164
-
Error,
165
-
);
166
-
},
167
-
sanitizeResources: false,
168
-
sanitizeOps: false,
169
-
});
170
-
171
-
Deno.test({
172
-
name: "Find Multiple - should find multiple users",
173
-
async fn() {
174
-
await setup();
175
-
176
-
// Insert multiple users
177
-
const users: UserInsert[] = [
178
-
{ name: "User 1", email: "user1@example.com", age: 20 },
179
-
{ name: "User 2", email: "user2@example.com", age: 25 },
180
-
{ name: "User 3", email: "user3@example.com", age: 30 },
181
-
];
182
-
183
-
for (const user of users) {
184
-
await UserModel.insertOne(user);
185
-
}
186
-
187
-
// Find all users with age >= 25
188
-
const foundUsers = await UserModel.find({ age: { $gte: 25 } });
189
-
190
-
assertEquals(foundUsers.length >= 2, true);
191
-
},
192
-
sanitizeResources: false,
193
-
sanitizeOps: false,
194
-
});
195
-
196
-
Deno.test({
197
-
name: "Default Values - should handle default createdAt",
198
-
async fn() {
199
-
await setup();
200
-
201
-
const newUser: UserInsert = {
202
-
name: "Default Test User",
203
-
email: "default@example.com",
204
-
// No createdAt provided - should use default
205
-
};
206
-
207
-
const insertResult = await UserModel.insertOne(newUser);
208
-
assertExists(insertResult.insertedId);
209
-
210
-
const foundUser = await UserModel.findOne({
211
-
_id: new ObjectId(insertResult.insertedId),
212
-
});
213
-
214
-
assertExists(foundUser);
215
-
assertExists(foundUser.createdAt);
216
-
assertEquals(foundUser.createdAt instanceof Date, true);
217
-
},
218
-
sanitizeResources: false,
219
-
sanitizeOps: false,
220
-
});
221
-
222
-
Deno.test({
223
-
name: "Teardown - Clean up and disconnect",
224
-
async fn() {
225
-
await teardown();
226
-
},
227
-
sanitizeResources: false,
228
-
sanitizeOps: false,
229
-
});
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
-452
tests/mock_test.ts
···
1
-
import { afterEach, beforeEach, describe, it } from "jsr:@std/testing/bdd";
2
-
import { assertEquals, assertExists, assertRejects } from "jsr:@std/assert";
3
-
import { z } from "jsr:@zod/zod";
4
-
5
-
// Mock implementation for demonstration
6
-
class MockModel<T> {
7
-
private data: Array<T & { _id: number }> = [];
8
-
private idCounter = 1;
9
-
10
-
constructor(private collection: string, private schema: z.ZodSchema<T>) {}
11
-
12
-
insertOne(doc: z.input<z.ZodSchema<T>>) {
13
-
// Validate with schema
14
-
const validated = this.schema.parse(doc);
15
-
const withId = { ...validated, _id: this.idCounter++ };
16
-
this.data.push(withId);
17
-
return { insertedId: withId._id };
18
-
}
19
-
20
-
findOne(filter: Partial<T & { _id: number }>) {
21
-
if (filter._id) {
22
-
return this.data.find((item) => item._id === filter._id) || null;
23
-
}
24
-
return this.data.find((item) =>
25
-
Object.entries(filter).every(([key, value]) =>
26
-
(item as Record<string, unknown>)[key] === value
27
-
)
28
-
) || null;
29
-
}
30
-
31
-
find(filter: Record<string, unknown> = {}) {
32
-
return this.data.filter((item) => {
33
-
return Object.entries(filter).every(([key, value]) => {
34
-
if (typeof value === "object" && value !== null && "$gte" in value) {
35
-
const itemValue = (item as Record<string, unknown>)[key];
36
-
const gteValue = (value as { $gte: unknown }).$gte;
37
-
return typeof itemValue === "number" &&
38
-
typeof gteValue === "number" && itemValue >= gteValue;
39
-
}
40
-
return (item as Record<string, unknown>)[key] === value;
41
-
});
42
-
});
43
-
}
44
-
45
-
update(filter: Partial<T & { _id: number }>, update: Partial<T>) {
46
-
let modifiedCount = 0;
47
-
this.data = this.data.map((item) => {
48
-
if (filter._id && (item as T & { _id: number })._id === filter._id) {
49
-
modifiedCount++;
50
-
return { ...item, ...update };
51
-
}
52
-
return item;
53
-
});
54
-
return { modifiedCount };
55
-
}
56
-
57
-
delete(filter: Partial<T & { _id: number }>) {
58
-
const initialLength = this.data.length;
59
-
if (Object.keys(filter).length === 0) {
60
-
// Delete all
61
-
this.data = [];
62
-
return { deletedCount: initialLength };
63
-
}
64
-
65
-
this.data = this.data.filter((item) => {
66
-
if (filter._id) {
67
-
return (item as T & { _id: number })._id !== filter._id;
68
-
}
69
-
return !Object.entries(filter).every(([key, value]) =>
70
-
(item as Record<string, unknown>)[key] === value
71
-
);
72
-
});
73
-
74
-
return { deletedCount: initialLength - this.data.length };
75
-
}
76
-
77
-
// Helper method to clear data
78
-
clear() {
79
-
this.data = [];
80
-
this.idCounter = 1;
81
-
}
82
-
}
83
-
84
-
// Schema definition
85
-
const userSchema = z.object({
86
-
name: z.string(),
87
-
email: z.string().email(),
88
-
age: z.number().int().positive().optional(),
89
-
createdAt: z.date().default(() => new Date()),
90
-
});
91
-
92
-
type User = z.infer<typeof userSchema>;
93
-
type UserInsert = z.input<typeof userSchema>;
94
-
95
-
let UserModel: MockModel<User>;
96
-
97
-
describe("Mock User Model Tests", () => {
98
-
beforeEach(() => {
99
-
UserModel = new MockModel("users", userSchema);
100
-
});
101
-
102
-
afterEach(() => {
103
-
UserModel?.clear();
104
-
});
105
-
106
-
describe("Insert Operations", () => {
107
-
it("should insert a new user successfully", async () => {
108
-
const newUser: UserInsert = {
109
-
name: "Test User",
110
-
email: "test@example.com",
111
-
age: 25,
112
-
};
113
-
114
-
const insertResult = await UserModel.insertOne(newUser);
115
-
116
-
assertExists(insertResult.insertedId);
117
-
assertEquals(typeof insertResult.insertedId, "number");
118
-
});
119
-
120
-
it("should handle user without optional age", async () => {
121
-
const newUser: UserInsert = {
122
-
name: "User Without Age",
123
-
email: "noage@example.com",
124
-
};
125
-
126
-
const insertResult = await UserModel.insertOne(newUser);
127
-
assertExists(insertResult.insertedId);
128
-
129
-
const foundUser = await UserModel.findOne({
130
-
_id: insertResult.insertedId,
131
-
});
132
-
assertEquals(foundUser?.name, "User Without Age");
133
-
assertEquals(foundUser?.age, undefined);
134
-
});
135
-
136
-
it("should apply default createdAt value", async () => {
137
-
const newUser: UserInsert = {
138
-
name: "Default Test User",
139
-
email: "default@example.com",
140
-
};
141
-
142
-
const insertResult = await UserModel.insertOne(newUser);
143
-
const foundUser = await UserModel.findOne({
144
-
_id: insertResult.insertedId,
145
-
});
146
-
147
-
assertExists(foundUser);
148
-
assertExists(foundUser.createdAt);
149
-
assertEquals(foundUser.createdAt instanceof Date, true);
150
-
});
151
-
});
152
-
153
-
describe("Find Operations", () => {
154
-
it("should find user by ID", async () => {
155
-
const newUser: UserInsert = {
156
-
name: "Find Test User",
157
-
email: "findtest@example.com",
158
-
age: 30,
159
-
};
160
-
const insertResult = await UserModel.insertOne(newUser);
161
-
162
-
const foundUser = await UserModel.findOne({
163
-
_id: insertResult.insertedId,
164
-
});
165
-
166
-
assertExists(foundUser);
167
-
assertEquals(foundUser.email, "findtest@example.com");
168
-
assertEquals(foundUser.name, "Find Test User");
169
-
assertEquals(foundUser.age, 30);
170
-
});
171
-
172
-
it("should find user by email", async () => {
173
-
await UserModel.insertOne({
174
-
name: "Email Test User",
175
-
email: "email@test.com",
176
-
age: 28,
177
-
});
178
-
179
-
const foundUser = await UserModel.findOne({
180
-
email: "email@test.com",
181
-
});
182
-
183
-
assertExists(foundUser);
184
-
assertEquals(foundUser.name, "Email Test User");
185
-
});
186
-
187
-
it("should return null for non-existent user", async () => {
188
-
const foundUser = await UserModel.findOne({
189
-
_id: 999,
190
-
});
191
-
192
-
assertEquals(foundUser, null);
193
-
});
194
-
195
-
it("should find multiple users with filters", async () => {
196
-
const users: UserInsert[] = [
197
-
{ name: "User 1", email: "user1@example.com", age: 20 },
198
-
{ name: "User 2", email: "user2@example.com", age: 25 },
199
-
{ name: "User 3", email: "user3@example.com", age: 30 },
200
-
];
201
-
202
-
for (const user of users) {
203
-
await UserModel.insertOne(user);
204
-
}
205
-
206
-
const foundUsers = await UserModel.find({ age: { $gte: 25 } });
207
-
208
-
assertEquals(foundUsers.length, 2);
209
-
assertEquals(
210
-
foundUsers.every((user) => user.age !== undefined && user.age >= 25),
211
-
true,
212
-
);
213
-
});
214
-
215
-
it("should find all users with empty filter", async () => {
216
-
await UserModel.insertOne({
217
-
name: "User A",
218
-
email: "a@test.com",
219
-
age: 20,
220
-
});
221
-
await UserModel.insertOne({
222
-
name: "User B",
223
-
email: "b@test.com",
224
-
age: 25,
225
-
});
226
-
227
-
const allUsers = await UserModel.find();
228
-
229
-
assertEquals(allUsers.length, 2);
230
-
});
231
-
});
232
-
233
-
describe("Update Operations", () => {
234
-
it("should update user data", async () => {
235
-
const newUser: UserInsert = {
236
-
name: "Update Test User",
237
-
email: "updatetest@example.com",
238
-
age: 25,
239
-
};
240
-
const insertResult = await UserModel.insertOne(newUser);
241
-
242
-
const updateResult = await UserModel.update(
243
-
{ _id: insertResult.insertedId },
244
-
{ age: 26 },
245
-
);
246
-
247
-
assertEquals(updateResult.modifiedCount, 1);
248
-
249
-
const updatedUser = await UserModel.findOne({
250
-
_id: insertResult.insertedId,
251
-
});
252
-
assertEquals(updatedUser?.age, 26);
253
-
});
254
-
255
-
it("should update multiple fields", async () => {
256
-
const newUser: UserInsert = {
257
-
name: "Multi Update User",
258
-
email: "multi@example.com",
259
-
age: 30,
260
-
};
261
-
const insertResult = await UserModel.insertOne(newUser);
262
-
263
-
await UserModel.update(
264
-
{ _id: insertResult.insertedId },
265
-
{ name: "Updated Name", age: 35 },
266
-
);
267
-
268
-
const updatedUser = await UserModel.findOne({
269
-
_id: insertResult.insertedId,
270
-
});
271
-
assertEquals(updatedUser?.name, "Updated Name");
272
-
assertEquals(updatedUser?.age, 35);
273
-
});
274
-
275
-
it("should return 0 modified count for non-existent user", async () => {
276
-
const updateResult = await UserModel.update(
277
-
{ _id: 999 },
278
-
{ age: 100 },
279
-
);
280
-
281
-
assertEquals(updateResult.modifiedCount, 0);
282
-
});
283
-
});
284
-
285
-
describe("Delete Operations", () => {
286
-
it("should delete user successfully", async () => {
287
-
const newUser: UserInsert = {
288
-
name: "Delete Test User",
289
-
email: "deletetest@example.com",
290
-
age: 35,
291
-
};
292
-
const insertResult = await UserModel.insertOne(newUser);
293
-
294
-
const deleteResult = await UserModel.delete({
295
-
_id: insertResult.insertedId,
296
-
});
297
-
298
-
assertEquals(deleteResult.deletedCount, 1);
299
-
300
-
const deletedUser = await UserModel.findOne({
301
-
_id: insertResult.insertedId,
302
-
});
303
-
assertEquals(deletedUser, null);
304
-
});
305
-
306
-
it("should delete all users with empty filter", async () => {
307
-
await UserModel.insertOne({
308
-
name: "User 1",
309
-
email: "user1@test.com",
310
-
});
311
-
await UserModel.insertOne({
312
-
name: "User 2",
313
-
email: "user2@test.com",
314
-
});
315
-
316
-
const deleteResult = await UserModel.delete({});
317
-
318
-
assertEquals(deleteResult.deletedCount, 2);
319
-
320
-
const remainingUsers = await UserModel.find();
321
-
assertEquals(remainingUsers.length, 0);
322
-
});
323
-
324
-
it("should return 0 deleted count for non-existent user", async () => {
325
-
const deleteResult = await UserModel.delete({
326
-
_id: 999,
327
-
});
328
-
329
-
assertEquals(deleteResult.deletedCount, 0);
330
-
});
331
-
});
332
-
333
-
describe("Schema Validation", () => {
334
-
it("should validate user data and reject invalid email", async () => {
335
-
const invalidUser = {
336
-
name: "Invalid User",
337
-
email: "not-an-email",
338
-
age: 25,
339
-
};
340
-
341
-
await assertRejects(
342
-
async () => {
343
-
await UserModel.insertOne(invalidUser as UserInsert);
344
-
},
345
-
z.ZodError,
346
-
);
347
-
});
348
-
349
-
it("should reject negative age", async () => {
350
-
const invalidUser = {
351
-
name: "Invalid Age User",
352
-
email: "valid@example.com",
353
-
age: -5,
354
-
};
355
-
356
-
await assertRejects(
357
-
async () => {
358
-
await UserModel.insertOne(invalidUser as UserInsert);
359
-
},
360
-
z.ZodError,
361
-
);
362
-
});
363
-
364
-
it("should reject missing required fields", async () => {
365
-
const invalidUser = {
366
-
age: 25,
367
-
// Missing name and email
368
-
};
369
-
370
-
await assertRejects(
371
-
async () => {
372
-
await UserModel.insertOne(invalidUser as UserInsert);
373
-
},
374
-
z.ZodError,
375
-
);
376
-
});
377
-
});
378
-
379
-
describe("Complex Scenarios", () => {
380
-
it("should handle multiple operations in sequence", async () => {
381
-
// Insert multiple users
382
-
const user1 = await UserModel.insertOne({
383
-
name: "Alice",
384
-
email: "alice@example.com",
385
-
age: 28,
386
-
});
387
-
388
-
const user2 = await UserModel.insertOne({
389
-
name: "Bob",
390
-
email: "bob@example.com",
391
-
age: 32,
392
-
});
393
-
394
-
// Find all users
395
-
const allUsers = await UserModel.find({});
396
-
assertEquals(allUsers.length, 2);
397
-
398
-
// Update one user
399
-
await UserModel.update({ _id: user1.insertedId }, { age: 29 });
400
-
401
-
// Delete one user
402
-
await UserModel.delete({ _id: user2.insertedId });
403
-
404
-
// Verify final state
405
-
const finalUsers = await UserModel.find({});
406
-
assertEquals(finalUsers.length, 1);
407
-
assertEquals(finalUsers[0].name, "Alice");
408
-
assertEquals(finalUsers[0].age, 29);
409
-
});
410
-
411
-
it("should maintain data isolation between operations", async () => {
412
-
// This test ensures that operations don't interfere with each other
413
-
const user1 = await UserModel.insertOne({
414
-
name: "Isolation Test 1",
415
-
email: "iso1@test.com",
416
-
age: 20,
417
-
});
418
-
419
-
const user2 = await UserModel.insertOne({
420
-
name: "Isolation Test 2",
421
-
email: "iso2@test.com",
422
-
age: 30,
423
-
});
424
-
425
-
// Update user1 shouldn't affect user2
426
-
await UserModel.update({ _id: user1.insertedId }, {
427
-
name: "Updated User 1",
428
-
});
429
-
430
-
const foundUser2 = await UserModel.findOne({ _id: user2.insertedId });
431
-
assertEquals(foundUser2?.name, "Isolation Test 2"); // Should remain unchanged
432
-
});
433
-
434
-
it("should handle concurrent-like operations", async () => {
435
-
const insertPromises = [
436
-
UserModel.insertOne({ name: "Concurrent 1", email: "con1@test.com" }),
437
-
UserModel.insertOne({ name: "Concurrent 2", email: "con2@test.com" }),
438
-
UserModel.insertOne({ name: "Concurrent 3", email: "con3@test.com" }),
439
-
];
440
-
441
-
const results = await Promise.all(insertPromises);
442
-
443
-
assertEquals(results.length, 3);
444
-
results.forEach((result) => {
445
-
assertExists(result.insertedId);
446
-
});
447
-
448
-
const allUsers = await UserModel.find({});
449
-
assertEquals(allUsers.length, 3);
450
-
});
451
-
});
452
-
});
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
+47
tests/utils.ts
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
import { z } from "@zod/zod";
2
+
import { connect, disconnect, type InsertType, Model } from "../mod.ts";
3
+
import { MongoMemoryServer } from "mongodb-memory-server-core";
4
+
5
+
export const userSchema = z.object({
6
+
name: z.string(),
7
+
email: z.email(),
8
+
age: z.number().int().positive().optional(),
9
+
createdAt: z.date().default(() => new Date()),
10
+
});
11
+
12
+
export type UserInsert = InsertType<typeof userSchema>;
13
+
14
+
let mongoServer: MongoMemoryServer | null = null;
15
+
let isSetup = false;
16
+
17
+
export async function setupTestDb() {
18
+
if (!isSetup) {
19
+
// Start MongoDB Memory Server
20
+
mongoServer = await MongoMemoryServer.create();
21
+
const uri = mongoServer.getUri();
22
+
23
+
// Connect to the in-memory database
24
+
await connect(uri, "test_db");
25
+
isSetup = true;
26
+
}
27
+
}
28
+
29
+
export async function teardownTestDb() {
30
+
if (isSetup) {
31
+
await disconnect();
32
+
if (mongoServer) {
33
+
await mongoServer.stop();
34
+
mongoServer = null;
35
+
}
36
+
isSetup = false;
37
+
}
38
+
}
39
+
40
+
export function createUserModel(): Model<typeof userSchema> {
41
+
return new Model("users", userSchema);
42
+
}
43
+
44
+
export async function cleanupCollection(model: Model<typeof userSchema>) {
45
+
await model.delete({});
46
+
}
47
+
+172
tests/validation_test.ts
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
import { assertEquals, assertExists, assertRejects } from "@std/assert";
2
+
import { ObjectId } from "mongodb";
3
+
import {
4
+
cleanupCollection,
5
+
createUserModel,
6
+
setupTestDb,
7
+
teardownTestDb,
8
+
type UserInsert,
9
+
type userSchema,
10
+
} from "./utils.ts";
11
+
import type { Model } from "../mod.ts";
12
+
13
+
let UserModel: Model<typeof userSchema>;
14
+
15
+
Deno.test.beforeAll(async () => {
16
+
await setupTestDb();
17
+
UserModel = createUserModel();
18
+
});
19
+
20
+
Deno.test.beforeEach(async () => {
21
+
await cleanupCollection(UserModel);
22
+
});
23
+
24
+
Deno.test.afterAll(async () => {
25
+
await teardownTestDb();
26
+
});
27
+
28
+
Deno.test({
29
+
name: "Validation: Schema - should validate user data on insert",
30
+
async fn() {
31
+
32
+
const invalidUser = {
33
+
name: "Invalid User",
34
+
email: "not-an-email", // Invalid email
35
+
age: -5, // Negative age
36
+
};
37
+
38
+
// This should throw an error due to schema validation
39
+
await assertRejects(
40
+
async () => {
41
+
await UserModel.insertOne(invalidUser as UserInsert);
42
+
},
43
+
Error,
44
+
);
45
+
},
46
+
sanitizeResources: false,
47
+
sanitizeOps: false,
48
+
});
49
+
50
+
Deno.test({
51
+
name: "Validation: Update - should reject invalid email in update",
52
+
async fn() {
53
+
54
+
// Insert a user for this test
55
+
const newUser: UserInsert = {
56
+
name: "Validation Test User",
57
+
email: "valid@example.com",
58
+
age: 25,
59
+
};
60
+
const insertResult = await UserModel.insertOne(newUser);
61
+
assertExists(insertResult.insertedId);
62
+
63
+
// Try to update with invalid email - should throw error
64
+
await assertRejects(
65
+
async () => {
66
+
await UserModel.update(
67
+
{ _id: new ObjectId(insertResult.insertedId) },
68
+
{ email: "not-an-email" },
69
+
);
70
+
},
71
+
Error,
72
+
"Update validation failed",
73
+
);
74
+
},
75
+
sanitizeResources: false,
76
+
sanitizeOps: false,
77
+
});
78
+
79
+
Deno.test({
80
+
name: "Validation: Update - should reject negative age in update",
81
+
async fn() {
82
+
83
+
// Insert a user for this test
84
+
const newUser: UserInsert = {
85
+
name: "Age Validation Test User",
86
+
email: "age@example.com",
87
+
age: 25,
88
+
};
89
+
const insertResult = await UserModel.insertOne(newUser);
90
+
assertExists(insertResult.insertedId);
91
+
92
+
// Try to update with negative age - should throw error
93
+
await assertRejects(
94
+
async () => {
95
+
await UserModel.updateOne(
96
+
{ _id: new ObjectId(insertResult.insertedId) },
97
+
{ age: -5 },
98
+
);
99
+
},
100
+
Error,
101
+
"Update validation failed",
102
+
);
103
+
},
104
+
sanitizeResources: false,
105
+
sanitizeOps: false,
106
+
});
107
+
108
+
Deno.test({
109
+
name: "Validation: Update - should reject invalid name type in update",
110
+
async fn() {
111
+
112
+
// Insert a user for this test
113
+
const newUser: UserInsert = {
114
+
name: "Type Validation Test User",
115
+
email: "type@example.com",
116
+
age: 25,
117
+
};
118
+
const insertResult = await UserModel.insertOne(newUser);
119
+
assertExists(insertResult.insertedId);
120
+
121
+
// Try to update with invalid name type (number instead of string) - should throw error
122
+
await assertRejects(
123
+
async () => {
124
+
await UserModel.updateOne(
125
+
{ _id: new ObjectId(insertResult.insertedId) },
126
+
{ name: 123 as unknown as string },
127
+
);
128
+
},
129
+
Error,
130
+
"Update validation failed",
131
+
);
132
+
},
133
+
sanitizeResources: false,
134
+
sanitizeOps: false,
135
+
});
136
+
137
+
Deno.test({
138
+
name: "Validation: Update - should accept valid partial updates",
139
+
async fn() {
140
+
141
+
// Insert a user for this test
142
+
const newUser: UserInsert = {
143
+
name: "Valid Update Test User",
144
+
email: "validupdate@example.com",
145
+
age: 25,
146
+
};
147
+
const insertResult = await UserModel.insertOne(newUser);
148
+
assertExists(insertResult.insertedId);
149
+
150
+
// Update with valid data - should succeed
151
+
const updateResult = await UserModel.updateOne(
152
+
{ _id: new ObjectId(insertResult.insertedId) },
153
+
{ age: 30, email: "newemail@example.com" },
154
+
);
155
+
156
+
assertEquals(updateResult.modifiedCount, 1);
157
+
158
+
// Verify the update
159
+
const updatedUser = await UserModel.findOne({
160
+
_id: new ObjectId(insertResult.insertedId),
161
+
});
162
+
163
+
assertExists(updatedUser);
164
+
assertEquals(updatedUser.age, 30);
165
+
assertEquals(updatedUser.email, "newemail@example.com");
166
+
assertEquals(updatedUser.name, "Valid Update Test User"); // Should remain unchanged
167
+
},
168
+
sanitizeResources: false,
169
+
sanitizeOps: false,
170
+
});
171
+
172
+