Thin MongoDB ODM built for Standard Schema
mongodb
zod
deno
1import { assert, assertEquals, assertExists, assertRejects } from "@std/assert";
2import {
3 connect,
4 disconnect,
5 Model,
6 ValidationError,
7 ConnectionError,
8} from "../mod.ts";
9import { z } from "@zod/zod";
10import { MongoMemoryServer } from "mongodb-memory-server-core";
11
12let mongoServer: MongoMemoryServer | null = null;
13
14async function setupTestServer() {
15 if (!mongoServer) {
16 mongoServer = await MongoMemoryServer.create();
17 }
18 return mongoServer.getUri();
19}
20
21Deno.test.afterEach(async () => {
22 await disconnect();
23});
24
25Deno.test.afterAll(async () => {
26 if (mongoServer) {
27 await mongoServer.stop();
28 mongoServer = null;
29 }
30});
31
32// Test schemas
33const userSchema = z.object({
34 name: z.string().min(1),
35 email: z.string().email(),
36 age: z.number().int().positive().optional(),
37});
38
39Deno.test({
40 name: "Errors: ValidationError - should throw on invalid insert",
41 async fn() {
42 const uri = await setupTestServer();
43 await connect(uri, "test_db");
44
45 const UserModel = new Model("users", userSchema);
46
47 await assertRejects(
48 async () => {
49 await UserModel.insertOne({ name: "", email: "invalid" });
50 },
51 ValidationError,
52 "Validation failed on insert"
53 );
54 },
55 sanitizeResources: false,
56 sanitizeOps: false,
57});
58
59Deno.test({
60 name: "Errors: ValidationError - should have structured issues",
61 async fn() {
62 const uri = await setupTestServer();
63 await connect(uri, "test_db");
64
65 const UserModel = new Model("users", userSchema);
66
67 try {
68 await UserModel.insertOne({ name: "", email: "invalid" });
69 throw new Error("Should have thrown ValidationError");
70 } catch (error) {
71 assert(error instanceof ValidationError);
72 assertEquals(error.operation, "insert");
73 assertExists(error.issues);
74 assert(error.issues.length > 0);
75
76 // Check field errors
77 const fieldErrors = error.getFieldErrors();
78 assertExists(fieldErrors.name);
79 assertExists(fieldErrors.email);
80 }
81 },
82 sanitizeResources: false,
83 sanitizeOps: false,
84});
85
86Deno.test({
87 name: "Errors: ValidationError - should throw on invalid update",
88 async fn() {
89 const uri = await setupTestServer();
90 await connect(uri, "test_db");
91
92 const UserModel = new Model("users", userSchema);
93
94 await assertRejects(
95 async () => {
96 await UserModel.updateOne({ name: "test" }, { email: "invalid-email" });
97 },
98 ValidationError,
99 "Validation failed on update"
100 );
101 },
102 sanitizeResources: false,
103 sanitizeOps: false,
104});
105
106Deno.test({
107 name: "Errors: ValidationError - should throw on invalid replace",
108 async fn() {
109 const uri = await setupTestServer();
110 await connect(uri, "test_db");
111
112 const UserModel = new Model("users", userSchema);
113
114 // First insert a valid document
115 await UserModel.insertOne({ name: "Test", email: "test@example.com" });
116
117 await assertRejects(
118 async () => {
119 await UserModel.replaceOne({ name: "Test" }, { name: "", email: "invalid" });
120 },
121 ValidationError,
122 "Validation failed on replace"
123 );
124 },
125 sanitizeResources: false,
126 sanitizeOps: false,
127});
128
129Deno.test({
130 name: "Errors: ValidationError - update operation should be in error",
131 async fn() {
132 const uri = await setupTestServer();
133 await connect(uri, "test_db");
134
135 const UserModel = new Model("users", userSchema);
136
137 try {
138 await UserModel.updateOne({ name: "test" }, { age: -5 });
139 throw new Error("Should have thrown ValidationError");
140 } catch (error) {
141 assert(error instanceof ValidationError);
142 assertEquals(error.operation, "update");
143
144 const fieldErrors = error.getFieldErrors();
145 assertExists(fieldErrors.age);
146 }
147 },
148 sanitizeResources: false,
149 sanitizeOps: false,
150});
151
152Deno.test({
153 name: "Errors: ConnectionError - should throw on connection failure",
154 async fn() {
155 await assertRejects(
156 async () => {
157 await connect("mongodb://invalid-host-that-does-not-exist:27017", "test_db", {
158 serverSelectionTimeoutMS: 1000, // 1 second timeout
159 connectTimeoutMS: 1000,
160 });
161 },
162 ConnectionError,
163 "Failed to connect to MongoDB"
164 );
165 },
166 sanitizeResources: false,
167 sanitizeOps: false,
168});
169
170Deno.test({
171 name: "Errors: ConnectionError - should include URI in error",
172 async fn() {
173 try {
174 await connect("mongodb://invalid-host-that-does-not-exist:27017", "test_db", {
175 serverSelectionTimeoutMS: 1000, // 1 second timeout
176 connectTimeoutMS: 1000,
177 });
178 throw new Error("Should have thrown ConnectionError");
179 } catch (error) {
180 assert(error instanceof ConnectionError);
181 assertEquals(error.uri, "mongodb://invalid-host-that-does-not-exist:27017");
182 }
183 },
184 sanitizeResources: false,
185 sanitizeOps: false,
186});
187
188Deno.test({
189 name: "Errors: ConnectionError - should throw when getDb called without connection",
190 async fn() {
191 // Make sure not connected
192 await disconnect();
193
194 const { getDb } = await import("../client/connection.ts");
195
196 try {
197 getDb();
198 throw new Error("Should have thrown ConnectionError");
199 } catch (error) {
200 assert(error instanceof ConnectionError);
201 assert(error.message.includes("not connected"));
202 }
203 },
204 sanitizeResources: false,
205 sanitizeOps: false,
206});
207
208Deno.test({
209 name: "Errors: ValidationError - field errors should be grouped correctly",
210 async fn() {
211 const uri = await setupTestServer();
212 await connect(uri, "test_db");
213
214 const UserModel = new Model("users", userSchema);
215
216 try {
217 await UserModel.insertOne({
218 name: "",
219 email: "not-an-email",
220 age: -10,
221 });
222 throw new Error("Should have thrown ValidationError");
223 } catch (error) {
224 assert(error instanceof ValidationError);
225
226 const fieldErrors = error.getFieldErrors();
227
228 // Each field should have its own errors
229 assert(Array.isArray(fieldErrors.name));
230 assert(Array.isArray(fieldErrors.email));
231 assert(Array.isArray(fieldErrors.age));
232
233 // Verify error messages are present
234 assert(fieldErrors.name.length > 0);
235 assert(fieldErrors.email.length > 0);
236 assert(fieldErrors.age.length > 0);
237 }
238 },
239 sanitizeResources: false,
240 sanitizeOps: false,
241});
242
243Deno.test({
244 name: "Errors: Error name should be set correctly",
245 async fn() {
246 const uri = await setupTestServer();
247 await connect(uri, "test_db");
248
249 const UserModel = new Model("users", userSchema);
250
251 try {
252 await UserModel.insertOne({ name: "", email: "invalid" });
253 } catch (error) {
254 assert(error instanceof ValidationError);
255 assertEquals(error.name, "ValidationError");
256 }
257 },
258 sanitizeResources: false,
259 sanitizeOps: false,
260});