-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest.js
More file actions
254 lines (230 loc) · 10.6 KB
/
Copy pathtest.js
File metadata and controls
254 lines (230 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
'use strict'
const assert = require('assert')
const { Validator } = require('ata-validator')
const { withKeywords } = require('./index.js')
let passed = 0
function ok(name, cond) {
assert.strictEqual(cond, true, name)
passed++
}
// 1. instanceof on a plain object property (regression: the original behavior)
{
const v = withKeywords(new Validator({
type: 'object',
properties: { createdAt: { instanceof: 'Date' } },
}))
ok('object prop: Date accepted', v.validate({ createdAt: new Date() }).valid)
ok('object prop: non-Date rejected', v.validate({ createdAt: 'nope' }).valid === false)
ok('object prop: missing is skipped by keyword', v.validate({}).valid)
}
// 2. instanceof INSIDE array items — the gap this fix closes
{
const v = withKeywords(new Validator({
type: 'object',
properties: {
images: {
type: 'array',
items: { properties: { takenAt: { instanceof: 'Date' } } },
},
},
}))
ok('array items: all Dates accepted',
v.validate({ images: [{ takenAt: new Date() }, { takenAt: new Date() }] }).valid)
const bad = v.validate({ images: [{ takenAt: new Date() }, { takenAt: 'nope' }] })
ok('array items: a non-Date element is rejected', bad.valid === false)
ok('array items: instancePath points at the bad index',
bad.errors.some((e) => e.instancePath === '/images/1/takenAt'))
}
// 3. instanceof directly on array elements (items is the leaf check)
{
const v = withKeywords(new Validator({
type: 'object',
properties: { dates: { type: 'array', items: { instanceof: 'Date' } } },
}))
ok('array of Dates: accepted', v.validate({ dates: [new Date(), new Date()] }).valid)
ok('array of Dates: string element rejected',
v.validate({ dates: [new Date(), 'x'] }).valid === false)
}
// 4. nested arrays (array of arrays)
{
const v = withKeywords(new Validator({
type: 'object',
properties: {
grid: { type: 'array', items: { type: 'array', items: { instanceof: 'Date' } } },
},
}))
ok('nested array: accepted', v.validate({ grid: [[new Date()], [new Date()]] }).valid)
const bad = v.validate({ grid: [[new Date()], ['x']] })
ok('nested array: rejected', bad.valid === false)
ok('nested array: path carries both indices',
bad.errors.some((e) => e.instancePath === '/grid/1/0'))
}
// 5. tuple prefixItems
{
const v = withKeywords(new Validator({
type: 'object',
properties: {
pair: { type: 'array', prefixItems: [{ instanceof: 'Date' }, { typeof: 'string' }] },
},
}))
ok('tuple: matching accepted', v.validate({ pair: [new Date(), 'x'] }).valid)
ok('tuple: wrong first element rejected', v.validate({ pair: ['x', 'y'] }).valid === false)
ok('tuple: wrong second element rejected', v.validate({ pair: [new Date(), 5] }).valid === false)
}
// 6. typeof inside array items
{
const v = withKeywords(new Validator({
type: 'object',
properties: {
tags: { type: 'array', items: { properties: { label: { typeof: 'string' } } } },
},
}))
ok('typeof in array: accepted', v.validate({ tags: [{ label: 'a' }] }).valid)
ok('typeof in array: number rejected', v.validate({ tags: [{ label: 3 }] }).valid === false)
}
// 7. top-level array root
{
const v = withKeywords(new Validator({ type: 'array', items: { instanceof: 'Date' } }))
ok('root array: accepted', v.validate([new Date()]).valid)
ok('root array: rejected', v.validate([new Date(), 'x']).valid === false)
}
// 8. no custom keywords: validator returned untouched, standard validation intact
{
const v = withKeywords(new Validator({ type: 'object', properties: { n: { type: 'number' } } }))
ok('no keywords: valid data passes', v.validate({ n: 1 }).valid)
ok('no keywords: standard validation still rejects', v.validate({ n: 'x' }).valid === false)
}
// 9. every entry point agrees with validate(), not just validate()
{
const v = withKeywords(new Validator({
type: 'object',
properties: { createdAt: { instanceof: 'Date' } },
}))
ok('isValidObject: Date accepted', v.isValidObject({ createdAt: new Date() }) === true)
ok('isValidObject: non-Date rejected', v.isValidObject({ createdAt: 'nope' }) === false)
ok('isValidObject: missing is skipped by keyword', v.isValidObject({}) === true)
ok('~standard: non-Date rejected',
v['~standard'].validate({ createdAt: 'nope' }).issues !== undefined)
}
// 10. JSON entry points see the same schema
{
const v = withKeywords(new Validator({
type: 'object',
properties: { label: { typeof: 'string' } },
}))
ok('validateJSON: matching typeof accepted', v.validateJSON('{"label":"a"}').valid === true)
ok('validateJSON: wrong typeof rejected', v.validateJSON('{"label":3}').valid === false)
ok('isValidJSON: matching typeof accepted', v.isValidJSON('{"label":"a"}') === true)
ok('isValidJSON: wrong typeof rejected', v.isValidJSON('{"label":3}') === false)
ok('validateJSON: invalid JSON still reports invalid', v.validateJSON('{oops').valid === false)
}
// 11. schemas without custom keywords keep every entry point untouched
{
const v = withKeywords(new Validator({ type: 'object', properties: { n: { type: 'number' } } }))
ok('no keywords: isValidObject passes', v.isValidObject({ n: 1 }) === true)
ok('no keywords: isValidObject rejects', v.isValidObject({ n: 'x' }) === false)
ok('no keywords: isValidJSON passes', v.isValidJSON('{"n":1}') === true)
}
// 12. the keyword check survives the validator installing its own compiled
// function, whichever entry point runs first
{
for (const first of ['isValidObject', 'validate', 'validateJSON', 'isValidJSON']) {
const v = withKeywords(new Validator({
type: 'object',
properties: { createdAt: { instanceof: 'Date' } },
}))
if (first === 'validateJSON' || first === 'isValidJSON') v[first]('{"createdAt":null}')
else v[first]({ createdAt: new Date() })
ok(first + ' first: isValidObject still rejects a non-Date',
v.isValidObject({ createdAt: 'nope' }) === false)
ok(first + ' first: validate still rejects a non-Date',
v.validate({ createdAt: 'nope' }).valid === false)
ok(first + ' first: a Date is still accepted',
v.isValidObject({ createdAt: new Date() }) === true)
}
}
// 13. a value that breaks both the schema and a custom keyword reports both
{
const v = withKeywords(new Validator({
type: 'object',
properties: { createdAt: { instanceof: 'Date' }, n: { type: 'number' } },
required: ['n'],
}))
const res = v.validate({ createdAt: 'nope', n: 'x' })
ok('both broken: invalid', res.valid === false)
ok('both broken: the schema error is reported',
res.errors.some((e) => e.keyword !== 'instanceof'))
ok('both broken: the keyword error is reported',
res.errors.some((e) => e.keyword === 'instanceof'))
}
// 14. the schema rejecting on its own still rejects, keywords clean
{
const v = withKeywords(new Validator({
type: 'object',
properties: { createdAt: { instanceof: 'Date' }, n: { type: 'number' } },
}))
const res = v.validate({ createdAt: new Date(), n: 'x' })
ok('schema-only failure: invalid', res.valid === false)
ok('schema-only failure: no keyword error', res.errors.every((e) => e.keyword !== 'instanceof'))
ok('schema-only failure: isValidObject agrees', v.isValidObject({ createdAt: new Date(), n: 'x' }) === false)
}
// 15. a validator that rewrites its input keeps the keywords
{
const v = withKeywords(new Validator({
type: 'object',
properties: { createdAt: { instanceof: 'Date' }, n: { type: 'number', default: 7 } },
}, { coerceTypes: true, useDefaults: true }))
const res = v.validate({ createdAt: new Date() })
ok('coerceTypes: valid data passes', res.valid)
ok('coerceTypes: default applied', res.data.n === 7)
ok('coerceTypes: non-Date rejected', v.validate({ createdAt: 'nope' }).valid === false)
ok('coerceTypes: isValidObject agrees', v.isValidObject({ createdAt: 'nope' }) === false)
}
// Wrapping is lazy: nothing is compiled until an entry point is used, so a
// constructor registered after withKeywords() and before the first call is
// honoured, and a schema without custom keywords ends up with its plain
// entry points, no accessor left behind.
{
class Money {}
const v = withKeywords(new Validator({ type: 'object', properties: { price: { instanceof: 'Money' } } }))
withKeywords.CONSTRUCTORS.Money = Money
ok('constructor registered after wrapping is used', v.validate({ price: new Money() }).valid)
ok('constructor registered after wrapping rejects', v.validate({ price: 1 }).valid === false)
delete withKeywords.CONSTRUCTORS.Money
const plain = withKeywords(new Validator({ type: 'object', properties: { n: { type: 'number' } } }))
ok('no-keyword schema validates', plain.validate({ n: 1 }).valid && plain.validate({ n: 'x' }).valid === false)
const desc = Object.getOwnPropertyDescriptor(plain, 'validate')
ok('no-keyword schema keeps a plain validate after first use', !desc || typeof desc.get !== 'function')
}
// A wrapped validator enforces checks its schema does not carry, so ata's
// ahead-of-time emitters must not turn it into a standalone module: the module
// is built from the compiled schema alone and would accept documents this
// validator rejects, with nothing to say it had been weakened. The wrapper is
// the only thing that knows it wraps, so it declares `_externalChecks` and the
// emitters read it.
{
const v = withKeywords(new Validator({
type: 'object',
properties: { created: { instanceof: 'Date' } },
required: ['created'],
}))
ok('a wrapped schema with custom keywords declares external checks', v._externalChecks === true)
ok('the wrapped validator rejects what the bare schema accepts', v.isValidObject({ created: {} }) === false)
const inert = withKeywords(new Validator({ type: 'object', properties: { n: { type: 'number' } } }))
ok('a wrapped schema with no custom keyword declares none', inert._externalChecks === false)
// The refusal itself lives in ata. Older versions have no guard to exercise,
// so this asserts it only where it exists rather than pinning a version.
let emitters = null
try { emitters = require('ata-validator/aot') } catch { /* not exported here */ }
if (emitters && emitters.toStandaloneModule) {
let threw = null
try { emitters.toStandaloneModule(v, { format: 'esm' }) } catch (e) { threw = e }
if (threw && /enforces checks that are not in its schema/.test(threw.message)) {
ok('ata refuses to emit a standalone module for a wrapped validator', true)
} else {
console.log(' (installed ata-validator ' + require('ata-validator/package.json').version +
' has no external-checks guard; skipping the refusal assertion)')
}
}
}
console.log('ata-keywords: ' + passed + ' assertions passed')