monastery 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +1 -1
- package/changelog.md +2 -0
- package/docs/readme.md +4 -2
- package/lib/index.js +14 -2
- package/lib/model-crud.js +125 -88
- package/lib/model-validate.js +131 -97
- package/lib/model.js +15 -0
- package/lib/util.js +101 -72
- package/package.json +1 -1
- package/test/crud.js +128 -1
- package/test/util.js +237 -22
- package/test/validate.js +87 -3
package/test/crud.js
CHANGED
|
@@ -663,7 +663,7 @@ test('update operators', async () => {
|
|
|
663
663
|
expect(beforeValidateHookCalled).toEqual(false)
|
|
664
664
|
})
|
|
665
665
|
|
|
666
|
-
test('update mixing
|
|
666
|
+
test('update mixing data structures (bracket notation and objects)', async() => {
|
|
667
667
|
// Mixing data
|
|
668
668
|
let consignment = db.model('consignment', {
|
|
669
669
|
fields: {
|
|
@@ -1373,4 +1373,131 @@ test('hooks > async and next conflict', async () => {
|
|
|
1373
1373
|
expect(logSpy).toHaveBeenCalledWith('Monastery user.afterFind error: you cannot return a promise AND call next()')
|
|
1374
1374
|
|
|
1375
1375
|
db2.close()
|
|
1376
|
+
})
|
|
1377
|
+
|
|
1378
|
+
test('hooks > option skipHooks', async () => {
|
|
1379
|
+
let user = db.model('user_crud_skiphooks', {
|
|
1380
|
+
fields: {
|
|
1381
|
+
name: { type: 'string' },
|
|
1382
|
+
},
|
|
1383
|
+
beforeInsert: [async (data) => {
|
|
1384
|
+
data.name = 'Hooked ' + data.name
|
|
1385
|
+
return data
|
|
1386
|
+
}],
|
|
1387
|
+
beforeUpdate: [async (data) => {
|
|
1388
|
+
data.name = 'Hooked ' + data.name
|
|
1389
|
+
return data
|
|
1390
|
+
}],
|
|
1391
|
+
})
|
|
1392
|
+
|
|
1393
|
+
// Insert with hooks
|
|
1394
|
+
let inserted = await user.insert({ data: { name: 'Martin Luther' } })
|
|
1395
|
+
expect(inserted).toEqual({
|
|
1396
|
+
_id: expect.any(Object),
|
|
1397
|
+
name: 'Hooked Martin Luther',
|
|
1398
|
+
})
|
|
1399
|
+
|
|
1400
|
+
// Update with hooks (use $set in this case)
|
|
1401
|
+
let updated = await user.update({
|
|
1402
|
+
query: inserted._id,
|
|
1403
|
+
$set: { name: 'Updated Name' },
|
|
1404
|
+
})
|
|
1405
|
+
expect(updated).toEqual({
|
|
1406
|
+
name: 'Hooked Updated Name',
|
|
1407
|
+
})
|
|
1408
|
+
|
|
1409
|
+
// Insert with skipHooks option
|
|
1410
|
+
let insertedSkipHooks = await user.insert({
|
|
1411
|
+
data: { name: 'Skipped Hooks Name' },
|
|
1412
|
+
skipHooks: true,
|
|
1413
|
+
})
|
|
1414
|
+
expect(insertedSkipHooks).toEqual({
|
|
1415
|
+
_id: expect.any(Object),
|
|
1416
|
+
name: 'Skipped Hooks Name',
|
|
1417
|
+
})
|
|
1418
|
+
|
|
1419
|
+
// Update with skipHooks option
|
|
1420
|
+
let updatedSkipHooks = await user.update({
|
|
1421
|
+
query: inserted._id,
|
|
1422
|
+
$set: { name: 'Skipped Hooks Name Updated' },
|
|
1423
|
+
skipHooks: true,
|
|
1424
|
+
})
|
|
1425
|
+
expect(updatedSkipHooks).toEqual({
|
|
1426
|
+
name: 'Skipped Hooks Name Updated',
|
|
1427
|
+
})
|
|
1428
|
+
})
|
|
1429
|
+
|
|
1430
|
+
test('update set and unset with option skipValidation', async () => {
|
|
1431
|
+
const db2 = monastery('127.0.0.1/monastery', { timestamps: false })
|
|
1432
|
+
let user = db2.model('user_set_and_unset', {
|
|
1433
|
+
fields: {
|
|
1434
|
+
profile: {
|
|
1435
|
+
name: { type: 'string', required: true },
|
|
1436
|
+
age: { type: 'number' },
|
|
1437
|
+
},
|
|
1438
|
+
},
|
|
1439
|
+
})
|
|
1440
|
+
|
|
1441
|
+
// Insert a document to update
|
|
1442
|
+
let inserted = await user.insert({ data: { profile: { name: 'John Doe', age: 30 } } })
|
|
1443
|
+
let userId = inserted._id
|
|
1444
|
+
let error = (detail, title) => [{
|
|
1445
|
+
detail: detail,
|
|
1446
|
+
title: title || expect.any(String),
|
|
1447
|
+
meta: expect.any(Object),
|
|
1448
|
+
status: '400',
|
|
1449
|
+
}]
|
|
1450
|
+
|
|
1451
|
+
// --- $set/data ---
|
|
1452
|
+
|
|
1453
|
+
// $set with skipValidation: undefined (true)
|
|
1454
|
+
const u1 = { query: userId, $set: { 'profile.age': 'not a number' } }
|
|
1455
|
+
await expect(user.update(u1)).resolves.toEqual({ 'profile.age': 'not a number' })
|
|
1456
|
+
await expect(user.findOne(userId)).resolves.toEqual({ _id: userId, profile: { name: 'John Doe', age: 'not a number' } })
|
|
1457
|
+
|
|
1458
|
+
// data with skipValidation: undefined (false)
|
|
1459
|
+
const u11 = { query: userId, data: { 'profile.age': 'not a number' } }
|
|
1460
|
+
await expect(user.update(u11)).rejects.toEqual(error('Value was not a number.', 'profile.age'))
|
|
1461
|
+
|
|
1462
|
+
for (let key of ['$set', 'data']) {
|
|
1463
|
+
// $set with skipValidation: true
|
|
1464
|
+
const u2 = { query: userId, [key]: { 'profile.age': 'not a number2' }, skipValidation: true }
|
|
1465
|
+
await expect(user.update(u2)).resolves.toEqual({ 'profile.age': 'not a number2' })
|
|
1466
|
+
|
|
1467
|
+
// $set with skipValidation: false
|
|
1468
|
+
const u3 = { query: userId, [key]: { 'profile.age': '8' }, skipValidation: false }
|
|
1469
|
+
await expect(user.update(u3)).resolves.toEqual({ 'profile.age': 8 })
|
|
1470
|
+
await expect(user.findOne(userId)).resolves.toEqual({ _id: userId, profile: { name: 'John Doe', age: 8 } })
|
|
1471
|
+
|
|
1472
|
+
// $set error with skipValidation: false
|
|
1473
|
+
const u4 = { query: userId, [key]: { 'profile.age': 'not a number' }, skipValidation: false }
|
|
1474
|
+
await expect(user.update(u4)).rejects.toEqual(error('Value was not a number.', 'profile.age'))
|
|
1475
|
+
|
|
1476
|
+
// $set error with skipValidation: ['profile']
|
|
1477
|
+
const u6 = { query: userId, [key]: { 'profile.age': 'not a number4' }, skipValidation: ['profile'] }
|
|
1478
|
+
await expect(user.update(u6)).resolves.toEqual({ 'profile.age': 'not a number4' })
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// --- $unset ---
|
|
1482
|
+
|
|
1483
|
+
// $unset with skipValidation: undefined
|
|
1484
|
+
const u7 = { query: userId, $unset: { 'profile.age': '' } }
|
|
1485
|
+
await expect(user.update(u7)).resolves.toEqual({}) // returns empty object
|
|
1486
|
+
await expect(user.findOne(userId)).resolves.toEqual({ _id: userId, profile: { name: 'John Doe' } })
|
|
1487
|
+
|
|
1488
|
+
// $unset error with skipValidation: false
|
|
1489
|
+
const u9 = { query: userId, $unset: { 'profile.name': '' }, skipValidation: false }
|
|
1490
|
+
await expect(user.update(u9)).rejects.toEqual(error('This field is required.', 'profile.name'))
|
|
1491
|
+
await expect(user.findOne(userId)).resolves.toEqual({ _id: userId, profile: { name: 'John Doe' } })
|
|
1492
|
+
|
|
1493
|
+
// $unset error with skipValidation: ['profile.name']
|
|
1494
|
+
const u10 = { query: userId, $unset: { 'profile.name': '' }, skipValidation: ['profile.name'] }
|
|
1495
|
+
await expect(user.update(u10)).resolves.toEqual({}) // returns empty object
|
|
1496
|
+
await expect(user.findOne(userId)).resolves.toEqual({ _id: userId, profile: {} }) /////////unseeeet
|
|
1497
|
+
|
|
1498
|
+
// $unset with skipValidation: true
|
|
1499
|
+
await expect(user.update({ query: userId, $set: { 'profile.name': 'John Doe2' } })) // add age back
|
|
1500
|
+
const u8 = { query: userId, $unset: { 'profile.name': '' }, skipValidation: true }
|
|
1501
|
+
await expect(user.update(u8)).resolves.toEqual({})
|
|
1502
|
+
await expect(user.findOne(userId)).resolves.toEqual({ _id: userId, profile: {} })
|
|
1376
1503
|
})
|
package/test/util.js
CHANGED
|
@@ -1,32 +1,247 @@
|
|
|
1
1
|
const util = require('../lib/util.js')
|
|
2
2
|
const monastery = require('../lib/index.js')
|
|
3
3
|
|
|
4
|
-
test('util >
|
|
5
|
-
|
|
4
|
+
test('util > parseDotNotation', async () => {
|
|
5
|
+
const input = {
|
|
6
6
|
'name': 'Martin',
|
|
7
|
-
'
|
|
8
|
-
|
|
9
|
-
'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
'deep.companyLogo1': 'a',
|
|
8
|
+
// not dot notation
|
|
9
|
+
'specialInstructions': [
|
|
10
|
+
{
|
|
11
|
+
text: 'POD added by driver',
|
|
12
|
+
createdAt: 1653603212886,
|
|
13
|
+
updatedByName: 'Paul Driver 3',
|
|
14
|
+
importance: 'low',
|
|
15
|
+
}, {
|
|
16
|
+
text: 'filler',
|
|
17
|
+
createdAt: 1653601752472,
|
|
18
|
+
updatedByName: 'Paul',
|
|
19
|
+
importance: 'low',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
// Fields below are not dot notation, but should still be kept, in order.
|
|
23
|
+
'specialInstructions[0][text]': 'filler',
|
|
24
|
+
'specialInstructions[0][createdAt]': 1653601752472,
|
|
25
|
+
'specialInstructions[0][updatedByName]': 'Paul',
|
|
26
|
+
'specialInstructions[0][importance]': 'low',
|
|
27
|
+
// should override above
|
|
28
|
+
'deep': {
|
|
29
|
+
companyLogo2: 'b',
|
|
30
|
+
companyLogo3: 'b',
|
|
31
|
+
},
|
|
32
|
+
// should be added into above
|
|
33
|
+
'deep.companyLogo3': 'c',
|
|
34
|
+
'deep.companyLogos.0.logo': 'd',
|
|
35
|
+
'deep.companyLogos.1.logo': 'e',
|
|
36
|
+
'deep.companyLogos.2': 'f',
|
|
37
|
+
}
|
|
38
|
+
const output = {
|
|
17
39
|
name: 'Martin',
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
companyLogos: ['
|
|
22
|
-
companyLogos2: [{ logo: 'c' }, { logo: '' }],
|
|
40
|
+
deep: { // object first seen here
|
|
41
|
+
companyLogo2: 'b',
|
|
42
|
+
companyLogo3: 'c',
|
|
43
|
+
companyLogos: [{ logo: 'd' }, { logo: 'e' }, 'f'],
|
|
23
44
|
},
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
|
|
45
|
+
specialInstructions: [
|
|
46
|
+
{
|
|
47
|
+
text: 'POD added by driver',
|
|
48
|
+
createdAt: 1653603212886,
|
|
49
|
+
updatedByName: 'Paul Driver 3',
|
|
50
|
+
importance: 'low',
|
|
51
|
+
}, {
|
|
52
|
+
text: 'filler',
|
|
53
|
+
createdAt: 1653601752472,
|
|
54
|
+
updatedByName: 'Paul',
|
|
55
|
+
importance: 'low',
|
|
56
|
+
},
|
|
27
57
|
],
|
|
28
|
-
|
|
29
|
-
|
|
58
|
+
'specialInstructions[0][text]': 'filler',
|
|
59
|
+
'specialInstructions[0][createdAt]': 1653601752472,
|
|
60
|
+
'specialInstructions[0][updatedByName]': 'Paul',
|
|
61
|
+
'specialInstructions[0][importance]': 'low',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// parseDotNotation output
|
|
65
|
+
expect(util.parseDotNotation(input)).toEqual(output)
|
|
66
|
+
|
|
67
|
+
// expected order of keys
|
|
68
|
+
expect(Object.keys(output)).toEqual([
|
|
69
|
+
'name',
|
|
70
|
+
'deep',
|
|
71
|
+
'specialInstructions',
|
|
72
|
+
'specialInstructions[0][text]',
|
|
73
|
+
'specialInstructions[0][createdAt]',
|
|
74
|
+
'specialInstructions[0][updatedByName]',
|
|
75
|
+
'specialInstructions[0][importance]',
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('util > parseBracketNotation', async () => {
|
|
81
|
+
const input = {
|
|
82
|
+
'name': 'Martin',
|
|
83
|
+
// 'pets[]': '', // <-- no longer supported
|
|
84
|
+
'deep[companyLogo1]': 'a',
|
|
85
|
+
// not dot notation
|
|
86
|
+
'specialInstructions': [
|
|
87
|
+
{
|
|
88
|
+
text: 'POD added by driver',
|
|
89
|
+
createdAt: 1653603212886,
|
|
90
|
+
updatedByName: 'Paul Driver 3',
|
|
91
|
+
importance: 'low',
|
|
92
|
+
}, {
|
|
93
|
+
text: 'filler',
|
|
94
|
+
createdAt: 1653601752472,
|
|
95
|
+
updatedByName: 'Paul',
|
|
96
|
+
importance: 'low',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
// Fields below are not bracket notation, but should still be kept, in order.
|
|
100
|
+
'specialInstructions.0.text': 'filler',
|
|
101
|
+
'specialInstructions.0.createdAt': 1653601752472,
|
|
102
|
+
'specialInstructions.0.updatedByName': 'Paul',
|
|
103
|
+
'specialInstructions.0.importance': 'low',
|
|
104
|
+
// should override above
|
|
105
|
+
'deep': {
|
|
106
|
+
companyLogo2: 'b',
|
|
107
|
+
companyLogo3: 'b',
|
|
108
|
+
},
|
|
109
|
+
// should be added into above
|
|
110
|
+
'deep[companyLogo3]': 'c',
|
|
111
|
+
'deep[companyLogos][0][logo]': 'd',
|
|
112
|
+
'deep[companyLogos][1][logo]': 'e',
|
|
113
|
+
'deep[companyLogos][2]': 'f',
|
|
114
|
+
}
|
|
115
|
+
const output = {
|
|
116
|
+
name: 'Martin',
|
|
117
|
+
// pets: expect.any(Array),
|
|
118
|
+
deep: { // object first seen here
|
|
119
|
+
companyLogo2: 'b',
|
|
120
|
+
companyLogo3: 'c',
|
|
121
|
+
companyLogos: [{ logo: 'd' }, { logo: 'e' }, 'f'],
|
|
122
|
+
},
|
|
123
|
+
specialInstructions: [
|
|
124
|
+
{
|
|
125
|
+
text: 'POD added by driver',
|
|
126
|
+
createdAt: 1653603212886,
|
|
127
|
+
updatedByName: 'Paul Driver 3',
|
|
128
|
+
importance: 'low',
|
|
129
|
+
}, {
|
|
130
|
+
text: 'filler',
|
|
131
|
+
createdAt: 1653601752472,
|
|
132
|
+
updatedByName: 'Paul',
|
|
133
|
+
importance: 'low',
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
'specialInstructions.0.text': 'filler',
|
|
137
|
+
'specialInstructions.0.createdAt': 1653601752472,
|
|
138
|
+
'specialInstructions.0.updatedByName': 'Paul',
|
|
139
|
+
'specialInstructions.0.importance': 'low',
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// parseBracketNotation output
|
|
143
|
+
expect(util.parseBracketNotation(input)).toEqual(output)
|
|
144
|
+
|
|
145
|
+
// expected order of keys
|
|
146
|
+
expect(Object.keys(output)).toEqual([
|
|
147
|
+
'name',
|
|
148
|
+
'deep',
|
|
149
|
+
'specialInstructions',
|
|
150
|
+
'specialInstructions.0.text',
|
|
151
|
+
'specialInstructions.0.createdAt',
|
|
152
|
+
'specialInstructions.0.updatedByName',
|
|
153
|
+
'specialInstructions.0.importance',
|
|
154
|
+
])
|
|
155
|
+
|
|
156
|
+
expect(() => util.parseBracketNotation({ 'users[][\'name\']': 'Martin' })).toThrow(
|
|
157
|
+
'Monastery: Array items in bracket notation need array indexes "users[][\'name\']", e.g. users[0][name]'
|
|
158
|
+
)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('util > parseBracketToDotNotation', async () => {
|
|
162
|
+
const input = {
|
|
163
|
+
'name': 'Martin',
|
|
164
|
+
'deep[companyLogo1]': 'a',
|
|
165
|
+
// not dot notation
|
|
166
|
+
'specialInstructions': [
|
|
167
|
+
{
|
|
168
|
+
text: 'POD added by driver',
|
|
169
|
+
createdAt: 1653603212886,
|
|
170
|
+
updatedByName: 'Paul Driver 3',
|
|
171
|
+
importance: 'low',
|
|
172
|
+
}, {
|
|
173
|
+
text: 'filler',
|
|
174
|
+
createdAt: 1653601752472,
|
|
175
|
+
updatedByName: 'Paul',
|
|
176
|
+
importance: 'low',
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
// Fields below are not bracket notation, but should still be kept, in order.
|
|
180
|
+
'specialInstructions.0.text': 'filler',
|
|
181
|
+
'specialInstructions.0.createdAt': 1653601752472,
|
|
182
|
+
'specialInstructions.0.updatedByName': 'Paul',
|
|
183
|
+
'specialInstructions.0.importance': 'low',
|
|
184
|
+
// should NOT override above
|
|
185
|
+
'deep': {
|
|
186
|
+
companyLogo2: 'b',
|
|
187
|
+
companyLogo3: 'b',
|
|
188
|
+
},
|
|
189
|
+
// should NOT be added into above
|
|
190
|
+
'deep[companyLogo3]': 'c',
|
|
191
|
+
'deep[companyLogos][0][logo]': 'd',
|
|
192
|
+
'deep[companyLogos][1][logo]': 'e',
|
|
193
|
+
'deep[companyLogos][2]': 'f',
|
|
194
|
+
}
|
|
195
|
+
const output = {
|
|
196
|
+
name: 'Martin',
|
|
197
|
+
'deep.companyLogo1': 'a',
|
|
198
|
+
specialInstructions: [
|
|
199
|
+
{
|
|
200
|
+
text: 'POD added by driver',
|
|
201
|
+
createdAt: 1653603212886,
|
|
202
|
+
updatedByName: 'Paul Driver 3',
|
|
203
|
+
importance: 'low',
|
|
204
|
+
}, {
|
|
205
|
+
text: 'filler',
|
|
206
|
+
createdAt: 1653601752472,
|
|
207
|
+
updatedByName: 'Paul',
|
|
208
|
+
importance: 'low',
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
'specialInstructions.0.text': 'filler',
|
|
212
|
+
'specialInstructions.0.createdAt': 1653601752472,
|
|
213
|
+
'specialInstructions.0.updatedByName': 'Paul',
|
|
214
|
+
'specialInstructions.0.importance': 'low',
|
|
215
|
+
'deep': {
|
|
216
|
+
companyLogo2: 'b',
|
|
217
|
+
companyLogo3: 'b',
|
|
218
|
+
},
|
|
219
|
+
'deep.companyLogo3': 'c',
|
|
220
|
+
'deep.companyLogos.0.logo': 'd',
|
|
221
|
+
'deep.companyLogos.1.logo': 'e',
|
|
222
|
+
'deep.companyLogos.2': 'f',
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// parseBracketToDotNotation output
|
|
226
|
+
expect(util.parseBracketToDotNotation(input)).toEqual(output)
|
|
227
|
+
|
|
228
|
+
// expected order of keys
|
|
229
|
+
expect(Object.keys(output)).toEqual([
|
|
230
|
+
'name',
|
|
231
|
+
'deep.companyLogo1',
|
|
232
|
+
'specialInstructions',
|
|
233
|
+
'specialInstructions.0.text',
|
|
234
|
+
'specialInstructions.0.createdAt',
|
|
235
|
+
'specialInstructions.0.updatedByName',
|
|
236
|
+
'specialInstructions.0.importance',
|
|
237
|
+
'deep',
|
|
238
|
+
'deep.companyLogo3',
|
|
239
|
+
'deep.companyLogos.0.logo',
|
|
240
|
+
'deep.companyLogos.1.logo',
|
|
241
|
+
'deep.companyLogos.2',
|
|
242
|
+
])
|
|
243
|
+
|
|
244
|
+
expect(() => util.parseBracketToDotNotation({ 'users[][\'name\']': 'Martin' })).toThrow(
|
|
30
245
|
'Monastery: Array items in bracket notation need array indexes "users[][\'name\']", e.g. users[0][name]'
|
|
31
246
|
)
|
|
32
247
|
})
|
package/test/validate.js
CHANGED
|
@@ -300,7 +300,7 @@ test('validation subdocument errors', async () => {
|
|
|
300
300
|
])
|
|
301
301
|
)
|
|
302
302
|
|
|
303
|
-
// Insert:
|
|
303
|
+
// Insert: Required subdocument property is ignored with a parent/grandparent specificed
|
|
304
304
|
await expect(user.validate({ animals: {} }, { validateUndefined: false })).resolves.toEqual({
|
|
305
305
|
animals: {},
|
|
306
306
|
})
|
|
@@ -1181,8 +1181,85 @@ test('validation option validateUndefined', async () => {
|
|
|
1181
1181
|
.resolves.toEqual({ names: [{}] })
|
|
1182
1182
|
})
|
|
1183
1183
|
|
|
1184
|
-
test('validation
|
|
1185
|
-
|
|
1184
|
+
test('validation update dot notation', async () => {
|
|
1185
|
+
// Only updates the fields that are passed in the data object, similar to $set. They don't
|
|
1186
|
+
// remove subdocument fields that are not present in the data object.
|
|
1187
|
+
const user = db.model('user_partialUpdate1', {
|
|
1188
|
+
fields: {
|
|
1189
|
+
name: { type: 'string' },
|
|
1190
|
+
address: {
|
|
1191
|
+
city: { type: 'string' },
|
|
1192
|
+
country: { type: 'string' },
|
|
1193
|
+
},
|
|
1194
|
+
},
|
|
1195
|
+
})
|
|
1196
|
+
const user2 = db.model('user_partialUpdate2', {
|
|
1197
|
+
fields: {
|
|
1198
|
+
name: { type: 'string' },
|
|
1199
|
+
address: {
|
|
1200
|
+
city: { type: 'string' },
|
|
1201
|
+
country: { type: 'string', required: true },
|
|
1202
|
+
},
|
|
1203
|
+
books: [{
|
|
1204
|
+
title: { type: 'string', required: true },
|
|
1205
|
+
pages: {
|
|
1206
|
+
count: { type: 'number', required: true },
|
|
1207
|
+
},
|
|
1208
|
+
}],
|
|
1209
|
+
},
|
|
1210
|
+
})
|
|
1211
|
+
|
|
1212
|
+
// Partial validate
|
|
1213
|
+
const validated1 = await user.validate({
|
|
1214
|
+
'address.city': 'Berlin2',
|
|
1215
|
+
'address': { city: 'Berlin' }, // preserved
|
|
1216
|
+
})
|
|
1217
|
+
expect(validated1).toEqual({
|
|
1218
|
+
'address': { city: 'Berlin' },
|
|
1219
|
+
'address.city': 'Berlin2',
|
|
1220
|
+
})
|
|
1221
|
+
|
|
1222
|
+
/// { partialUpdate: true }
|
|
1223
|
+
|
|
1224
|
+
// Order of fields, normal objects are ordered first
|
|
1225
|
+
expect(Object.keys(validated1)).toEqual(['address', 'address.city'])
|
|
1226
|
+
|
|
1227
|
+
// validate (update) throws required error for deep field (as normal)
|
|
1228
|
+
await expect(user2.validate({ 'address': { city: 'Berlin' }}, { update: true }))
|
|
1229
|
+
.rejects.toEqual(expect.any(Array))
|
|
1230
|
+
|
|
1231
|
+
// validate (update) works with dot notatation paths
|
|
1232
|
+
await expect(user2.validate({ 'address.city': 'Berlin2' }, { update: true }))
|
|
1233
|
+
.resolves.toEqual({ 'address.city': 'Berlin2' })
|
|
1234
|
+
|
|
1235
|
+
// validate (insert) still throws required errors, even with dot notatation paths
|
|
1236
|
+
await expect(user2.validate({ 'address.city': 'Berlin2' }))
|
|
1237
|
+
.rejects.toEqual(expect.any(Array))
|
|
1238
|
+
|
|
1239
|
+
// validate (update) works with dot notation array paths, and validates data
|
|
1240
|
+
await expect(user2.validate({ 'books.0.pages.count': '1' }, { update: true }))
|
|
1241
|
+
.resolves.toEqual({ 'books.0.pages.count': 1 })
|
|
1242
|
+
|
|
1243
|
+
// validate (update) works with positional array paths
|
|
1244
|
+
await expect(user2.validate({ 'books.$.pages.count': '2' }, { update: true }))
|
|
1245
|
+
.resolves.toEqual({ 'books.$.pages.count': 2 })
|
|
1246
|
+
|
|
1247
|
+
// Non-field dot notation paths removed
|
|
1248
|
+
await expect(user2.validate({ 'books.0.badfield': 2 }, { update: true }))
|
|
1249
|
+
.resolves.toEqual({})
|
|
1250
|
+
|
|
1251
|
+
// validate (update) should continue on throwing validation errors for dot notation array paths with subdocument values
|
|
1252
|
+
await expect(user2.validate({ 'books.$.pages': {} }, { update: true }))
|
|
1253
|
+
.rejects.toEqual([{
|
|
1254
|
+
'detail': 'This field is required.',
|
|
1255
|
+
'meta': { 'detailLong': undefined, 'field': 'count', 'model': 'user_partialUpdate2', 'rule': 'required' },
|
|
1256
|
+
'status': '400',
|
|
1257
|
+
'title': 'books.$.pages.count',
|
|
1258
|
+
}])
|
|
1259
|
+
})
|
|
1260
|
+
|
|
1261
|
+
test('validation hooks and option skipHooks', async () => {
|
|
1262
|
+
let user = db.model('user_validation_hooks', {
|
|
1186
1263
|
fields: {
|
|
1187
1264
|
first: { type: 'string'},
|
|
1188
1265
|
last: { type: 'string'},
|
|
@@ -1204,6 +1281,7 @@ test('validation hooks', async () => {
|
|
|
1204
1281
|
// Catch validate (a)synchronous errors thrown in function or through `next(err)`
|
|
1205
1282
|
await expect(user.validate({ first: '' })).rejects.toThrow('beforeValidate error 1..')
|
|
1206
1283
|
await expect(user.validate({ first: 'Martin' })).rejects.toThrow('beforeValidate error 2..')
|
|
1284
|
+
await expect(user.validate({ first: 'Martin' }, { skipHooks: true })).resolves.toEqual({ first: 'Martin' })
|
|
1207
1285
|
await expect(user.validate({ first: 'Martin', last: 'Luther' })).resolves.toEqual({
|
|
1208
1286
|
first: 'Martin',
|
|
1209
1287
|
last: 'Luther',
|
|
@@ -1212,6 +1290,10 @@ test('validation hooks', async () => {
|
|
|
1212
1290
|
// Catch insert (a)synchronous errors thrown in function or through `next(err)`
|
|
1213
1291
|
await expect(user.insert({ data: { first: '' } })).rejects.toThrow('beforeValidate error 1..')
|
|
1214
1292
|
await expect(user.insert({ data: { first: 'Martin' } })).rejects.toThrow('beforeValidate error 2..')
|
|
1293
|
+
await expect(user.insert({ data: { first: 'Martin' }, skipHooks: true })).resolves.toEqual({
|
|
1294
|
+
_id: expect.any(Object),
|
|
1295
|
+
first: 'Martin',
|
|
1296
|
+
})
|
|
1215
1297
|
await expect(user.insert({ data: { first: 'Martin', last: 'Luther' } })).resolves.toEqual({
|
|
1216
1298
|
_id: expect.any(Object),
|
|
1217
1299
|
first: 'Martin',
|
|
@@ -1223,6 +1305,8 @@ test('validation hooks', async () => {
|
|
|
1223
1305
|
.rejects.toThrow('beforeValidate error 1..')
|
|
1224
1306
|
await expect(user.update({ query: userDoc._id, data: { first: 'Martin' } }))
|
|
1225
1307
|
.rejects.toThrow('beforeValidate error 2..')
|
|
1308
|
+
await expect(user.update({ query: userDoc._id, data: { first: 'Martin' }, skipHooks: true }))
|
|
1309
|
+
.resolves.toEqual({ first: 'Martin' })
|
|
1226
1310
|
await expect(user.update({ query: userDoc._id, data: { first: 'Martin', last: 'Luther' } }))
|
|
1227
1311
|
.resolves.toEqual({
|
|
1228
1312
|
first: 'Martin',
|