validation.test.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. 'use strict';
  2. const { expect } = require('chai');
  3. const sinon = require('sinon');
  4. const { DataTypes, Op, Sequelize } = require('@sequelize/core');
  5. const { rand, sequelize } = require('../../support');
  6. const dialect = sequelize.dialect;
  7. describe('InstanceValidator', () => {
  8. describe('validations', () => {
  9. const checks = {
  10. is: {
  11. spec: { args: ['[a-z]', 'i'] },
  12. fail: '0',
  13. pass: 'a',
  14. },
  15. not: {
  16. spec: { args: ['[a-z]', 'i'] },
  17. fail: 'a',
  18. pass: '0',
  19. },
  20. isEmail: {
  21. fail: 'a',
  22. pass: 'abc@abc.com',
  23. },
  24. isUrl: {
  25. fail: 'abc',
  26. pass: 'http://abc.com',
  27. },
  28. isIP: {
  29. fail: 'abc',
  30. pass: '129.89.23.1',
  31. },
  32. isIPv4: {
  33. fail: 'abc',
  34. pass: '129.89.23.1',
  35. },
  36. isIPv6: {
  37. fail: '1111:2222:3333::5555:',
  38. pass: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156',
  39. },
  40. isAlpha: {
  41. stringOrBoolean: true,
  42. spec: { args: 'en-GB' },
  43. fail: '012',
  44. pass: 'abc',
  45. },
  46. isAlphanumeric: {
  47. stringOrBoolean: true,
  48. spec: { args: 'en-GB' },
  49. fail: '_abc019',
  50. pass: 'abc019',
  51. },
  52. isNumeric: {
  53. fail: 'abc',
  54. pass: '019',
  55. },
  56. isInt: {
  57. fail: '9.2',
  58. pass: '-9',
  59. },
  60. isLowercase: {
  61. fail: 'AB',
  62. pass: 'ab',
  63. },
  64. isUppercase: {
  65. fail: 'ab',
  66. pass: 'AB',
  67. },
  68. isDecimal: {
  69. fail: 'a',
  70. pass: '0.2',
  71. },
  72. isFloat: {
  73. fail: 'a',
  74. pass: '9.2',
  75. },
  76. isNull: {
  77. fail: '0',
  78. pass: null,
  79. },
  80. notEmpty: {
  81. fail: ' ',
  82. pass: 'a',
  83. },
  84. equals: {
  85. spec: { args: 'bla bla bla' },
  86. fail: 'bla',
  87. pass: 'bla bla bla',
  88. },
  89. contains: {
  90. spec: { args: 'bla' },
  91. fail: 'la',
  92. pass: '0bla23',
  93. },
  94. notContains: {
  95. spec: { args: 'bla' },
  96. fail: '0bla23',
  97. pass: 'la',
  98. },
  99. regex: {
  100. spec: { args: ['[a-z]', 'i'] },
  101. fail: '0',
  102. pass: 'a',
  103. },
  104. notRegex: {
  105. spec: { args: ['[a-z]', 'i'] },
  106. fail: 'a',
  107. pass: '0',
  108. },
  109. len: {
  110. spec: { args: [2, 4] },
  111. fail: ['1', '12345'],
  112. pass: ['12', '123', '1234'],
  113. raw: true,
  114. },
  115. len$: {
  116. spec: [2, 4],
  117. fail: ['1', '12345'],
  118. pass: ['12', '123', '1234'],
  119. raw: true,
  120. },
  121. isUUID: {
  122. spec: { args: 4 },
  123. fail: 'f47ac10b-58cc-3372-a567-0e02b2c3d479',
  124. pass: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
  125. },
  126. isDate: {
  127. fail: 'not a date',
  128. pass: '2011-02-04',
  129. },
  130. isAfter: {
  131. spec: { args: '2011-11-05' },
  132. fail: '2011-11-04',
  133. pass: '2011-11-06',
  134. },
  135. isBefore: {
  136. spec: { args: '2011-11-05' },
  137. fail: '2011-11-06',
  138. pass: '2011-11-04',
  139. },
  140. isIn: {
  141. spec: { args: 'abcdefghijk' },
  142. fail: 'ghik',
  143. pass: 'ghij',
  144. },
  145. notIn: {
  146. spec: { args: 'abcdefghijk' },
  147. fail: 'ghij',
  148. pass: 'ghik',
  149. },
  150. max: {
  151. spec: { args: 23 },
  152. fail: '24',
  153. pass: '23',
  154. },
  155. max$: {
  156. spec: 23,
  157. fail: '24',
  158. pass: '23',
  159. },
  160. min: {
  161. spec: { args: 23 },
  162. fail: '22',
  163. pass: '23',
  164. },
  165. min$: {
  166. spec: 23,
  167. fail: '22',
  168. pass: '23',
  169. },
  170. isCreditCard: {
  171. fail: '401288888888188f',
  172. pass: '4012888888881881',
  173. },
  174. };
  175. const applyFailTest = function applyFailTest(validatorDetails, i, validator) {
  176. const failingValue = validatorDetails.fail[i];
  177. it(`correctly specifies an instance as invalid using a value of "${failingValue}" for the validation "${validator}"`, async function () {
  178. const validations = {};
  179. const message = `${validator}(${failingValue})`;
  180. validations[validator] = validatorDetails.spec || {};
  181. validations[validator].msg = message;
  182. const UserFail = this.sequelize.define(`User${rand()}`, {
  183. name: {
  184. type: DataTypes.STRING,
  185. validate: validations,
  186. },
  187. });
  188. const failingUser = UserFail.build({ name: failingValue });
  189. const _errors = await expect(failingUser.validate()).to.be.rejected;
  190. expect(_errors.get('name')[0].message).to.equal(message);
  191. expect(_errors.get('name')[0].value).to.equal(failingValue);
  192. });
  193. };
  194. const applyPassTest = function applyPassTest(validatorDetails, j, validator, type) {
  195. const succeedingValue = validatorDetails.pass[j];
  196. it(`correctly specifies an instance as valid using a value of "${succeedingValue}" for the validation "${validator}"`, async function () {
  197. const validations = {};
  198. const message = `${validator}(${succeedingValue})`;
  199. validations[validator] = validatorDetails.spec || {};
  200. switch (type) {
  201. case 'msg': {
  202. validations[validator].msg = message;
  203. break;
  204. }
  205. case 'args': {
  206. validations[validator].args = validations[validator].args || true;
  207. validations[validator].msg = message;
  208. break;
  209. }
  210. case 'true': {
  211. validations[validator] = true;
  212. break;
  213. }
  214. // No default
  215. }
  216. const UserSuccess = this.sequelize.define(`User${rand()}`, {
  217. name: {
  218. type: DataTypes.STRING,
  219. validate: validations,
  220. },
  221. });
  222. const successfulUser = UserSuccess.build({ name: succeedingValue });
  223. await expect(successfulUser.validate()).not.to.be.rejected;
  224. });
  225. };
  226. for (let validator in checks) {
  227. if (checks.hasOwnProperty(validator)) {
  228. validator = validator.replace(/\$$/, '');
  229. const validatorDetails = checks[validator];
  230. if (!validatorDetails.raw) {
  231. validatorDetails.fail = Array.isArray(validatorDetails.fail)
  232. ? validatorDetails.fail
  233. : [validatorDetails.fail];
  234. validatorDetails.pass = Array.isArray(validatorDetails.pass)
  235. ? validatorDetails.pass
  236. : [validatorDetails.pass];
  237. }
  238. for (let i = 0; i < validatorDetails.fail.length; i++) {
  239. applyFailTest(validatorDetails, i, validator);
  240. }
  241. for (let i = 0; i < validatorDetails.pass.length; i++) {
  242. applyPassTest(validatorDetails, i, validator);
  243. applyPassTest(validatorDetails, i, validator, 'msg');
  244. applyPassTest(validatorDetails, i, validator, 'args');
  245. if (validatorDetails.stringOrBoolean || validatorDetails.spec === undefined) {
  246. applyPassTest(validatorDetails, i, validator, 'true');
  247. }
  248. }
  249. }
  250. }
  251. });
  252. if (dialect.supports.dataTypes.DECIMAL) {
  253. describe('DECIMAL validator', () => {
  254. let User;
  255. before(function () {
  256. User = sequelize.define('user', {
  257. decimal: DataTypes.DECIMAL(10, 2),
  258. });
  259. this.stub = sinon.stub(sequelize, 'queryRaw').callsFake(async () => [User.build({}), 1]);
  260. });
  261. after(function () {
  262. this.stub.restore();
  263. });
  264. it('should allow decimal as a string', async () => {
  265. await expect(
  266. User.create({
  267. decimal: '12.6',
  268. }),
  269. ).not.to.be.rejected;
  270. });
  271. it('should allow decimal big numbers as a string', async () => {
  272. await expect(
  273. User.create({
  274. decimal: '2321312301230128391820831289123012',
  275. }),
  276. ).not.to.be.rejected;
  277. });
  278. it('should allow decimal as scientific notation', async () => {
  279. await Promise.all([
  280. expect(
  281. User.create({
  282. decimal: '2321312301230128391820e219',
  283. }),
  284. ).not.to.be.rejected,
  285. expect(
  286. User.create({
  287. decimal: '2321312301230128391820e+219',
  288. }),
  289. ).not.to.be.rejected,
  290. ]);
  291. });
  292. });
  293. }
  294. describe('datatype validations', () => {
  295. let User;
  296. before(function () {
  297. User = sequelize.define('user', {
  298. integer: DataTypes.INTEGER,
  299. name: DataTypes.STRING,
  300. awesome: DataTypes.BOOLEAN,
  301. uid: DataTypes.UUID,
  302. date: DataTypes.DATE,
  303. });
  304. this.stub = sinon.stub(sequelize, 'queryRaw').callsFake(async () => [User.build({}), 1]);
  305. });
  306. after(function () {
  307. this.stub.restore();
  308. });
  309. describe('should not throw', () => {
  310. describe('create', () => {
  311. it('should allow number as a string', async () => {
  312. await expect(
  313. User.create({
  314. integer: '12',
  315. }),
  316. ).not.to.be.rejected;
  317. });
  318. it('should allow dates as a string', async () => {
  319. await expect(
  320. User.findOne({
  321. where: {
  322. date: '2000-12-16',
  323. },
  324. }),
  325. ).not.to.be.rejected;
  326. });
  327. });
  328. describe('findAll', () => {
  329. it('should allow $in', async () => {
  330. if (!dialect.supports.dataTypes.ARRAY) {
  331. return;
  332. }
  333. await expect(
  334. User.findAll({
  335. where: {
  336. name: {
  337. [Op.like]: {
  338. [Op.any]: ['foo%', 'bar%'],
  339. },
  340. },
  341. },
  342. }),
  343. ).not.to.be.rejected;
  344. });
  345. it('should allow $like for uuid if cast', async () => {
  346. await expect(
  347. User.findAll({
  348. where: {
  349. 'uid::text': {
  350. [Op.like]: '12345678%',
  351. },
  352. },
  353. }),
  354. ).not.to.be.rejected;
  355. });
  356. });
  357. });
  358. describe('should throw validationError', () => {
  359. describe('create', () => {
  360. it('should throw when passing string', async () => {
  361. const error = await expect(
  362. User.create({
  363. integer: 'jan',
  364. }),
  365. ).to.be.rejectedWith(Sequelize.ValidationError, `'jan' is not a valid integer`);
  366. expect(error)
  367. .to.have.property('errors')
  368. .that.is.an('array')
  369. .with.lengthOf(1)
  370. .and.with.property(0)
  371. .that.is.an.instanceOf(Sequelize.ValidationErrorItem)
  372. .and.include({
  373. type: 'Validation error',
  374. path: 'integer',
  375. value: 'jan',
  376. validatorKey: 'INTEGER validator',
  377. });
  378. });
  379. it('should throw when passing decimal', async () => {
  380. await expect(
  381. User.create({
  382. integer: 4.5,
  383. }),
  384. )
  385. .to.be.rejectedWith(Sequelize.ValidationError)
  386. .which.eventually.have.property('errors')
  387. .that.is.an('array')
  388. .with.lengthOf(1)
  389. .and.with.property(0)
  390. .that.is.an.instanceOf(Sequelize.ValidationErrorItem)
  391. .and.include({
  392. type: 'Validation error',
  393. path: 'integer',
  394. value: 4.5,
  395. validatorKey: 'INTEGER validator',
  396. });
  397. });
  398. });
  399. describe('update', () => {
  400. it('should throw when passing string', async () => {
  401. await expect(User.update({ integer: 'jan' }, { where: {} }))
  402. .to.be.rejectedWith(Sequelize.ValidationError)
  403. .which.eventually.have.property('errors')
  404. .that.is.an('array')
  405. .with.lengthOf(1)
  406. .and.with.property(0)
  407. .that.is.an.instanceOf(Sequelize.ValidationErrorItem)
  408. .and.include({
  409. type: 'Validation error',
  410. path: 'integer',
  411. value: 'jan',
  412. validatorKey: 'INTEGER validator',
  413. });
  414. });
  415. it('should throw when passing decimal', async () => {
  416. await expect(
  417. User.update(
  418. {
  419. integer: 4.5,
  420. },
  421. { where: {} },
  422. ),
  423. )
  424. .to.be.rejectedWith(Sequelize.ValidationError)
  425. .which.eventually.have.property('errors')
  426. .that.is.an('array')
  427. .with.lengthOf(1)
  428. .and.with.property(0)
  429. .that.is.an.instanceOf(Sequelize.ValidationErrorItem)
  430. .and.include({
  431. type: 'Validation error',
  432. path: 'integer',
  433. value: 4.5,
  434. validatorKey: 'INTEGER validator',
  435. });
  436. });
  437. });
  438. });
  439. });
  440. describe('custom validation functions', () => {
  441. let User;
  442. before(function () {
  443. User = sequelize.define(
  444. 'user',
  445. {
  446. integer: {
  447. type: DataTypes.INTEGER,
  448. validate: {
  449. customFn(val, next) {
  450. if (val < 0) {
  451. next('integer must be greater or equal zero');
  452. } else {
  453. next();
  454. }
  455. },
  456. },
  457. },
  458. name: DataTypes.STRING,
  459. },
  460. {
  461. validate: {
  462. customFn() {
  463. if (this.get('name') === 'error') {
  464. throw new Error('Error from model validation promise');
  465. }
  466. },
  467. },
  468. },
  469. );
  470. this.stub = sinon.stub(sequelize, 'queryRaw').resolves([User.build(), 1]);
  471. });
  472. after(function () {
  473. this.stub.restore();
  474. });
  475. describe('should not throw', () => {
  476. describe('create', () => {
  477. it('custom validation functions are successful', async () => {
  478. await expect(
  479. User.create({
  480. integer: 1,
  481. name: 'noerror',
  482. }),
  483. ).not.to.be.rejected;
  484. });
  485. });
  486. describe('update', () => {
  487. it('custom validation functions are successful', async () => {
  488. await expect(
  489. User.update(
  490. {
  491. integer: 1,
  492. name: 'noerror',
  493. },
  494. { where: {} },
  495. ),
  496. ).not.to.be.rejected;
  497. });
  498. });
  499. });
  500. describe('should throw validationerror', () => {
  501. describe('create', () => {
  502. it('custom attribute validation function fails', async () => {
  503. await expect(
  504. User.create({
  505. integer: -1,
  506. }),
  507. ).to.be.rejectedWith(Sequelize.ValidationError);
  508. });
  509. it('custom model validation function fails', async () => {
  510. await expect(
  511. User.create({
  512. name: 'error',
  513. }),
  514. ).to.be.rejectedWith(Sequelize.ValidationError);
  515. });
  516. });
  517. describe('update', () => {
  518. it('custom attribute validation function fails', async () => {
  519. await expect(
  520. User.update(
  521. {
  522. integer: -1,
  523. },
  524. { where: {} },
  525. ),
  526. ).to.be.rejectedWith(Sequelize.ValidationError);
  527. });
  528. it('when custom model validation function fails', async () => {
  529. await expect(
  530. User.update(
  531. {
  532. name: 'error',
  533. },
  534. { where: {} },
  535. ),
  536. ).to.be.rejectedWith(Sequelize.ValidationError);
  537. });
  538. });
  539. });
  540. });
  541. describe('custom validation functions returning promises', () => {
  542. let User;
  543. before(function () {
  544. User = sequelize.define(
  545. 'user',
  546. {
  547. name: DataTypes.STRING,
  548. },
  549. {
  550. validate: {
  551. async customFn() {
  552. if (this.get('name') === 'error') {
  553. throw new Error('Error from model validation promise');
  554. }
  555. },
  556. },
  557. },
  558. );
  559. this.stub = sinon.stub(sequelize, 'queryRaw').resolves([User.build(), 1]);
  560. });
  561. after(function () {
  562. this.stub.restore();
  563. });
  564. describe('should not throw', () => {
  565. describe('create', () => {
  566. it('custom model validation functions are successful', async () => {
  567. await expect(
  568. User.create({
  569. name: 'noerror',
  570. }),
  571. ).not.to.be.rejected;
  572. });
  573. });
  574. describe('update', () => {
  575. it('custom model validation functions are successful', async () => {
  576. await expect(
  577. User.update(
  578. {
  579. name: 'noerror',
  580. },
  581. { where: {} },
  582. ),
  583. ).not.to.be.rejected;
  584. });
  585. });
  586. });
  587. describe('should throw validationerror', () => {
  588. describe('create', () => {
  589. it('custom model validation function fails', async () => {
  590. await expect(
  591. User.create({
  592. name: 'error',
  593. }),
  594. ).to.be.rejectedWith(Sequelize.ValidationError);
  595. });
  596. });
  597. describe('update', () => {
  598. it('when custom model validation function fails', async () => {
  599. await expect(
  600. User.update(
  601. {
  602. name: 'error',
  603. },
  604. { where: {} },
  605. ),
  606. ).to.be.rejectedWith(Sequelize.ValidationError);
  607. });
  608. });
  609. });
  610. });
  611. describe('custom validation functions and null values', () => {
  612. before(function () {
  613. this.customValidator = sinon.fake(function (value) {
  614. if (value === null && this.integer !== 10) {
  615. throw new Error("name can't be null unless integer is 10");
  616. }
  617. });
  618. });
  619. describe('with allowNull set to true', () => {
  620. before(function () {
  621. this.User = sequelize.define('user', {
  622. integer: DataTypes.INTEGER,
  623. name: {
  624. type: DataTypes.STRING,
  625. allowNull: true,
  626. validate: {
  627. customValidator: this.customValidator,
  628. },
  629. },
  630. });
  631. this.stub = sinon.stub(sequelize, 'queryRaw').resolves([this.User.build(), 1]);
  632. });
  633. after(function () {
  634. this.stub.restore();
  635. });
  636. describe('should call validator and not throw', () => {
  637. beforeEach(function () {
  638. this.customValidator.resetHistory();
  639. });
  640. it('on create', async function () {
  641. await expect(
  642. this.User.create({
  643. integer: 10,
  644. name: null,
  645. }),
  646. ).not.to.be.rejected;
  647. await expect(this.customValidator).to.have.been.calledOnce;
  648. });
  649. it('on update', async function () {
  650. await expect(
  651. this.User.update(
  652. {
  653. integer: 10,
  654. name: null,
  655. },
  656. { where: {} },
  657. ),
  658. ).not.to.be.rejected;
  659. await expect(this.customValidator).to.have.been.calledOnce;
  660. });
  661. });
  662. describe('should call validator and throw ValidationError', () => {
  663. beforeEach(function () {
  664. this.customValidator.resetHistory();
  665. });
  666. it('on create', async function () {
  667. await expect(
  668. this.User.create({
  669. integer: 11,
  670. name: null,
  671. }),
  672. ).to.be.rejectedWith(Sequelize.ValidationError);
  673. await expect(this.customValidator).to.have.been.calledOnce;
  674. });
  675. it('on update', async function () {
  676. await expect(
  677. this.User.update(
  678. {
  679. integer: 11,
  680. name: null,
  681. },
  682. { where: {} },
  683. ),
  684. ).to.be.rejectedWith(Sequelize.ValidationError);
  685. await expect(this.customValidator).to.have.been.calledOnce;
  686. });
  687. });
  688. });
  689. describe('with allowNull set to false', () => {
  690. before(function () {
  691. this.User = sequelize.define('user', {
  692. integer: DataTypes.INTEGER,
  693. name: {
  694. type: DataTypes.STRING,
  695. allowNull: false,
  696. validate: {
  697. customValidator: this.customValidator,
  698. },
  699. },
  700. });
  701. this.stub = sinon.stub(sequelize, 'queryRaw').resolves([this.User.build(), 1]);
  702. });
  703. after(function () {
  704. this.stub.restore();
  705. });
  706. describe('should not call validator and throw ValidationError', () => {
  707. beforeEach(function () {
  708. this.customValidator.resetHistory();
  709. });
  710. it('on create', async function () {
  711. await expect(
  712. this.User.create({
  713. integer: 99,
  714. name: null,
  715. }),
  716. ).to.be.rejectedWith(Sequelize.ValidationError);
  717. await expect(this.customValidator).to.have.not.been.called;
  718. });
  719. it('on update', async function () {
  720. await expect(
  721. this.User.update(
  722. {
  723. integer: 99,
  724. name: null,
  725. },
  726. { where: {} },
  727. ),
  728. ).to.be.rejectedWith(Sequelize.ValidationError);
  729. await expect(this.customValidator).to.have.not.been.called;
  730. });
  731. });
  732. describe('should call validator and not throw', () => {
  733. beforeEach(function () {
  734. this.customValidator.resetHistory();
  735. });
  736. it('on create', async function () {
  737. await expect(
  738. this.User.create({
  739. integer: 99,
  740. name: 'foo',
  741. }),
  742. ).not.to.be.rejected;
  743. await expect(this.customValidator).to.have.been.calledOnce;
  744. });
  745. it('on update', async function () {
  746. await expect(
  747. this.User.update(
  748. {
  749. integer: 99,
  750. name: 'foo',
  751. },
  752. { where: {} },
  753. ),
  754. ).not.to.be.rejected;
  755. await expect(this.customValidator).to.have.been.calledOnce;
  756. });
  757. });
  758. });
  759. });
  760. });