Thin MongoDB ODM built for Standard Schema
mongodb zod deno
at main 8.4 kB view raw
1import { assertEquals, assertExists, assertRejects } from "@std/assert"; 2import { 3 connect, 4 disconnect, 5 endSession, 6 Model, 7 startSession, 8 withTransaction, 9} from "../mod.ts"; 10import { z } from "@zod/zod"; 11import { MongoMemoryReplSet } from "mongodb-memory-server-core"; 12 13let replSet: MongoMemoryReplSet | null = null; 14 15async function setupTestReplSet() { 16 if (!replSet) { 17 replSet = await MongoMemoryReplSet.create({ 18 replSet: { 19 count: 1, 20 storageEngine: "wiredTiger", // Required for transactions 21 }, 22 }); 23 } 24 return replSet.getUri(); 25} 26 27Deno.test.afterEach(async () => { 28 // Clean up database 29 if (replSet) { 30 try { 31 const { getDb } = await import("../client/connection.ts"); 32 const db = getDb(); 33 await db.dropDatabase(); 34 } catch { 35 // Ignore if not connected 36 } 37 } 38 await disconnect(); 39}); 40 41Deno.test.afterAll(async () => { 42 if (replSet) { 43 await replSet.stop(); 44 replSet = null; 45 } 46}); 47 48// Test schemas 49const userSchema = z.object({ 50 name: z.string().min(1), 51 email: z.string().email(), 52 balance: z.number().nonnegative().default(0), 53}); 54 55const orderSchema = z.object({ 56 userId: z.string(), 57 amount: z.number().positive(), 58 status: z.enum(["pending", "completed", "failed"]).default("pending"), 59}); 60 61Deno.test({ 62 name: "Transactions: withTransaction - should commit successful operations", 63 async fn() { 64 const uri = await setupTestReplSet(); 65 await connect(uri, "test_db"); 66 67 const UserModel = new Model("users", userSchema); 68 const OrderModel = new Model("orders", orderSchema); 69 70 const result = await withTransaction(async (session) => { 71 const user = await UserModel.insertOne( 72 { name: "Alice", email: "alice@example.com", balance: 100 }, 73 { session }, 74 ); 75 76 const order = await OrderModel.insertOne( 77 { userId: user.insertedId.toString(), amount: 50 }, 78 { session }, 79 ); 80 81 return { userId: user.insertedId, orderId: order.insertedId }; 82 }); 83 84 assertExists(result.userId); 85 assertExists(result.orderId); 86 87 // Verify data was committed 88 const users = await UserModel.find({}); 89 const orders = await OrderModel.find({}); 90 assertEquals(users.length, 1); 91 assertEquals(orders.length, 1); 92 }, 93 sanitizeResources: false, 94 sanitizeOps: false, 95}); 96 97Deno.test({ 98 name: "Transactions: withTransaction - should abort on error", 99 async fn() { 100 const uri = await setupTestReplSet(); 101 await connect(uri, "test_db"); 102 103 const UserModel = new Model("users", userSchema); 104 105 await assertRejects( 106 async () => { 107 await withTransaction(async (session) => { 108 await UserModel.insertOne( 109 { name: "Bob", email: "bob@example.com" }, 110 { session }, 111 ); 112 113 // This will fail and abort the transaction 114 throw new Error("Simulated error"); 115 }); 116 }, 117 Error, 118 "Simulated error", 119 ); 120 121 // Verify no data was committed 122 const users = await UserModel.find({}); 123 assertEquals(users.length, 0); 124 }, 125 sanitizeResources: false, 126 sanitizeOps: false, 127}); 128 129Deno.test({ 130 name: "Transactions: withTransaction - should handle multiple operations", 131 async fn() { 132 const uri = await setupTestReplSet(); 133 await connect(uri, "test_db"); 134 135 const UserModel = new Model("users", userSchema); 136 137 const result = await withTransaction(async (session) => { 138 const users = []; 139 140 for (let i = 0; i < 5; i++) { 141 const user = await UserModel.insertOne( 142 { name: `User${i}`, email: `user${i}@example.com` }, 143 { session }, 144 ); 145 users.push(user.insertedId); 146 } 147 148 return users; 149 }); 150 151 assertEquals(result.length, 5); 152 153 // Verify all users were created 154 const users = await UserModel.find({}); 155 assertEquals(users.length, 5); 156 }, 157 sanitizeResources: false, 158 sanitizeOps: false, 159}); 160 161Deno.test({ 162 name: 163 "Transactions: withTransaction - should support read and write operations", 164 async fn() { 165 const uri = await setupTestReplSet(); 166 await connect(uri, "test_db"); 167 168 const UserModel = new Model("users", userSchema); 169 170 // Insert initial user 171 const initialUser = await UserModel.insertOne({ 172 name: "Charlie", 173 email: "charlie@example.com", 174 balance: 100, 175 }); 176 177 const result = await withTransaction(async (session) => { 178 // Read 179 const user = await UserModel.findById(initialUser.insertedId, { 180 session, 181 }); 182 assertExists(user); 183 184 // Update 185 await UserModel.updateOne( 186 { _id: initialUser.insertedId }, 187 { balance: 150 }, 188 { session }, 189 ); 190 191 // Read again 192 const updatedUser = await UserModel.findById(initialUser.insertedId, { 193 session, 194 }); 195 196 return updatedUser?.balance; 197 }); 198 199 assertEquals(result, 150); 200 }, 201 sanitizeResources: false, 202 sanitizeOps: false, 203}); 204 205Deno.test({ 206 name: "Transactions: withTransaction - should handle validation errors", 207 async fn() { 208 const uri = await setupTestReplSet(); 209 await connect(uri, "test_db"); 210 211 const UserModel = new Model("users", userSchema); 212 213 await assertRejects( 214 async () => { 215 await withTransaction(async (session) => { 216 // Valid insert 217 await UserModel.insertOne( 218 { name: "Valid", email: "valid@example.com" }, 219 { session }, 220 ); 221 222 // Invalid insert (will throw ValidationError) 223 await UserModel.insertOne( 224 { name: "", email: "invalid" }, 225 { session }, 226 ); 227 }); 228 }, 229 Error, // ValidationError 230 ); 231 232 // Transaction should have been aborted, no data should exist 233 const users = await UserModel.find({}); 234 assertEquals(users.length, 0); 235 }, 236 sanitizeResources: false, 237 sanitizeOps: false, 238}); 239 240Deno.test({ 241 name: 242 "Transactions: Manual session - should work with manual session management", 243 async fn() { 244 const uri = await setupTestReplSet(); 245 await connect(uri, "test_db"); 246 247 const UserModel = new Model("users", userSchema); 248 249 const session = startSession(); 250 251 try { 252 await session.withTransaction(async () => { 253 await UserModel.insertOne( 254 { name: "Dave", email: "dave@example.com" }, 255 { session }, 256 ); 257 await UserModel.insertOne( 258 { name: "Eve", email: "eve@example.com" }, 259 { session }, 260 ); 261 }); 262 } finally { 263 await endSession(session); 264 } 265 266 // Verify both users were created 267 const users = await UserModel.find({}); 268 assertEquals(users.length, 2); 269 }, 270 sanitizeResources: false, 271 sanitizeOps: false, 272}); 273 274Deno.test({ 275 name: "Transactions: withTransaction - should support delete operations", 276 async fn() { 277 const uri = await setupTestReplSet(); 278 await connect(uri, "test_db"); 279 280 const UserModel = new Model("users", userSchema); 281 282 // Insert initial users 283 await UserModel.insertMany([ 284 { name: "User1", email: "user1@example.com" }, 285 { name: "User2", email: "user2@example.com" }, 286 { name: "User3", email: "user3@example.com" }, 287 ]); 288 289 await withTransaction(async (session) => { 290 // Delete one user 291 await UserModel.deleteOne({ name: "User1" }, { session }); 292 293 // Delete multiple users 294 await UserModel.delete({ name: { $in: ["User2", "User3"] } }, { 295 session, 296 }); 297 }); 298 299 // Verify all were deleted 300 const users = await UserModel.find({}); 301 assertEquals(users.length, 0); 302 }, 303 sanitizeResources: false, 304 sanitizeOps: false, 305}); 306 307Deno.test({ 308 name: "Transactions: withTransaction - should handle transaction options", 309 async fn() { 310 const uri = await setupTestReplSet(); 311 await connect(uri, "test_db"); 312 313 const UserModel = new Model("users", userSchema); 314 315 const result = await withTransaction( 316 async (session) => { 317 await UserModel.insertOne( 318 { name: "Frank", email: "frank@example.com" }, 319 { session }, 320 ); 321 return "success"; 322 }, 323 { 324 readPreference: "primary", 325 readConcern: { level: "snapshot" }, 326 writeConcern: { w: "majority" }, 327 }, 328 ); 329 330 assertEquals(result, "success"); 331 332 const users = await UserModel.find({}); 333 assertEquals(users.length, 1); 334 }, 335 sanitizeResources: false, 336 sanitizeOps: false, 337});