properties-definition-validator.spec.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. import {expect} from 'chai';
  2. import {chai} from '../../../chai.js';
  3. import {DataType} from './data-type.js';
  4. import {format} from '@e22m4u/js-format';
  5. import {PropertyUniqueness} from './property-uniqueness.js';
  6. import {PropertyValidatorRegistry} from './property-validator/index.js';
  7. import {PropertyTransformerRegistry} from './property-transformer/index.js';
  8. import {PropertiesDefinitionValidator} from './properties-definition-validator.js';
  9. import {PrimaryKeysDefinitionValidator} from './primary-keys-definition-validator.js';
  10. const S = new PropertiesDefinitionValidator();
  11. const sandbox = chai.spy.sandbox();
  12. S.getService(PropertyValidatorRegistry).addValidator('myValidator', () => true);
  13. S.getService(PropertyTransformerRegistry).addTransformer(
  14. 'myTransformer',
  15. () => true,
  16. );
  17. describe('PropertiesDefinitionValidator', function () {
  18. afterEach(function () {
  19. sandbox.restore();
  20. });
  21. describe('validate', function () {
  22. it('requires a first argument to be a non-empty string', function () {
  23. const validate = v => () => S.validate(v, {});
  24. const error = v =>
  25. format(
  26. 'The first argument of PropertiesDefinitionValidator.validate ' +
  27. 'should be a non-empty String, but %s given.',
  28. v,
  29. );
  30. expect(validate('')).to.throw(error('""'));
  31. expect(validate(10)).to.throw(error('10'));
  32. expect(validate(true)).to.throw(error('true'));
  33. expect(validate(false)).to.throw(error('false'));
  34. expect(validate([])).to.throw(error('Array'));
  35. expect(validate({})).to.throw(error('Object'));
  36. expect(validate(undefined)).to.throw(error('undefined'));
  37. expect(validate(null)).to.throw(error('null'));
  38. validate('model')();
  39. });
  40. it('requires a second argument to be an object', function () {
  41. const validate = v => () => S.validate('model', v);
  42. const error = v =>
  43. format(
  44. 'The provided option "properties" of the model "model" ' +
  45. 'should be an Object, but %s given.',
  46. v,
  47. );
  48. expect(validate('str')).to.throw(error('"str"'));
  49. expect(validate(10)).to.throw(error('10'));
  50. expect(validate(true)).to.throw(error('true'));
  51. expect(validate(false)).to.throw(error('false'));
  52. expect(validate([])).to.throw(error('Array'));
  53. expect(validate(undefined)).to.throw(error('undefined'));
  54. expect(validate(null)).to.throw(error('null'));
  55. validate({})();
  56. });
  57. it('requires a property name as a non-empty string', function () {
  58. const validate = v => () => S.validate('model', v);
  59. const error = v =>
  60. format(
  61. 'The property name of the model "model" should be ' +
  62. 'a non-empty String, but %s given.',
  63. v,
  64. );
  65. expect(validate({['']: {}})).to.throw(error('""'));
  66. validate({foo: DataType.STRING})();
  67. });
  68. it('requires a property definition', function () {
  69. const validate = v => () => S.validate('model', {foo: v});
  70. const error = v =>
  71. format(
  72. 'The property "foo" of the model "model" should have ' +
  73. 'a property definition, but %s given.',
  74. v,
  75. );
  76. expect(validate(undefined)).to.throw(error('undefined'));
  77. expect(validate(null)).to.throw(error('null'));
  78. validate(DataType.STRING)();
  79. validate({type: DataType.STRING})();
  80. });
  81. it('expects a short property definition to be DataType', function () {
  82. const validate = v => () => S.validate('model', {foo: v});
  83. const error = v =>
  84. format(
  85. 'In case of a short property definition, the property "foo" ' +
  86. 'of the model "model" should have one of data types: %l, but %s given.',
  87. Object.values(DataType),
  88. v,
  89. );
  90. expect(validate('invalid')).to.throw(error('"invalid"'));
  91. validate(DataType.STRING)();
  92. });
  93. it('expects a full property definition to be an object', function () {
  94. const validate = v => () => S.validate('model', {foo: v});
  95. const error = v =>
  96. format(
  97. 'In case of a full property definition, the property "foo" ' +
  98. 'of the model "model" should be an Object, but %s given.',
  99. v,
  100. );
  101. expect(validate(10)).to.throw(error('10'));
  102. expect(validate(true)).to.throw(error('true'));
  103. expect(validate([])).to.throw(error('Array'));
  104. validate({type: DataType.STRING})();
  105. });
  106. it('requires the option "type" to be a DataType', function () {
  107. const validate = v => () => S.validate('model', {foo: {type: v}});
  108. const error = v =>
  109. format(
  110. 'The property "foo" of the model "model" requires the option "type" ' +
  111. 'to have one of data types: %l, but %s given.',
  112. Object.values(DataType),
  113. v,
  114. );
  115. expect(validate('str')).to.throw(error('"str"'));
  116. expect(validate(10)).to.throw(error('10'));
  117. expect(validate(true)).to.throw(error('true'));
  118. expect(validate(false)).to.throw(error('false'));
  119. expect(validate([])).to.throw(error('Array'));
  120. expect(validate({})).to.throw(error('Object'));
  121. expect(validate(undefined)).to.throw(error('undefined'));
  122. expect(validate(null)).to.throw(error('null'));
  123. validate(DataType.STRING)();
  124. });
  125. it('expects the provided option "itemType" to be a DataType', function () {
  126. const validate = v => {
  127. const foo = {type: DataType.ARRAY, itemType: v};
  128. return () => S.validate('model', {foo});
  129. };
  130. const error = v =>
  131. format(
  132. 'The provided option "itemType" of the property "foo" in the model "model" ' +
  133. 'should have one of data types: %l, but %s given.',
  134. Object.values(DataType),
  135. v,
  136. );
  137. expect(validate('str')).to.throw(error('"str"'));
  138. expect(validate(10)).to.throw(error('10'));
  139. expect(validate(true)).to.throw(error('true'));
  140. expect(validate([])).to.throw(error('Array'));
  141. expect(validate({})).to.throw(error('Object'));
  142. validate(DataType.STRING)();
  143. });
  144. it('expects the provided option "itemModel" to be a string', function () {
  145. const validate = v => {
  146. const foo = {
  147. type: DataType.ARRAY,
  148. itemType: DataType.OBJECT,
  149. itemModel: v,
  150. };
  151. return () => S.validate('model', {foo});
  152. };
  153. const error = v =>
  154. format(
  155. 'The provided option "itemModel" of the property "foo" ' +
  156. 'in the model "model" should be a String, but %s given.',
  157. v,
  158. );
  159. expect(validate(10)).to.throw(error('10'));
  160. expect(validate(true)).to.throw(error('true'));
  161. expect(validate([])).to.throw(error('Array'));
  162. expect(validate({})).to.throw(error('Object'));
  163. validate('model')();
  164. });
  165. it('expects the provided option "model" to be a string', function () {
  166. const validate = v => {
  167. const foo = {
  168. type: DataType.OBJECT,
  169. model: v,
  170. };
  171. return () => S.validate('model', {foo});
  172. };
  173. const error = v =>
  174. format(
  175. 'The provided option "model" of the property "foo" in the model "model" ' +
  176. 'should be a String, but %s given.',
  177. v,
  178. );
  179. expect(validate(10)).to.throw(error('10'));
  180. expect(validate(true)).to.throw(error('true'));
  181. expect(validate([])).to.throw(error('Array'));
  182. expect(validate({})).to.throw(error('Object'));
  183. validate('model')();
  184. });
  185. it('expects the provided option "primaryKey" to be a boolean', function () {
  186. const validate = v => {
  187. const foo = {
  188. type: DataType.STRING,
  189. primaryKey: v,
  190. };
  191. return () => S.validate('model', {foo});
  192. };
  193. const error = v =>
  194. format(
  195. 'The provided option "primaryKey" of the property "foo" in the model "model" ' +
  196. 'should be a Boolean, but %s given.',
  197. v,
  198. );
  199. expect(validate(10)).to.throw(error('10'));
  200. expect(validate([])).to.throw(error('Array'));
  201. expect(validate({})).to.throw(error('Object'));
  202. validate(true)();
  203. validate(false)();
  204. });
  205. it('expects the provided option "columnName" to be a string', function () {
  206. const validate = v => {
  207. const foo = {
  208. type: DataType.STRING,
  209. columnName: v,
  210. };
  211. return () => S.validate('model', {foo});
  212. };
  213. const error = v =>
  214. format(
  215. 'The provided option "columnName" of the property "foo" in the model "model" ' +
  216. 'should be a String, but %s given.',
  217. v,
  218. );
  219. expect(validate(10)).to.throw(error('10'));
  220. expect(validate(true)).to.throw(error('true'));
  221. expect(validate([])).to.throw(error('Array'));
  222. expect(validate({})).to.throw(error('Object'));
  223. validate('columnName')();
  224. });
  225. it('expects the provided option "columnType" to be a string', function () {
  226. const validate = v => {
  227. const foo = {
  228. type: DataType.STRING,
  229. columnType: v,
  230. };
  231. return () => S.validate('model', {foo});
  232. };
  233. const error = v =>
  234. format(
  235. 'The provided option "columnType" of the property "foo" in the model "model" ' +
  236. 'should be a String, but %s given.',
  237. v,
  238. );
  239. expect(validate(10)).to.throw(error('10'));
  240. expect(validate(true)).to.throw(error('true'));
  241. expect(validate([])).to.throw(error('Array'));
  242. expect(validate({})).to.throw(error('Object'));
  243. validate('columnType')();
  244. });
  245. it('expects the provided option "required" to be a boolean', function () {
  246. const validate = v => {
  247. const foo = {
  248. type: DataType.STRING,
  249. required: v,
  250. };
  251. return () => S.validate('model', {foo});
  252. };
  253. const error = v =>
  254. format(
  255. 'The provided option "required" of the property "foo" in the model "model" ' +
  256. 'should be a Boolean, but %s given.',
  257. v,
  258. );
  259. expect(validate('str')).to.throw(error('"str"'));
  260. expect(validate(10)).to.throw(error('10'));
  261. expect(validate([])).to.throw(error('Array'));
  262. expect(validate({})).to.throw(error('Object'));
  263. validate(true)();
  264. validate(false)();
  265. });
  266. it('expects the required property should not have the option "default" to be provided', function () {
  267. const validate = v => () => {
  268. const foo = {
  269. type: DataType.ANY,
  270. required: true,
  271. default: v,
  272. };
  273. S.validate('model', {foo});
  274. };
  275. const error = format(
  276. 'The property "foo" of the model "model" is a required property, ' +
  277. 'so it should not have the option "default" to be provided.',
  278. );
  279. expect(validate('str')).to.throw(error);
  280. expect(validate(10)).to.throw(error);
  281. expect(validate(true)).to.throw(error);
  282. expect(validate(false)).to.throw(error);
  283. expect(validate([])).to.throw(error);
  284. expect(validate({})).to.throw(error);
  285. expect(validate(null)).to.throw(error);
  286. validate(undefined)();
  287. });
  288. it('expects the primary key should not have the option "required" to be true', function () {
  289. const validate = v => () => {
  290. const foo = {
  291. type: DataType.ANY,
  292. primaryKey: true,
  293. required: v,
  294. };
  295. S.validate('model', {foo});
  296. };
  297. const error = format(
  298. 'The property "foo" of the model "model" is a primary key, ' +
  299. 'so it should not have the option "required" to be provided.',
  300. );
  301. expect(validate(true)).to.throw(error);
  302. validate(false)();
  303. validate(undefined)();
  304. });
  305. it('expects the primary key should not have the option "default" to be provided', function () {
  306. const validate = v => () => {
  307. const foo = {
  308. type: DataType.ANY,
  309. primaryKey: true,
  310. default: v,
  311. };
  312. S.validate('model', {foo});
  313. };
  314. const error = format(
  315. 'The property "foo" of the model "model" is a primary key, ' +
  316. 'so it should not have the option "default" to be provided.',
  317. );
  318. expect(validate('str')).to.throw(error);
  319. expect(validate(10)).to.throw(error);
  320. expect(validate(true)).to.throw(error);
  321. expect(validate(false)).to.throw(error);
  322. expect(validate([])).to.throw(error);
  323. expect(validate({})).to.throw(error);
  324. expect(validate(null)).to.throw(error);
  325. validate(undefined)();
  326. });
  327. it('expects a non-array property should not have the option "itemType" to be provided', function () {
  328. const validate = v => () => {
  329. const foo = {
  330. type: v,
  331. itemType: DataType.STRING,
  332. };
  333. S.validate('model', {foo});
  334. };
  335. const error =
  336. 'The property "foo" of the model "model" has a non-array type, ' +
  337. 'so it should not have the option "itemType" to be provided.';
  338. expect(validate(DataType.ANY)).to.throw(error);
  339. expect(validate(DataType.STRING)).to.throw(error);
  340. expect(validate(DataType.NUMBER)).to.throw(error);
  341. expect(validate(DataType.BOOLEAN)).to.throw(error);
  342. expect(validate(DataType.OBJECT)).to.throw(error);
  343. validate(DataType.ARRAY)();
  344. });
  345. it('the option "model" requires the "object" property type', function () {
  346. const validate = v => () => {
  347. const foo = {
  348. type: v,
  349. model: 'model',
  350. };
  351. S.validate('model', {foo});
  352. };
  353. const error = v =>
  354. format(
  355. 'The option "model" is not supported for %s property type, ' +
  356. 'so the property "foo" of the model "model" should not have ' +
  357. 'the option "model" to be provided.',
  358. v,
  359. );
  360. expect(validate(DataType.ANY)).to.throw(error('Any'));
  361. expect(validate(DataType.STRING)).to.throw(error('String'));
  362. expect(validate(DataType.NUMBER)).to.throw(error('Number'));
  363. expect(validate(DataType.BOOLEAN)).to.throw(error('Boolean'));
  364. validate(DataType.OBJECT)();
  365. });
  366. it('the option "itemModel" requires the "object" item type', function () {
  367. const validate = v => () => {
  368. const foo = {
  369. type: DataType.ARRAY,
  370. itemType: v,
  371. itemModel: 'model',
  372. };
  373. S.validate('model', {foo});
  374. };
  375. const errorForNonEmpty = v =>
  376. format(
  377. 'The provided option "itemModel" requires the option "itemType" ' +
  378. 'to be explicitly set to Object, but the property "foo" of ' +
  379. 'the model "model" has specified item type as %s.',
  380. v,
  381. );
  382. const errorForEmpty = format(
  383. 'The provided option "itemModel" requires the option "itemType" ' +
  384. 'to be explicitly set to Object, but the property "foo" of ' +
  385. 'the model "model" does not have specified item type.',
  386. );
  387. expect(validate(DataType.ANY)).to.throw(errorForNonEmpty('Any'));
  388. expect(validate(DataType.STRING)).to.throw(errorForNonEmpty('String'));
  389. expect(validate(DataType.NUMBER)).to.throw(errorForNonEmpty('Number'));
  390. expect(validate(DataType.BOOLEAN)).to.throw(errorForNonEmpty('Boolean'));
  391. expect(validate(DataType.ARRAY)).to.throw(errorForNonEmpty('Array'));
  392. expect(validate(undefined)).to.throw(errorForEmpty);
  393. expect(validate(null)).to.throw(errorForEmpty);
  394. validate(DataType.OBJECT)();
  395. });
  396. it('uses PrimaryKeysDefinitionValidator to validate primary keys', function () {
  397. const V = S.getService(PrimaryKeysDefinitionValidator);
  398. sandbox.on(V, 'validate');
  399. const propDefs = {};
  400. S.validate('model', propDefs);
  401. expect(V.validate).to.have.been.called.once;
  402. expect(V.validate).to.have.been.called.with.exactly('model', propDefs);
  403. });
  404. it('the option "validate" should have a non-empty String, an Array of String or an Object', function () {
  405. const validate = v => () => {
  406. const foo = {
  407. type: DataType.ANY,
  408. validate: v,
  409. };
  410. S.validate('model', {foo});
  411. };
  412. const error = v =>
  413. format(
  414. 'The provided option "validate" of the property "foo" in the model "model" ' +
  415. 'should be a non-empty String, an Array of String or an Object, ' +
  416. 'but %s given.',
  417. v,
  418. );
  419. expect(validate('')).to.throw(error('""'));
  420. expect(validate(10)).to.throw(error('10'));
  421. expect(validate(0)).to.throw(error('0'));
  422. expect(validate(true)).to.throw(error('true'));
  423. expect(validate(false)).to.throw(error('false'));
  424. expect(validate(() => undefined)).to.throw(error('Function'));
  425. validate('myValidator')();
  426. validate(['myValidator'])();
  427. validate([])();
  428. validate({myValidator: true})();
  429. validate({})();
  430. validate(null)();
  431. validate(undefined)();
  432. });
  433. it('the option "validate" requires only existing validator names', function () {
  434. const validate = v => () => {
  435. const foo = {
  436. type: DataType.ANY,
  437. validate: v,
  438. };
  439. S.validate('model', {foo});
  440. };
  441. const error = v => format('The property validator %s is not found.', v);
  442. expect(validate('unknown')).to.throw(error('"unknown"'));
  443. expect(validate({unknown: true})).to.throw(error('"unknown"'));
  444. expect(validate(['unknown'])).to.throw(error('"unknown"'));
  445. validate('myValidator')();
  446. validate(['myValidator'])();
  447. validate({myValidator: true})();
  448. });
  449. it('the option "transform" should have a non-empty String, an Array of String or an Object', function () {
  450. const validate = v => () => {
  451. const foo = {
  452. type: DataType.ANY,
  453. transform: v,
  454. };
  455. S.validate('model', {foo});
  456. };
  457. const error = v =>
  458. format(
  459. 'The provided option "transform" of the property "foo" in the model "model" ' +
  460. 'should be a non-empty String, an Array of String or an Object, ' +
  461. 'but %s given.',
  462. v,
  463. );
  464. expect(validate('')).to.throw(error('""'));
  465. expect(validate(10)).to.throw(error('10'));
  466. expect(validate(0)).to.throw(error('0'));
  467. expect(validate(true)).to.throw(error('true'));
  468. expect(validate(false)).to.throw(error('false'));
  469. expect(validate(() => undefined)).to.throw(error('Function'));
  470. validate('myTransformer')();
  471. validate(['myTransformer'])();
  472. validate([])();
  473. validate({myTransformer: true})();
  474. validate({})();
  475. validate(null)();
  476. validate(undefined)();
  477. });
  478. it('the option "transform" requires only existing transformer names', function () {
  479. const validate = v => () => {
  480. const foo = {
  481. type: DataType.ANY,
  482. transform: v,
  483. };
  484. S.validate('model', {foo});
  485. };
  486. const error = v => format('The property transformer %s is not found.', v);
  487. expect(validate('unknown')).to.throw(error('"unknown"'));
  488. expect(validate({unknown: true})).to.throw(error('"unknown"'));
  489. expect(validate(['unknown'])).to.throw(error('"unknown"'));
  490. validate('myTransformer')();
  491. validate(['myTransformer'])();
  492. validate({myTransformer: true})();
  493. });
  494. it('expects the provided option "unique" to be a Boolean or the PropertyUniqueness', function () {
  495. const validate = v => {
  496. const foo = {
  497. type: DataType.STRING,
  498. unique: v,
  499. };
  500. return () => S.validate('model', {foo});
  501. };
  502. const error = v =>
  503. format(
  504. 'The provided option "unique" of the property "foo" in the model "model" ' +
  505. 'should be a Boolean or one of values: %l, but %s given.',
  506. Object.values(PropertyUniqueness),
  507. v,
  508. );
  509. expect(validate('str')).to.throw(error('"str"'));
  510. expect(validate(10)).to.throw(error('10'));
  511. expect(validate([])).to.throw(error('Array'));
  512. expect(validate({})).to.throw(error('Object'));
  513. validate(true)();
  514. validate(false)();
  515. validate(PropertyUniqueness.STRICT)();
  516. validate(PropertyUniqueness.SPARSE)();
  517. validate(PropertyUniqueness.NON_UNIQUE)();
  518. });
  519. it('expects the primary key should not have the option "unique"', function () {
  520. const validate = v => () => {
  521. const foo = {
  522. type: DataType.ANY,
  523. primaryKey: true,
  524. unique: v,
  525. };
  526. S.validate('model', {foo});
  527. };
  528. const error = format(
  529. 'The property "foo" of the model "model" is a primary key, ' +
  530. 'so it should not have the option "unique" to be provided.',
  531. );
  532. expect(validate(true)).to.throw(error);
  533. expect(validate(PropertyUniqueness.STRICT)).to.throw(error);
  534. expect(validate(PropertyUniqueness.SPARSE)).to.throw(error);
  535. expect(validate(PropertyUniqueness.NON_UNIQUE)).to.throw(error);
  536. validate(false)();
  537. validate(undefined)();
  538. validate(null)();
  539. });
  540. });
  541. });