options.spec.js 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416
  1. 'use strict';
  2. var Ajv = require('./ajv')
  3. , getAjvInstances = require('./ajv_instances')
  4. , should = require('./chai').should();
  5. describe('Ajv Options', function () {
  6. describe('removeAdditional', function() {
  7. it('should remove all additional properties', function() {
  8. var ajv = new Ajv({ removeAdditional: 'all' });
  9. ajv.addSchema({
  10. $id: '//test/fooBar',
  11. properties: { foo: { type: 'string' }, bar: { type: 'string' } }
  12. });
  13. var object = {
  14. foo: 'foo', bar: 'bar', baz: 'baz-to-be-removed'
  15. };
  16. ajv.validate('//test/fooBar', object).should.equal(true);
  17. object.should.have.property('foo');
  18. object.should.have.property('bar');
  19. object.should.not.have.property('baz');
  20. });
  21. it('should remove properties that would error when `additionalProperties = false`', function() {
  22. var ajv = new Ajv({ removeAdditional: true });
  23. ajv.addSchema({
  24. $id: '//test/fooBar',
  25. properties: { foo: { type: 'string' }, bar: { type: 'string' } },
  26. additionalProperties: false
  27. });
  28. var object = {
  29. foo: 'foo', bar: 'bar', baz: 'baz-to-be-removed'
  30. };
  31. ajv.validate('//test/fooBar', object).should.equal(true);
  32. object.should.have.property('foo');
  33. object.should.have.property('bar');
  34. object.should.not.have.property('baz');
  35. });
  36. it('should remove properties that would error when `additionalProperties = false` (many properties, boolean schema)', function() {
  37. var ajv = new Ajv({removeAdditional: true});
  38. var schema = {
  39. properties: {
  40. obj: {
  41. additionalProperties: false,
  42. properties: {
  43. a: { type: 'string' },
  44. b: false,
  45. c: { type: 'string' },
  46. d: { type: 'string' },
  47. e: { type: 'string' },
  48. f: { type: 'string' },
  49. g: { type: 'string' },
  50. h: { type: 'string' },
  51. i: { type: 'string' }
  52. }
  53. }
  54. }
  55. };
  56. var data = {
  57. obj: {
  58. a: 'valid',
  59. b: 'should not be removed',
  60. additional: 'will be removed'
  61. }
  62. };
  63. ajv.validate(schema, data) .should.equal(false);
  64. data .should.eql({
  65. obj: {
  66. a: 'valid',
  67. b: 'should not be removed'
  68. }
  69. });
  70. });
  71. it('should remove properties that would error when `additionalProperties` is a schema', function() {
  72. var ajv = new Ajv({ removeAdditional: 'failing' });
  73. ajv.addSchema({
  74. $id: '//test/fooBar',
  75. properties: { foo: { type: 'string' }, bar: { type: 'string' } },
  76. additionalProperties: { type: 'string' }
  77. });
  78. var object = {
  79. foo: 'foo', bar: 'bar', baz: 'baz-to-be-kept', fizz: 1000
  80. };
  81. ajv.validate('//test/fooBar', object).should.equal(true);
  82. object.should.have.property('foo');
  83. object.should.have.property('bar');
  84. object.should.have.property('baz');
  85. object.should.not.have.property('fizz');
  86. ajv.addSchema({
  87. $id: '//test/fooBar2',
  88. properties: { foo: { type: 'string' }, bar: { type: 'string' } },
  89. additionalProperties: { type: 'string', pattern: '^to-be-', maxLength: 10 }
  90. });
  91. object = {
  92. foo: 'foo', bar: 'bar', baz: 'to-be-kept', quux: 'to-be-removed', fizz: 1000
  93. };
  94. ajv.validate('//test/fooBar2', object).should.equal(true);
  95. object.should.have.property('foo');
  96. object.should.have.property('bar');
  97. object.should.have.property('baz');
  98. object.should.not.have.property('fizz');
  99. });
  100. });
  101. describe('ownProperties', function() {
  102. var ajv, ajvOP, ajvOP1;
  103. beforeEach(function() {
  104. ajv = new Ajv({ allErrors: true });
  105. ajvOP = new Ajv({ ownProperties: true, allErrors: true });
  106. ajvOP1 = new Ajv({ ownProperties: true });
  107. });
  108. it('should only validate own properties with additionalProperties', function() {
  109. var schema = {
  110. properties: { a: { type: 'number' } },
  111. additionalProperties: false
  112. };
  113. var obj = { a: 1 };
  114. var proto = { b: 2 };
  115. test(schema, obj, proto);
  116. });
  117. it('should only validate own properties with properties keyword', function() {
  118. var schema = {
  119. properties: {
  120. a: { type: 'number' },
  121. b: { type: 'number' }
  122. }
  123. };
  124. var obj = { a: 1 };
  125. var proto = { b: 'not a number' };
  126. test(schema, obj, proto);
  127. });
  128. it('should only validate own properties with required keyword', function() {
  129. var schema = {
  130. required: ['a', 'b']
  131. };
  132. var obj = { a: 1 };
  133. var proto = { b: 2 };
  134. test(schema, obj, proto, 1, true);
  135. });
  136. it('should only validate own properties with required keyword - many properties', function() {
  137. ajv = new Ajv({ allErrors: true, loopRequired: 1 });
  138. ajvOP = new Ajv({ ownProperties: true, allErrors: true, loopRequired: 1 });
  139. ajvOP1 = new Ajv({ ownProperties: true, loopRequired: 1 });
  140. var schema = {
  141. required: ['a', 'b', 'c', 'd']
  142. };
  143. var obj = { a: 1, b: 2 };
  144. var proto = { c: 3, d: 4 };
  145. test(schema, obj, proto, 2, true);
  146. });
  147. it('should only validate own properties with required keyword as $data', function() {
  148. ajv = new Ajv({ allErrors: true, $data: true });
  149. ajvOP = new Ajv({ ownProperties: true, allErrors: true, $data: true });
  150. ajvOP1 = new Ajv({ ownProperties: true, $data: true });
  151. var schema = {
  152. required: { $data: '0/req' },
  153. properties: {
  154. req: {
  155. type: 'array',
  156. items: { type: 'string' }
  157. }
  158. }
  159. };
  160. var obj = {
  161. req: ['a', 'b'],
  162. a: 1
  163. };
  164. var proto = { b: 2 };
  165. test(schema, obj, proto, 1, true);
  166. });
  167. it('should only validate own properties with properties and required keyword', function() {
  168. var schema = {
  169. properties: {
  170. a: { type: 'number' },
  171. b: { type: 'number' }
  172. },
  173. required: ['a', 'b']
  174. };
  175. var obj = { a: 1 };
  176. var proto = { b: 2 };
  177. test(schema, obj, proto, 1, true);
  178. });
  179. it('should only validate own properties with dependencies keyword', function() {
  180. var schema = {
  181. dependencies: {
  182. a: ['c'],
  183. b: ['d']
  184. }
  185. };
  186. var obj = { a: 1, c: 3 };
  187. var proto = { b: 2 };
  188. test(schema, obj, proto);
  189. obj = { a: 1, b: 2, c: 3 };
  190. proto = { d: 4 };
  191. test(schema, obj, proto, 1, true);
  192. });
  193. it('should only validate own properties with schema dependencies', function() {
  194. var schema = {
  195. dependencies: {
  196. a: { not: { required: ['c'] } },
  197. b: { not: { required: ['d'] } }
  198. }
  199. };
  200. var obj = { a: 1, d: 3 };
  201. var proto = { b: 2 };
  202. test(schema, obj, proto);
  203. obj = { a: 1, b: 2 };
  204. proto = { d: 4 };
  205. test(schema, obj, proto);
  206. });
  207. it('should only validate own properties with patternProperties', function() {
  208. var schema = {
  209. patternProperties: { 'f.*o': { type: 'integer' } },
  210. };
  211. var obj = { fooo: 1 };
  212. var proto = { foo: 'not a number' };
  213. test(schema, obj, proto);
  214. });
  215. it('should only validate own properties with propertyNames', function() {
  216. var schema = {
  217. propertyNames: {
  218. format: 'email'
  219. }
  220. };
  221. var obj = { 'e@example.com': 2 };
  222. var proto = { 'not email': 1 };
  223. test(schema, obj, proto, 2);
  224. });
  225. function test(schema, obj, proto, errors, reverse) {
  226. errors = errors || 1;
  227. var validate = ajv.compile(schema);
  228. var validateOP = ajvOP.compile(schema);
  229. var validateOP1 = ajvOP1.compile(schema);
  230. var data = Object.create(proto);
  231. for (var key in obj) data[key] = obj[key];
  232. if (reverse) {
  233. validate(data) .should.equal(true);
  234. validateOP(data) .should.equal(false);
  235. validateOP.errors .should.have.length(errors);
  236. validateOP1(data) .should.equal(false);
  237. validateOP1.errors .should.have.length(1);
  238. } else {
  239. validate(data) .should.equal(false);
  240. validate.errors .should.have.length(errors);
  241. validateOP(data) .should.equal(true);
  242. validateOP1(data) .should.equal(true);
  243. }
  244. }
  245. });
  246. describe('meta and validateSchema', function() {
  247. it('should add draft-6 meta schema by default', function() {
  248. testOptionMeta(new Ajv);
  249. testOptionMeta(new Ajv({ meta: true }));
  250. function testOptionMeta(ajv) {
  251. ajv.getSchema('http://json-schema.org/draft-07/schema') .should.be.a('function');
  252. ajv.validateSchema({ type: 'integer' }) .should.equal(true);
  253. ajv.validateSchema({ type: 123 }) .should.equal(false);
  254. should.not.throw(function() { ajv.addSchema({ type: 'integer' }); });
  255. should.throw(function() { ajv.addSchema({ type: 123 }); });
  256. }
  257. });
  258. it('should throw if meta: false and validateSchema: true', function() {
  259. var ajv = new Ajv({ meta: false });
  260. should.not.exist(ajv.getSchema('http://json-schema.org/draft-07/schema'));
  261. should.not.throw(function() { ajv.addSchema({ type: 'wrong_type' }, 'integer'); });
  262. });
  263. it('should skip schema validation with validateSchema: false', function() {
  264. var ajv = new Ajv;
  265. should.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); });
  266. ajv = new Ajv({ validateSchema: false });
  267. should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); });
  268. ajv = new Ajv({ validateSchema: false, meta: false });
  269. should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); });
  270. });
  271. it('should not throw on invalid schema with validateSchema: "log"', function() {
  272. var logError = console.error;
  273. var loggedError = false;
  274. console.error = function() { loggedError = true; logError.apply(console, arguments); };
  275. var ajv = new Ajv({ validateSchema: 'log' });
  276. should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); });
  277. loggedError .should.equal(true);
  278. loggedError = false;
  279. ajv = new Ajv({ validateSchema: 'log', meta: false });
  280. should.not.throw(function() { ajv.addSchema({ type: 123 }, 'integer'); });
  281. loggedError .should.equal(false);
  282. console.error = logError;
  283. });
  284. it('should validate v6 schema', function() {
  285. var ajv = new Ajv;
  286. ajv.validateSchema({ contains: { minimum: 2 } }) .should.equal(true);
  287. ajv.validateSchema({ contains: 2 }). should.equal(false);
  288. });
  289. it('should use option meta as default meta schema', function() {
  290. var meta = {
  291. $schema: 'http://json-schema.org/draft-07/schema',
  292. properties: {
  293. myKeyword: { type: 'boolean' }
  294. }
  295. };
  296. var ajv = new Ajv({ meta: meta });
  297. ajv.validateSchema({ myKeyword: true }) .should.equal(true);
  298. ajv.validateSchema({ myKeyword: 2 }) .should.equal(false);
  299. ajv.validateSchema({
  300. $schema: 'http://json-schema.org/draft-07/schema',
  301. myKeyword: 2
  302. }) .should.equal(true);
  303. ajv = new Ajv;
  304. ajv.validateSchema({ myKeyword: true }) .should.equal(true);
  305. ajv.validateSchema({ myKeyword: 2 }) .should.equal(true);
  306. });
  307. });
  308. describe('schemas', function() {
  309. it('should add schemas from object', function() {
  310. var ajv = new Ajv({ schemas: {
  311. int: { type: 'integer' },
  312. str: { type: 'string' }
  313. }});
  314. ajv.validate('int', 123) .should.equal(true);
  315. ajv.validate('int', 'foo') .should.equal(false);
  316. ajv.validate('str', 'foo') .should.equal(true);
  317. ajv.validate('str', 123) .should.equal(false);
  318. });
  319. it('should add schemas from array', function() {
  320. var ajv = new Ajv({ schemas: [
  321. { $id: 'int', type: 'integer' },
  322. { $id: 'str', type: 'string' },
  323. { $id: 'obj', properties: { int: { $ref: 'int' }, str: { $ref: 'str' } } }
  324. ]});
  325. ajv.validate('obj', { int: 123, str: 'foo' }) .should.equal(true);
  326. ajv.validate('obj', { int: 'foo', str: 'bar' }) .should.equal(false);
  327. ajv.validate('obj', { int: 123, str: 456 }) .should.equal(false);
  328. });
  329. });
  330. describe('format', function() {
  331. it('should not validate formats if option format == false', function() {
  332. var ajv = new Ajv
  333. , ajvFF = new Ajv({ format: false });
  334. var schema = { format: 'date-time' };
  335. var invalideDateTime = '06/19/1963 08:30:06 PST';
  336. ajv.validate(schema, invalideDateTime) .should.equal(false);
  337. ajvFF.validate(schema, invalideDateTime) .should.equal(true);
  338. });
  339. });
  340. describe('formats', function() {
  341. it('should add formats from options', function() {
  342. var ajv = new Ajv({ formats: {
  343. identifier: /^[a-z_$][a-z0-9_$]*$/i
  344. }});
  345. var validate = ajv.compile({ format: 'identifier' });
  346. validate('Abc1') .should.equal(true);
  347. validate('123') .should.equal(false);
  348. validate(123) .should.equal(true);
  349. });
  350. });
  351. describe('missingRefs', function() {
  352. it('should throw if ref is missing without this option', function() {
  353. var ajv = new Ajv;
  354. should.throw(function() {
  355. ajv.compile({ $ref: 'missing_reference' });
  356. });
  357. });
  358. it('should not throw and pass validation with missingRef == "ignore"', function() {
  359. testMissingRefsIgnore(new Ajv({ missingRefs: 'ignore' }));
  360. testMissingRefsIgnore(new Ajv({ missingRefs: 'ignore', allErrors: true }));
  361. function testMissingRefsIgnore(ajv) {
  362. var validate = ajv.compile({ $ref: 'missing_reference' });
  363. validate({}) .should.equal(true);
  364. }
  365. });
  366. it('should not throw and fail validation with missingRef == "fail" if the ref is used', function() {
  367. testMissingRefsFail(new Ajv({ missingRefs: 'fail' }));
  368. testMissingRefsFail(new Ajv({ missingRefs: 'fail', verbose: true }));
  369. testMissingRefsFail(new Ajv({ missingRefs: 'fail', allErrors: true }));
  370. testMissingRefsFail(new Ajv({ missingRefs: 'fail', allErrors: true, verbose: true }));
  371. function testMissingRefsFail(ajv) {
  372. var validate = ajv.compile({
  373. anyOf: [
  374. { type: 'number' },
  375. { $ref: 'missing_reference' }
  376. ]
  377. });
  378. validate(123) .should.equal(true);
  379. validate('foo') .should.equal(false);
  380. validate = ajv.compile({ $ref: 'missing_reference' });
  381. validate({}) .should.equal(false);
  382. }
  383. });
  384. });
  385. describe('uniqueItems', function() {
  386. it('should not validate uniqueItems with uniqueItems option == false', function() {
  387. testUniqueItems(new Ajv({ uniqueItems: false }));
  388. testUniqueItems(new Ajv({ uniqueItems: false, allErrors: true }));
  389. function testUniqueItems(ajv) {
  390. var validate = ajv.compile({ uniqueItems: true });
  391. validate([1,2,3]) .should.equal(true);
  392. validate([1,1,1]) .should.equal(true);
  393. }
  394. });
  395. });
  396. describe('unicode', function() {
  397. it('should use String.prototype.length with unicode option == false', function() {
  398. var ajvUnicode = new Ajv;
  399. testUnicode(new Ajv({ unicode: false }));
  400. testUnicode(new Ajv({ unicode: false, allErrors: true }));
  401. function testUnicode(ajv) {
  402. var validateWithUnicode = ajvUnicode.compile({ minLength: 2 });
  403. var validate = ajv.compile({ minLength: 2 });
  404. validateWithUnicode('😀') .should.equal(false);
  405. validate('😀') .should.equal(true);
  406. validateWithUnicode = ajvUnicode.compile({ maxLength: 1 });
  407. validate = ajv.compile({ maxLength: 1 });
  408. validateWithUnicode('😀') .should.equal(true);
  409. validate('😀') .should.equal(false);
  410. }
  411. });
  412. });
  413. describe('verbose', function() {
  414. it('should add schema, parentSchema and data to errors with verbose option == true', function() {
  415. testVerbose(new Ajv({ verbose: true }));
  416. testVerbose(new Ajv({ verbose: true, allErrors: true }));
  417. function testVerbose(ajv) {
  418. var schema = { properties: { foo: { minimum: 5 } } };
  419. var validate = ajv.compile(schema);
  420. var data = { foo: 3 };
  421. validate(data) .should.equal(false);
  422. validate.errors .should.have.length(1);
  423. var err = validate.errors[0];
  424. should.equal(err.schema, 5);
  425. err.parentSchema .should.eql({ minimum: 5 });
  426. err.parentSchema .should.equal(schema.properties.foo); // by reference
  427. should.equal(err.data, 3);
  428. }
  429. });
  430. });
  431. describe('multipleOfPrecision', function() {
  432. it('should allow for some deviation from 0 when validating multipleOf with value < 1', function() {
  433. test(new Ajv({ multipleOfPrecision: 7 }));
  434. test(new Ajv({ multipleOfPrecision: 7, allErrors: true }));
  435. function test(ajv) {
  436. var schema = { multipleOf: 0.01 };
  437. var validate = ajv.compile(schema);
  438. validate(4.18) .should.equal(true);
  439. validate(4.181) .should.equal(false);
  440. schema = { multipleOf: 0.0000001 };
  441. validate = ajv.compile(schema);
  442. validate(53.198098) .should.equal(true);
  443. validate(53.1980981) .should.equal(true);
  444. validate(53.19809811) .should.equal(false);
  445. }
  446. });
  447. });
  448. describe('useDefaults', function() {
  449. it('should replace undefined property with default value', function() {
  450. var instances = getAjvInstances({
  451. allErrors: true,
  452. loopRequired: 3
  453. }, { useDefaults: true });
  454. instances.forEach(test);
  455. function test(ajv) {
  456. var schema = {
  457. properties: {
  458. foo: { type: 'string', default: 'abc' },
  459. bar: { type: 'number', default: 1 },
  460. baz: { type: 'boolean', default: false },
  461. nil: { type: 'null', default: null },
  462. obj: { type: 'object', default: {} },
  463. arr: { type: 'array', default: [] }
  464. },
  465. required: ['foo', 'bar', 'baz', 'nil', 'obj', 'arr'],
  466. minProperties: 6
  467. };
  468. var validate = ajv.compile(schema);
  469. var data = {};
  470. validate(data) .should.equal(true);
  471. data .should.eql({ foo: 'abc', bar: 1, baz: false, nil: null, obj: {}, arr:[] });
  472. data = { foo: 'foo', bar: 2, obj: { test: true } };
  473. validate(data) .should.equal(true);
  474. data .should.eql({ foo: 'foo', bar: 2, baz: false, nil: null, obj: { test: true }, arr:[] });
  475. }
  476. });
  477. it('should replace undefined item with default value', function() {
  478. test(new Ajv({ useDefaults: true }));
  479. test(new Ajv({ useDefaults: true, allErrors: true }));
  480. function test(ajv) {
  481. var schema = {
  482. items: [
  483. { type: 'string', default: 'abc' },
  484. { type: 'number', default: 1 },
  485. { type: 'boolean', default: false }
  486. ],
  487. minItems: 3
  488. };
  489. var validate = ajv.compile(schema);
  490. var data = [];
  491. validate(data) .should.equal(true);
  492. data .should.eql([ 'abc', 1, false ]);
  493. data = [ 'foo' ];
  494. validate(data) .should.equal(true);
  495. data .should.eql([ 'foo', 1, false ]);
  496. data = ['foo', 2,'false'];
  497. validate(data) .should.equal(false);
  498. validate.errors .should.have.length(1);
  499. data .should.eql([ 'foo', 2, 'false' ]);
  500. }
  501. });
  502. it('should apply default in "then" subschema (issue #635)', function() {
  503. test(new Ajv({ useDefaults: true }));
  504. test(new Ajv({ useDefaults: true, allErrors: true }));
  505. function test(ajv) {
  506. var schema = {
  507. if: { required: ['foo'] },
  508. then: {
  509. properties: {
  510. bar: { default: 2 }
  511. }
  512. },
  513. else: {
  514. properties: {
  515. foo: { default: 1 }
  516. }
  517. }
  518. };
  519. var validate = ajv.compile(schema);
  520. var data = {};
  521. validate(data) .should.equal(true);
  522. data .should.eql({foo: 1});
  523. data = {foo: 1};
  524. validate(data) .should.equal(true);
  525. data .should.eql({foo: 1, bar: 2});
  526. }
  527. });
  528. describe('useDefaults: by value / by reference', function() {
  529. describe('using by value', function() {
  530. it('should NOT modify underlying defaults when modifying validated data', function() {
  531. test('value', new Ajv({ useDefaults: true }));
  532. test('value', new Ajv({ useDefaults: true, allErrors: true }));
  533. });
  534. });
  535. describe('using by reference', function() {
  536. it('should modify underlying defaults when modifying validated data', function() {
  537. test('reference', new Ajv({ useDefaults: 'shared' }));
  538. test('reference', new Ajv({ useDefaults: 'shared', allErrors: true }));
  539. });
  540. });
  541. function test(useDefaultsMode, ajv) {
  542. var schema = {
  543. properties: {
  544. items: {
  545. type: 'array',
  546. default: ['a-default']
  547. }
  548. }
  549. };
  550. var validate = ajv.compile(schema);
  551. var data = {};
  552. validate(data) .should.equal(true);
  553. data.items .should.eql([ 'a-default' ]);
  554. data.items.push('another-value');
  555. data.items .should.eql([ 'a-default', 'another-value' ]);
  556. var data2 = {};
  557. validate(data2) .should.equal(true);
  558. if (useDefaultsMode == 'reference')
  559. data2.items .should.eql([ 'a-default', 'another-value' ]);
  560. else if (useDefaultsMode == 'value')
  561. data2.items .should.eql([ 'a-default' ]);
  562. else
  563. throw new Error('unknown useDefaults mode');
  564. }
  565. });
  566. });
  567. describe('addUsedSchema', function() {
  568. [true, undefined].forEach(function (optionValue) {
  569. describe('= ' + optionValue, function() {
  570. var ajv;
  571. beforeEach(function() {
  572. ajv = new Ajv({ addUsedSchema: optionValue });
  573. });
  574. describe('compile and validate', function() {
  575. it('should add schema', function() {
  576. var schema = { $id: 'str', type: 'string' };
  577. var validate = ajv.compile(schema);
  578. validate('abc') .should.equal(true);
  579. validate(1) .should.equal(false);
  580. ajv.getSchema('str') .should.equal(validate);
  581. schema = { $id: 'int', type: 'integer' };
  582. ajv.validate(schema, 1) .should.equal(true);
  583. ajv.validate(schema, 'abc') .should.equal(false);
  584. ajv.getSchema('int') .should.be.a('function');
  585. });
  586. it('should throw with duplicate ID', function() {
  587. ajv.compile({ $id: 'str', type: 'string' });
  588. should.throw(function() {
  589. ajv.compile({ $id: 'str', minLength: 2 });
  590. });
  591. var schema = { $id: 'int', type: 'integer' };
  592. var schema2 = { $id: 'int', minimum: 0 };
  593. ajv.validate(schema, 1) .should.equal(true);
  594. should.throw(function() {
  595. ajv.validate(schema2, 1);
  596. });
  597. });
  598. });
  599. });
  600. });
  601. describe('= false', function() {
  602. var ajv;
  603. beforeEach(function() {
  604. ajv = new Ajv({ addUsedSchema: false });
  605. });
  606. describe('compile and validate', function() {
  607. it('should NOT add schema', function() {
  608. var schema = { $id: 'str', type: 'string' };
  609. var validate = ajv.compile(schema);
  610. validate('abc') .should.equal(true);
  611. validate(1) .should.equal(false);
  612. should.equal(ajv.getSchema('str'), undefined);
  613. schema = { $id: 'int', type: 'integer' };
  614. ajv.validate(schema, 1) .should.equal(true);
  615. ajv.validate(schema, 'abc') .should.equal(false);
  616. should.equal(ajv.getSchema('int'), undefined);
  617. });
  618. it('should NOT throw with duplicate ID', function() {
  619. ajv.compile({ $id: 'str', type: 'string' });
  620. should.not.throw(function() {
  621. ajv.compile({ $id: 'str', minLength: 2 });
  622. });
  623. var schema = { $id: 'int', type: 'integer' };
  624. var schema2 = { $id: 'int', minimum: 0 };
  625. ajv.validate(schema, 1) .should.equal(true);
  626. should.not.throw(function() {
  627. ajv.validate(schema2, 1) .should.equal(true);
  628. });
  629. });
  630. });
  631. });
  632. });
  633. describe('passContext', function() {
  634. var ajv, contexts;
  635. beforeEach(function() {
  636. contexts = [];
  637. });
  638. describe('= true', function() {
  639. it('should pass this value as context to custom keyword validation function', function() {
  640. var validate = getValidate(true);
  641. var self = {};
  642. validate.call(self, {});
  643. contexts .should.have.length(4);
  644. contexts.forEach(function(ctx) {
  645. ctx .should.equal(self);
  646. });
  647. });
  648. });
  649. describe('= false', function() {
  650. it('should pass ajv instance as context to custom keyword validation function', function() {
  651. var validate = getValidate(false);
  652. var self = {};
  653. validate.call(self, {});
  654. contexts .should.have.length(4);
  655. contexts.forEach(function(ctx) {
  656. ctx .should.equal(ajv);
  657. });
  658. });
  659. });
  660. function getValidate(passContext) {
  661. ajv = new Ajv({ passContext: passContext, inlineRefs: false });
  662. ajv.addKeyword('testValidate', { validate: storeContext });
  663. ajv.addKeyword('testCompile', { compile: compileTestValidate });
  664. var schema = {
  665. definitions: {
  666. test1: {
  667. testValidate: true,
  668. testCompile: true,
  669. },
  670. test2: {
  671. allOf: [ { $ref: '#/definitions/test1' } ]
  672. }
  673. },
  674. allOf: [
  675. { $ref: '#/definitions/test1' },
  676. { $ref: '#/definitions/test2' }
  677. ]
  678. };
  679. return ajv.compile(schema);
  680. }
  681. function storeContext() {
  682. contexts.push(this);
  683. return true;
  684. }
  685. function compileTestValidate() {
  686. return storeContext;
  687. }
  688. });
  689. describe('allErrors', function() {
  690. it('should be disabled inside "not" keyword', function() {
  691. test(new Ajv, false);
  692. test(new Ajv({ allErrors: true }), true);
  693. function test(ajv, allErrors) {
  694. var format1called = false
  695. , format2called = false;
  696. ajv.addFormat('format1', function() {
  697. format1called = true;
  698. return false;
  699. });
  700. ajv.addFormat('format2', function() {
  701. format2called = true;
  702. return false;
  703. });
  704. var schema1 = {
  705. allOf: [
  706. { format: 'format1' },
  707. { format: 'format2' }
  708. ]
  709. };
  710. ajv.validate(schema1, 'abc') .should.equal(false);
  711. ajv.errors .should.have.length(allErrors ? 2 : 1);
  712. format1called .should.equal(true);
  713. format2called .should.equal(allErrors);
  714. var schema2 = {
  715. not: schema1
  716. };
  717. format1called = format2called = false;
  718. ajv.validate(schema2, 'abc') .should.equal(true);
  719. should.equal(ajv.errors, null);
  720. format1called .should.equal(true);
  721. format2called .should.equal(false);
  722. }
  723. });
  724. });
  725. describe('extendRefs', function() {
  726. describe('= true', function() {
  727. it('should allow extending $ref with other keywords', function() {
  728. test(new Ajv({ extendRefs: true }), true);
  729. });
  730. it('should NOT log warning if extendRefs is true', function() {
  731. testWarning(new Ajv({ extendRefs: true }));
  732. });
  733. });
  734. describe('= "ignore" and default', function() {
  735. it('should ignore other keywords when $ref is used', function() {
  736. test(new Ajv);
  737. test(new Ajv({ extendRefs: 'ignore' }), false);
  738. });
  739. it('should log warning when other keywords are used with $ref', function() {
  740. testWarning(new Ajv, /keywords\signored/);
  741. testWarning(new Ajv({ extendRefs: 'ignore' }), /keywords\signored/);
  742. });
  743. });
  744. describe('= "fail"', function() {
  745. it('should fail schema compilation if other keywords are used with $ref', function() {
  746. testFail(new Ajv({ extendRefs: 'fail' }));
  747. function testFail(ajv) {
  748. should.throw(function() {
  749. var schema = {
  750. "definitions": {
  751. "int": { "type": "integer" }
  752. },
  753. "$ref": "#/definitions/int",
  754. "minimum": 10
  755. };
  756. ajv.compile(schema);
  757. });
  758. should.not.throw(function() {
  759. var schema = {
  760. "definitions": {
  761. "int": { "type": "integer" }
  762. },
  763. "allOf": [
  764. { "$ref": "#/definitions/int" },
  765. { "minimum": 10 }
  766. ]
  767. };
  768. ajv.compile(schema);
  769. });
  770. }
  771. });
  772. });
  773. function test(ajv, shouldExtendRef) {
  774. var schema = {
  775. "definitions": {
  776. "int": { "type": "integer" }
  777. },
  778. "$ref": "#/definitions/int",
  779. "minimum": 10
  780. };
  781. var validate = ajv.compile(schema);
  782. validate(10) .should.equal(true);
  783. validate(1) .should.equal(!shouldExtendRef);
  784. schema = {
  785. "definitions": {
  786. "int": { "type": "integer" }
  787. },
  788. "type": "object",
  789. "properties": {
  790. "foo": {
  791. "$ref": "#/definitions/int",
  792. "minimum": 10
  793. },
  794. "bar": {
  795. "allOf": [
  796. { "$ref": "#/definitions/int" },
  797. { "minimum": 10 }
  798. ]
  799. }
  800. }
  801. };
  802. validate = ajv.compile(schema);
  803. validate({ foo: 10, bar: 10 }) .should.equal(true);
  804. validate({ foo: 1, bar: 10 }) .should.equal(!shouldExtendRef);
  805. validate({ foo: 10, bar: 1 }) .should.equal(false);
  806. }
  807. function testWarning(ajv, msgPattern) {
  808. var oldConsole;
  809. try {
  810. oldConsole = console.warn;
  811. var consoleMsg;
  812. console.warn = function() {
  813. consoleMsg = Array.prototype.join.call(arguments, ' ');
  814. };
  815. var schema = {
  816. "definitions": {
  817. "int": { "type": "integer" }
  818. },
  819. "$ref": "#/definitions/int",
  820. "minimum": 10
  821. };
  822. ajv.compile(schema);
  823. if (msgPattern) consoleMsg .should.match(msgPattern);
  824. else should.not.exist(consoleMsg);
  825. } finally {
  826. console.warn = oldConsole;
  827. }
  828. }
  829. });
  830. describe('sourceCode', function() {
  831. describe('= true', function() {
  832. it('should add source.code property', function() {
  833. test(new Ajv({sourceCode: true}));
  834. function test(ajv) {
  835. var validate = ajv.compile({ "type": "number" });
  836. validate.source.code .should.be.a('string');
  837. }
  838. });
  839. });
  840. describe('= false and default', function() {
  841. it('should not add source and sourceCode properties', function() {
  842. test(new Ajv);
  843. test(new Ajv({sourceCode: false}));
  844. function test(ajv) {
  845. var validate = ajv.compile({ "type": "number" });
  846. should.not.exist(validate.source);
  847. should.not.exist(validate.sourceCode);
  848. }
  849. });
  850. });
  851. });
  852. describe('unknownFormats', function() {
  853. describe('= true (default)', function() {
  854. it('should fail schema compilation if unknown format is used', function() {
  855. test(new Ajv);
  856. test(new Ajv({unknownFormats: true}));
  857. function test(ajv) {
  858. should.throw(function() {
  859. ajv.compile({ format: 'unknown' });
  860. });
  861. }
  862. });
  863. it('should fail validation if unknown format is used via $data', function() {
  864. test(new Ajv({$data: true}));
  865. test(new Ajv({$data: true, unknownFormats: true}));
  866. function test(ajv) {
  867. var validate = ajv.compile({
  868. properties: {
  869. foo: { format: { $data: '1/bar' } },
  870. bar: { type: 'string' }
  871. }
  872. });
  873. validate({foo: 1, bar: 'unknown'}) .should.equal(false);
  874. validate({foo: '2016-10-16', bar: 'date'}) .should.equal(true);
  875. validate({foo: '20161016', bar: 'date'}) .should.equal(false);
  876. validate({foo: '20161016'}) .should.equal(true);
  877. validate({foo: '2016-10-16', bar: 'unknown'}) .should.equal(false);
  878. }
  879. });
  880. });
  881. describe('= "ignore (default before 5.0.0)"', function() {
  882. it('should pass schema compilation and be valid if unknown format is used', function() {
  883. test(new Ajv({unknownFormats: 'ignore'}));
  884. function test(ajv) {
  885. var validate = ajv.compile({ format: 'unknown' });
  886. validate('anything') .should.equal(true);
  887. }
  888. });
  889. it('should be valid if unknown format is used via $data', function() {
  890. test(new Ajv({$data: true, unknownFormats: 'ignore'}));
  891. function test(ajv) {
  892. var validate = ajv.compile({
  893. properties: {
  894. foo: { format: { $data: '1/bar' } },
  895. bar: { type: 'string' }
  896. }
  897. });
  898. validate({foo: 1, bar: 'unknown'}) .should.equal(true);
  899. validate({foo: '2016-10-16', bar: 'date'}) .should.equal(true);
  900. validate({foo: '20161016', bar: 'date'}) .should.equal(false);
  901. validate({foo: '20161016'}) .should.equal(true);
  902. validate({foo: '2016-10-16', bar: 'unknown'}) .should.equal(true);
  903. }
  904. });
  905. });
  906. describe('= [String]', function() {
  907. it('should pass schema compilation and be valid if whitelisted unknown format is used', function() {
  908. test(new Ajv({unknownFormats: ['allowed']}));
  909. function test(ajv) {
  910. var validate = ajv.compile({ format: 'allowed' });
  911. validate('anything') .should.equal(true);
  912. should.throw(function() {
  913. ajv.compile({ format: 'unknown' });
  914. });
  915. }
  916. });
  917. it('should be valid if whitelisted unknown format is used via $data', function() {
  918. test(new Ajv({$data: true, unknownFormats: ['allowed']}));
  919. function test(ajv) {
  920. var validate = ajv.compile({
  921. properties: {
  922. foo: { format: { $data: '1/bar' } },
  923. bar: { type: 'string' }
  924. }
  925. });
  926. validate({foo: 1, bar: 'allowed'}) .should.equal(true);
  927. validate({foo: 1, bar: 'unknown'}) .should.equal(false);
  928. validate({foo: '2016-10-16', bar: 'date'}) .should.equal(true);
  929. validate({foo: '20161016', bar: 'date'}) .should.equal(false);
  930. validate({foo: '20161016'}) .should.equal(true);
  931. validate({foo: '2016-10-16', bar: 'allowed'}) .should.equal(true);
  932. validate({foo: '2016-10-16', bar: 'unknown'}) .should.equal(false);
  933. }
  934. });
  935. });
  936. });
  937. describe('processCode', function() {
  938. it('should process generated code', function() {
  939. var ajv = new Ajv;
  940. var validate = ajv.compile({type: 'string'});
  941. validate.toString().split('\n').length .should.equal(1);
  942. var beautify = require('js-beautify').js_beautify;
  943. var ajvPC = new Ajv({processCode: beautify});
  944. validate = ajvPC.compile({type: 'string'});
  945. validate.toString().split('\n').length .should.be.above(1);
  946. validate('foo') .should.equal(true);
  947. validate(1) .should.equal(false);
  948. });
  949. });
  950. describe('serialize', function() {
  951. var serializeCalled;
  952. it('should use custom function to serialize schema to string', function() {
  953. serializeCalled = undefined;
  954. var ajv = new Ajv({ serialize: serialize });
  955. ajv.addSchema({ type: 'string' });
  956. should.equal(serializeCalled, true);
  957. });
  958. function serialize(schema) {
  959. serializeCalled = true;
  960. return JSON.stringify(schema);
  961. }
  962. });
  963. describe('schemaId', function() {
  964. describe('= "$id" (default)', function() {
  965. it('should use $id and ignore id', function() {
  966. test(new Ajv);
  967. test(new Ajv({schemaId: '$id'}));
  968. function test(ajv) {
  969. ajv.addSchema({ $id: 'mySchema1', type: 'string' });
  970. var validate = ajv.getSchema('mySchema1');
  971. validate('foo') .should.equal(true);
  972. validate(1) .should.equal(false);
  973. validate = ajv.compile({ id: 'mySchema2', type: 'string' });
  974. should.not.exist(ajv.getSchema('mySchema2'));
  975. }
  976. });
  977. });
  978. describe('= "id"', function() {
  979. it('should use id and ignore $id', function() {
  980. var ajv = new Ajv({schemaId: 'id', meta: false});
  981. ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
  982. ajv._opts.defaultMeta = 'http://json-schema.org/draft-04/schema#';
  983. ajv.addSchema({ id: 'mySchema1', type: 'string' });
  984. var validate = ajv.getSchema('mySchema1');
  985. validate('foo') .should.equal(true);
  986. validate(1) .should.equal(false);
  987. validate = ajv.compile({ $id: 'mySchema2', type: 'string' });
  988. should.not.exist(ajv.getSchema('mySchema2'));
  989. });
  990. });
  991. describe('= "auto"', function() {
  992. it('should use both id and $id', function() {
  993. var ajv = new Ajv({schemaId: 'auto'});
  994. ajv.addSchema({ $id: 'mySchema1', type: 'string' });
  995. var validate = ajv.getSchema('mySchema1');
  996. validate('foo') .should.equal(true);
  997. validate(1) .should.equal(false);
  998. ajv.addSchema({ id: 'mySchema2', type: 'string' });
  999. validate = ajv.getSchema('mySchema2');
  1000. validate('foo') .should.equal(true);
  1001. validate(1) .should.equal(false);
  1002. });
  1003. it('should throw if both id and $id are available and different', function() {
  1004. var ajv = new Ajv({schemaId: 'auto'});
  1005. ajv.compile({
  1006. id: 'mySchema',
  1007. $id: 'mySchema'
  1008. });
  1009. should.throw(function() {
  1010. ajv.compile({
  1011. id: 'mySchema1',
  1012. $id: 'mySchema2'
  1013. });
  1014. });
  1015. });
  1016. });
  1017. });
  1018. describe('$comment', function() {
  1019. describe('= true', function() {
  1020. var logCalls, consoleLog;
  1021. beforeEach(function () {
  1022. consoleLog = console.log;
  1023. console.log = log;
  1024. });
  1025. afterEach(function () {
  1026. console.log = consoleLog;
  1027. });
  1028. function log() {
  1029. logCalls.push(Array.prototype.slice.call(arguments));
  1030. }
  1031. it('should log the text from $comment keyword', function() {
  1032. var schema = {
  1033. properties: {
  1034. foo: {$comment: 'property foo'},
  1035. bar: {$comment: 'property bar', type: 'integer'}
  1036. }
  1037. };
  1038. var ajv = new Ajv({$comment: true});
  1039. var fullAjv = new Ajv({allErrors: true, $comment: true});
  1040. [ajv, fullAjv].forEach(function (_ajv) {
  1041. var validate = _ajv.compile(schema);
  1042. test({}, true, []);
  1043. test({foo: 1}, true, [['property foo']]);
  1044. test({foo: 1, bar: 2}, true, [['property foo'], ['property bar']]);
  1045. test({foo: 1, bar: 'baz'}, false, [['property foo'], ['property bar']]);
  1046. function test(data, valid, expectedLogCalls) {
  1047. logCalls = [];
  1048. validate(data) .should.equal(valid);
  1049. logCalls .should.eql(expectedLogCalls);
  1050. }
  1051. });
  1052. console.log = consoleLog;
  1053. });
  1054. });
  1055. describe('function hook', function() {
  1056. var hookCalls;
  1057. function hook() {
  1058. hookCalls.push(Array.prototype.slice.call(arguments));
  1059. }
  1060. it('should pass the text from $comment keyword to the hook', function() {
  1061. var schema = {
  1062. properties: {
  1063. foo: {$comment: 'property foo'},
  1064. bar: {$comment: 'property bar', type: 'integer'}
  1065. }
  1066. };
  1067. var ajv = new Ajv({$comment: hook});
  1068. var fullAjv = new Ajv({allErrors: true, $comment: hook});
  1069. [ajv, fullAjv].forEach(function (_ajv) {
  1070. var validate = _ajv.compile(schema);
  1071. test({}, true, []);
  1072. test({foo: 1}, true, [['property foo', '#/properties/foo/$comment', schema]]);
  1073. test({foo: 1, bar: 2}, true,
  1074. [['property foo', '#/properties/foo/$comment', schema],
  1075. ['property bar', '#/properties/bar/$comment', schema]]);
  1076. test({foo: 1, bar: 'baz'}, false,
  1077. [['property foo', '#/properties/foo/$comment', schema],
  1078. ['property bar', '#/properties/bar/$comment', schema]]);
  1079. function test(data, valid, expectedHookCalls) {
  1080. hookCalls = [];
  1081. validate(data) .should.equal(valid);
  1082. hookCalls .should.eql(expectedHookCalls);
  1083. }
  1084. });
  1085. });
  1086. });
  1087. });
  1088. describe('logger', function() {
  1089. /**
  1090. * The logger option tests are based on the meta scenario which writes into the logger.warn
  1091. */
  1092. var origConsoleWarn = console.warn;
  1093. var consoleCalled;
  1094. beforeEach(function() {
  1095. consoleCalled = false;
  1096. console.warn = function() {
  1097. consoleCalled = true;
  1098. };
  1099. });
  1100. afterEach(function() {
  1101. console.warn = origConsoleWarn;
  1102. });
  1103. it('no custom logger is given - global console should be used', function() {
  1104. var ajv = new Ajv({
  1105. meta: false
  1106. });
  1107. ajv.compile({
  1108. type: 'number',
  1109. minimum: 1
  1110. });
  1111. should.equal(consoleCalled, true);
  1112. });
  1113. it('custom logger is an object - logs should only report to it', function() {
  1114. var loggerCalled = false;
  1115. var logger = {
  1116. warn: log,
  1117. log: log,
  1118. error: log
  1119. };
  1120. var ajv = new Ajv({
  1121. meta: false,
  1122. logger: logger
  1123. });
  1124. ajv.compile({
  1125. type: 'number',
  1126. minimum: 1
  1127. });
  1128. should.equal(loggerCalled, true);
  1129. should.equal(consoleCalled, false);
  1130. function log() {
  1131. loggerCalled = true;
  1132. }
  1133. });
  1134. it('logger option is false - no logs should be reported', function() {
  1135. var ajv = new Ajv({
  1136. meta: false,
  1137. logger: false
  1138. });
  1139. ajv.compile({
  1140. type: 'number',
  1141. minimum: 1
  1142. });
  1143. should.equal(consoleCalled, false);
  1144. });
  1145. it('logger option is an object without required methods - an error should be thrown', function() {
  1146. (function(){
  1147. new Ajv({
  1148. meta: false,
  1149. logger: {}
  1150. });
  1151. }).should.throw(Error, /logger must implement log, warn and error methods/);
  1152. });
  1153. });
  1154. });