misc.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. var tape = require('tape')
  2. var cosmic = require('./fixtures/cosmic')
  3. var validator = require('../')
  4. var validatorRequire = require('../require')
  5. tape('simple', function(t) {
  6. var schema = {
  7. required: true,
  8. type: 'object',
  9. properties: {
  10. hello: {type:'string', required:true}
  11. }
  12. }
  13. var validate = validator(schema)
  14. t.ok(validate({hello: 'world'}), 'should be valid')
  15. t.notOk(validate(), 'should be invalid')
  16. t.notOk(validate({}), 'should be invalid')
  17. t.end()
  18. })
  19. tape('data is undefined', function (t) {
  20. var validate = validator({type: 'string'})
  21. t.notOk(validate(null))
  22. t.notOk(validate(undefined))
  23. t.end()
  24. })
  25. tape('advanced', function(t) {
  26. var validate = validator(cosmic.schema)
  27. t.ok(validate(cosmic.valid), 'should be valid')
  28. t.notOk(validate(cosmic.invalid), 'should be invalid')
  29. t.end()
  30. })
  31. tape('greedy/false', function(t) {
  32. var validate = validator({
  33. type: 'object',
  34. properties: {
  35. x: {
  36. type: 'number'
  37. }
  38. },
  39. required: ['x', 'y']
  40. });
  41. t.notOk(validate({}), 'should be invalid')
  42. t.strictEqual(validate.errors.length, 2);
  43. t.strictEqual(validate.errors[0].field, 'data.x')
  44. t.strictEqual(validate.errors[0].message, 'is required')
  45. t.strictEqual(validate.errors[1].field, 'data.y')
  46. t.strictEqual(validate.errors[1].message, 'is required')
  47. t.notOk(validate({x: 'string'}), 'should be invalid')
  48. t.strictEqual(validate.errors.length, 1);
  49. t.strictEqual(validate.errors[0].field, 'data.y')
  50. t.strictEqual(validate.errors[0].message, 'is required')
  51. t.notOk(validate({x: 'string', y: 'value'}), 'should be invalid')
  52. t.strictEqual(validate.errors.length, 1);
  53. t.strictEqual(validate.errors[0].field, 'data.x')
  54. t.strictEqual(validate.errors[0].message, 'is the wrong type')
  55. t.end();
  56. });
  57. tape('greedy/true', function(t) {
  58. var validate = validator({
  59. type: 'object',
  60. properties: {
  61. x: {
  62. type: 'number'
  63. }
  64. },
  65. required: ['x', 'y']
  66. }, {
  67. greedy: true
  68. });
  69. t.notOk(validate({}), 'should be invalid')
  70. t.strictEqual(validate.errors.length, 2);
  71. t.strictEqual(validate.errors[0].field, 'data.x')
  72. t.strictEqual(validate.errors[0].message, 'is required')
  73. t.strictEqual(validate.errors[1].field, 'data.y')
  74. t.strictEqual(validate.errors[1].message, 'is required')
  75. t.notOk(validate({x: 'string'}), 'should be invalid')
  76. t.strictEqual(validate.errors.length, 2);
  77. t.strictEqual(validate.errors[0].field, 'data.y')
  78. t.strictEqual(validate.errors[0].message, 'is required')
  79. t.strictEqual(validate.errors[1].field, 'data.x')
  80. t.strictEqual(validate.errors[1].message, 'is the wrong type')
  81. t.notOk(validate({x: 'string', y: 'value'}), 'should be invalid')
  82. t.strictEqual(validate.errors.length, 1);
  83. t.strictEqual(validate.errors[0].field, 'data.x')
  84. t.strictEqual(validate.errors[0].message, 'is the wrong type')
  85. t.ok(validate({x: 1, y: 'value'}), 'should be invalid')
  86. t.end();
  87. });
  88. tape('additional props', function(t) {
  89. var validate = validator({
  90. type: 'object',
  91. additionalProperties: false
  92. }, {
  93. verbose: true
  94. })
  95. t.ok(validate({}))
  96. t.notOk(validate({foo:'bar'}))
  97. t.ok(validate.errors[0].value === 'data.foo', 'should output the property not allowed in verbose mode')
  98. t.strictEqual(validate.errors[0].type, 'object', 'error object should contain the type')
  99. t.end()
  100. })
  101. tape('array', function(t) {
  102. var validate = validator({
  103. type: 'array',
  104. required: true,
  105. items: {
  106. type: 'string'
  107. }
  108. })
  109. t.notOk(validate({}), 'wrong type')
  110. t.notOk(validate(), 'is required')
  111. t.ok(validate(['test']))
  112. t.end()
  113. })
  114. tape('nested array', function(t) {
  115. var validate = validator({
  116. type: 'object',
  117. properties: {
  118. list: {
  119. type: 'array',
  120. required: true,
  121. items: {
  122. type: 'string'
  123. }
  124. }
  125. }
  126. })
  127. t.notOk(validate({}), 'is required')
  128. t.ok(validate({list:['test']}))
  129. t.notOk(validate({list:[1]}))
  130. t.end()
  131. })
  132. tape('enum', function(t) {
  133. var validate = validator({
  134. type: 'object',
  135. properties: {
  136. foo: {
  137. type: 'number',
  138. required: true,
  139. enum: [42]
  140. }
  141. }
  142. })
  143. t.notOk(validate({}), 'is required')
  144. t.ok(validate({foo:42}))
  145. t.notOk(validate({foo:43}))
  146. t.end()
  147. })
  148. tape('minimum/maximum', function(t) {
  149. var validate = validator({
  150. type: 'object',
  151. properties: {
  152. foo: {
  153. type: 'number',
  154. minimum: 0,
  155. maximum: 0
  156. }
  157. }
  158. })
  159. t.notOk(validate({foo:-42}))
  160. t.ok(validate({foo:0}))
  161. t.notOk(validate({foo:42}))
  162. t.end()
  163. })
  164. tape('exclusiveMinimum/exclusiveMaximum', function(t) {
  165. var validate = validator({
  166. type: 'object',
  167. properties: {
  168. foo: {
  169. type: 'number',
  170. minimum: 10,
  171. maximum: 20,
  172. exclusiveMinimum: true,
  173. exclusiveMaximum: true
  174. }
  175. }
  176. })
  177. t.notOk(validate({foo:10}))
  178. t.ok(validate({foo:11}))
  179. t.notOk(validate({foo:20}))
  180. t.ok(validate({foo:19}))
  181. t.end()
  182. })
  183. tape('minimum/maximum number type', function(t) {
  184. var validate = validator({
  185. type: ['integer', 'null'],
  186. minimum: 1,
  187. maximum: 100
  188. })
  189. t.notOk(validate(-1))
  190. t.notOk(validate(0))
  191. t.ok(validate(null))
  192. t.ok(validate(1))
  193. t.ok(validate(100))
  194. t.notOk(validate(101))
  195. t.end()
  196. })
  197. tape('custom format', function(t) {
  198. var validate = validator({
  199. type: 'object',
  200. properties: {
  201. foo: {
  202. type: 'string',
  203. format: 'as'
  204. }
  205. }
  206. }, {formats: {as:/^a+$/}})
  207. t.notOk(validate({foo:''}), 'not as')
  208. t.notOk(validate({foo:'b'}), 'not as')
  209. t.notOk(validate({foo:'aaab'}), 'not as')
  210. t.ok(validate({foo:'a'}), 'as')
  211. t.ok(validate({foo:'aaaaaa'}), 'as')
  212. t.end()
  213. })
  214. tape('custom format function', function(t) {
  215. var validate = validator({
  216. type: 'object',
  217. properties: {
  218. foo: {
  219. type: 'string',
  220. format: 'as'
  221. }
  222. }
  223. }, {formats: {as:function(s) { return /^a+$/.test(s) } }})
  224. t.notOk(validate({foo:''}), 'not as')
  225. t.notOk(validate({foo:'b'}), 'not as')
  226. t.notOk(validate({foo:'aaab'}), 'not as')
  227. t.ok(validate({foo:'a'}), 'as')
  228. t.ok(validate({foo:'aaaaaa'}), 'as')
  229. t.end()
  230. })
  231. tape('do not mutate schema', function(t) {
  232. var sch = {
  233. items: [
  234. {}
  235. ],
  236. additionalItems: {
  237. type: 'integer'
  238. }
  239. }
  240. var copy = JSON.parse(JSON.stringify(sch))
  241. validator(sch)
  242. t.same(sch, copy, 'did not mutate')
  243. t.end()
  244. })
  245. tape('#toJSON()', function(t) {
  246. var schema = {
  247. required: true,
  248. type: 'object',
  249. properties: {
  250. hello: {type:'string', required:true}
  251. }
  252. }
  253. var validate = validator(schema)
  254. t.deepEqual(validate.toJSON(), schema, 'should return original schema')
  255. t.end()
  256. })
  257. tape('external schemas', function(t) {
  258. var ext = {type: 'string'}
  259. var schema = {
  260. required: true,
  261. $ref: '#ext'
  262. }
  263. var validate = validator(schema, {schemas: {ext:ext}})
  264. t.ok(validate('hello string'), 'is a string')
  265. t.notOk(validate(42), 'not a string')
  266. t.end()
  267. })
  268. tape('external schema URIs', function(t) {
  269. var ext = {type: 'string'}
  270. var schema = {
  271. required: true,
  272. $ref: 'http://example.com/schemas/schemaURIs'
  273. }
  274. var opts = {schemas:{}};
  275. opts.schemas['http://example.com/schemas/schemaURIs'] = ext;
  276. var validate = validator(schema, opts)
  277. t.ok(validate('hello string'), 'is a string')
  278. t.notOk(validate(42), 'not a string')
  279. t.end()
  280. })
  281. tape('top-level external schema', function(t) {
  282. var defs = {
  283. "string": {
  284. type: "string"
  285. },
  286. "sex": {
  287. type: "string",
  288. enum: ["male", "female", "other"]
  289. }
  290. }
  291. var schema = {
  292. type: "object",
  293. properties: {
  294. "name": { $ref: "definitions.json#/string" },
  295. "sex": { $ref: "definitions.json#/sex" }
  296. },
  297. required: ["name", "sex"]
  298. }
  299. var validate = validator(schema, {
  300. schemas: {
  301. "definitions.json": defs
  302. }
  303. })
  304. t.ok(validate({name:"alice", sex:"female"}), 'is an object')
  305. t.notOk(validate({name:"alice", sex: "bob"}), 'recognizes external schema')
  306. t.notOk(validate({name:2, sex: "female"}), 'recognizes external schema')
  307. t.end()
  308. })
  309. tape('nested required array decl', function(t) {
  310. var schema = {
  311. properties: {
  312. x: {
  313. type: 'object',
  314. properties: {
  315. y: {
  316. type: 'object',
  317. properties: {
  318. z: {
  319. type: 'string'
  320. }
  321. },
  322. required: ['z']
  323. }
  324. }
  325. }
  326. },
  327. required: ['x']
  328. }
  329. var validate = validator(schema)
  330. t.ok(validate({x: {}}), 'should be valid')
  331. t.notOk(validate({}), 'should not be valid')
  332. t.strictEqual(validate.errors[0].field, 'data.x', 'should output the missing field')
  333. t.end()
  334. })
  335. tape('verbose mode', function(t) {
  336. var schema = {
  337. required: true,
  338. type: 'object',
  339. properties: {
  340. hello: {
  341. required: true,
  342. type: 'string'
  343. }
  344. }
  345. };
  346. var validate = validator(schema, {verbose: true})
  347. t.ok(validate({hello: 'string'}), 'should be valid')
  348. t.notOk(validate({hello: 100}), 'should not be valid')
  349. t.strictEqual(validate.errors[0].value, 100, 'error object should contain the invalid value')
  350. t.strictEqual(validate.errors[0].type, 'string', 'error object should contain the type')
  351. t.end()
  352. })
  353. tape('additional props in verbose mode', function(t) {
  354. var schema = {
  355. type: 'object',
  356. required: true,
  357. additionalProperties: false,
  358. properties: {
  359. foo: {
  360. type: 'string'
  361. },
  362. 'hello world': {
  363. type: 'object',
  364. required: true,
  365. additionalProperties: false,
  366. properties: {
  367. foo: {
  368. type: 'string'
  369. }
  370. }
  371. }
  372. }
  373. };
  374. var validate = validator(schema, {verbose: true})
  375. validate({'hello world': {bar: 'string'}});
  376. t.strictEqual(validate.errors[0].value, 'data["hello world"].bar', 'should output the path to the additional prop in the error')
  377. t.end()
  378. })
  379. tape('Date.now() is an integer', function(t) {
  380. var schema = {type: 'integer'}
  381. var validate = validator(schema)
  382. t.ok(validate(Date.now()), 'is integer')
  383. t.end()
  384. })
  385. tape('field shows item index in arrays', function(t) {
  386. var schema = {
  387. type: 'array',
  388. items: {
  389. type: 'array',
  390. items: {
  391. properties: {
  392. foo: {
  393. type: 'string',
  394. required: true
  395. }
  396. }
  397. }
  398. }
  399. }
  400. var validate = validator(schema)
  401. validate([
  402. [
  403. { foo: 'test' },
  404. { foo: 'test' }
  405. ],
  406. [
  407. { foo: 'test' },
  408. { baz: 'test' }
  409. ]
  410. ])
  411. t.strictEqual(validate.errors[0].field, 'data.1.1.foo', 'should output the field with specific index of failing item in the error')
  412. t.end()
  413. })