···
1
+
import { assertEquals, assertExists } from "@std/assert";
2
+
import { z } from "@zod/zod";
3
+
import { connect, disconnect, Model, type Input } from "../mod.ts";
4
+
import { applyDefaultsForUpsert } from "../model/validation.ts";
5
+
import { MongoMemoryServer } from "mongodb-memory-server-core";
8
+
* Test suite for default value handling in different operation types
10
+
* This tests the three main cases:
11
+
* 1. Plain inserts - defaults applied directly
12
+
* 2. Updates without upsert - defaults NOT applied
13
+
* 3. Upserts that create - defaults applied via $setOnInsert
14
+
* 4. Upserts that match - $setOnInsert ignored (correct behavior)
15
+
* 5. Replace with upsert - defaults applied on creation
18
+
// Schema with defaults for testing
19
+
const productSchema = z.object({
21
+
price: z.number().min(0),
22
+
category: z.string().default("general"),
23
+
inStock: z.boolean().default(true),
24
+
createdAt: z.date().default(() => new Date("2024-01-01T00:00:00Z")),
25
+
tags: z.array(z.string()).default([]),
28
+
type Product = z.infer<typeof productSchema>;
29
+
type ProductInsert = Input<typeof productSchema>;
31
+
let ProductModel: Model<typeof productSchema>;
32
+
let mongoServer: MongoMemoryServer;
34
+
Deno.test.beforeAll(async () => {
35
+
mongoServer = await MongoMemoryServer.create();
36
+
const uri = mongoServer.getUri();
37
+
await connect(uri, "test_defaults_db");
38
+
ProductModel = new Model("test_products_defaults", productSchema);
41
+
Deno.test.beforeEach(async () => {
42
+
await ProductModel.delete({});
45
+
Deno.test.afterAll(async () => {
46
+
await ProductModel.delete({});
48
+
await mongoServer.stop();
52
+
name: "Defaults: Case 1 - Plain insert applies defaults",
54
+
// Insert without providing optional fields with defaults
55
+
const result = await ProductModel.insertOne({
58
+
// category, inStock, createdAt, tags not provided - should use defaults
61
+
assertExists(result.insertedId);
63
+
// Verify defaults were applied
64
+
const product = await ProductModel.findById(result.insertedId);
65
+
assertExists(product);
67
+
assertEquals(product.name, "Widget");
68
+
assertEquals(product.price, 29.99);
69
+
assertEquals(product.category, "general"); // default
70
+
assertEquals(product.inStock, true); // default
71
+
assertExists(product.createdAt); // default function called
72
+
assertEquals(product.tags, []); // default empty array
74
+
sanitizeResources: false,
79
+
name: "Defaults: Case 2 - Update without upsert does NOT apply defaults",
81
+
// First create a document without defaults (simulate old data)
82
+
const insertResult = await ProductModel.insertOne({
85
+
category: "electronics",
87
+
createdAt: new Date("2023-01-01"),
91
+
assertExists(insertResult.insertedId);
93
+
// Now update it - defaults should NOT be applied
94
+
await ProductModel.updateOne(
95
+
{ _id: insertResult.insertedId },
100
+
const updated = await ProductModel.findById(insertResult.insertedId);
101
+
assertExists(updated);
103
+
assertEquals(updated.price, 24.99); // updated
104
+
assertEquals(updated.category, "electronics"); // unchanged
105
+
assertEquals(updated.inStock, false); // unchanged
106
+
assertEquals(updated.tags, ["test"]); // unchanged
108
+
sanitizeResources: false,
109
+
sanitizeOps: false,
113
+
name: "Defaults: Case 3 - Upsert that creates applies defaults via $setOnInsert",
115
+
// Upsert with a query that won't match - will create new document
116
+
const result = await ProductModel.updateOne(
117
+
{ name: "NonExistent" },
122
+
assertEquals(result.upsertedCount, 1);
123
+
assertExists(result.upsertedId);
125
+
// Verify the created document has defaults applied
126
+
const product = await ProductModel.findOne({ name: "NonExistent" });
127
+
assertExists(product);
129
+
assertEquals(product.price, 39.99); // from $set
130
+
assertEquals(product.name, "NonExistent"); // from query
131
+
assertEquals(product.category, "general"); // default via $setOnInsert
132
+
assertEquals(product.inStock, true); // default via $setOnInsert
133
+
assertExists(product.createdAt); // default via $setOnInsert
134
+
assertEquals(product.tags, []); // default via $setOnInsert
136
+
sanitizeResources: false,
137
+
sanitizeOps: false,
141
+
name: "Defaults: Case 4 - Upsert that matches does NOT apply defaults",
143
+
// Create a document first with explicit non-default values
144
+
const insertResult = await ProductModel.insertOne({
145
+
name: "ExistingProduct",
147
+
category: "premium",
149
+
createdAt: new Date("2023-06-01"),
150
+
tags: ["premium", "featured"],
153
+
assertExists(insertResult.insertedId);
155
+
// Upsert with matching query - should update, not insert
156
+
const result = await ProductModel.updateOne(
157
+
{ name: "ExistingProduct" },
162
+
assertEquals(result.matchedCount, 1);
163
+
assertEquals(result.modifiedCount, 1);
164
+
assertEquals(result.upsertedCount, 0); // No insert happened
166
+
// Verify defaults were NOT applied (existing values preserved)
167
+
const product = await ProductModel.findOne({ name: "ExistingProduct" });
168
+
assertExists(product);
170
+
assertEquals(product.price, 44.99); // updated via $set
171
+
assertEquals(product.category, "premium"); // preserved (not overwritten with default)
172
+
assertEquals(product.inStock, false); // preserved
173
+
assertEquals(product.tags, ["premium", "featured"]); // preserved
175
+
sanitizeResources: false,
176
+
sanitizeOps: false,
180
+
name: "Defaults: Case 5 - Replace without upsert uses defaults from parse",
182
+
// Create initial document
183
+
const insertResult = await ProductModel.insertOne({
188
+
createdAt: new Date("2020-01-01"),
192
+
assertExists(insertResult.insertedId);
194
+
// Replace with partial data - defaults should fill in missing fields
195
+
await ProductModel.replaceOne(
196
+
{ _id: insertResult.insertedId },
200
+
// category, inStock, createdAt, tags not provided - defaults should apply
204
+
const product = await ProductModel.findById(insertResult.insertedId);
205
+
assertExists(product);
207
+
assertEquals(product.name, "Replaced");
208
+
assertEquals(product.price, 15.0);
209
+
assertEquals(product.category, "general"); // default applied
210
+
assertEquals(product.inStock, true); // default applied
211
+
assertExists(product.createdAt); // default applied
212
+
assertEquals(product.tags, []); // default applied
214
+
sanitizeResources: false,
215
+
sanitizeOps: false,
219
+
name: "Defaults: Case 6 - Replace with upsert (creates) applies defaults",
221
+
// Replace with upsert on non-existent document
222
+
const result = await ProductModel.replaceOne(
223
+
{ name: "NewViaReplace" },
225
+
name: "NewViaReplace",
227
+
// Missing optional fields - defaults should apply
232
+
assertEquals(result.upsertedCount, 1);
233
+
assertExists(result.upsertedId);
235
+
const product = await ProductModel.findOne({ name: "NewViaReplace" });
236
+
assertExists(product);
238
+
assertEquals(product.name, "NewViaReplace");
239
+
assertEquals(product.price, 99.99);
240
+
assertEquals(product.category, "general"); // default
241
+
assertEquals(product.inStock, true); // default
242
+
assertExists(product.createdAt); // default
243
+
assertEquals(product.tags, []); // default
245
+
sanitizeResources: false,
246
+
sanitizeOps: false,
250
+
name: "Defaults: Upsert only applies defaults to unmodified fields",
252
+
// Upsert where we explicitly set some fields that have defaults
253
+
const result = await ProductModel.updateOne(
254
+
{ name: "CustomDefaults" },
257
+
category: "custom", // Explicitly setting a field that has a default
258
+
// inStock not set - should get default
263
+
assertEquals(result.upsertedCount, 1);
265
+
const product = await ProductModel.findOne({ name: "CustomDefaults" });
266
+
assertExists(product);
268
+
assertEquals(product.name, "CustomDefaults"); // from query
269
+
assertEquals(product.price, 25.0); // from $set
270
+
assertEquals(product.category, "custom"); // from $set (NOT default)
271
+
assertEquals(product.inStock, true); // default via $setOnInsert
272
+
assertExists(product.createdAt); // default via $setOnInsert
273
+
assertEquals(product.tags, []); // default via $setOnInsert
275
+
sanitizeResources: false,
276
+
sanitizeOps: false,
280
+
name: "Defaults: insertMany applies defaults to all documents",
282
+
const result = await ProductModel.insertMany([
283
+
{ name: "Bulk1", price: 10 },
284
+
{ name: "Bulk2", price: 20, category: "special" },
285
+
{ name: "Bulk3", price: 30 },
288
+
assertEquals(Object.keys(result.insertedIds).length, 3);
290
+
const products = await ProductModel.find({});
291
+
assertEquals(products.length, 3);
293
+
// All should have defaults where not provided
294
+
for (const product of products) {
295
+
assertExists(product.createdAt);
296
+
assertEquals(product.inStock, true);
297
+
assertEquals(product.tags, []);
299
+
if (product.name === "Bulk2") {
300
+
assertEquals(product.category, "special");
302
+
assertEquals(product.category, "general");
306
+
sanitizeResources: false,
307
+
sanitizeOps: false,
311
+
name: "Defaults: applyDefaultsForUpsert preserves existing $setOnInsert values",
313
+
const schema = z.object({
315
+
flag: z.boolean().default(true),
316
+
count: z.number().default(0),
320
+
$set: { name: "test" },
321
+
$setOnInsert: { flag: false },
324
+
const result = applyDefaultsForUpsert(schema, {}, update);
326
+
assertEquals(result.$setOnInsert?.flag, false);
327
+
assertEquals(result.$setOnInsert?.count, 0);
329
+
sanitizeResources: false,
330
+
sanitizeOps: false,
334
+
name: "Defaults: applyDefaultsForUpsert keeps query equality fields untouched",
336
+
const schema = z.object({
337
+
status: z.string().default("pending"),
338
+
flag: z.boolean().default(true),
342
+
const query = { status: "queued" };
343
+
const update = { $set: { name: "upsert-test" } };
345
+
const result = applyDefaultsForUpsert(schema, query, update);
347
+
assertEquals(result.$setOnInsert?.status, undefined);
348
+
assertEquals(result.$setOnInsert?.flag, true);
350
+
sanitizeResources: false,
351
+
sanitizeOps: false,