123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847 |
- 'use strict';
- const { expect } = require('chai');
- const sinon = require('sinon');
- const { DataTypes, Op, Sequelize } = require('@sequelize/core');
- const { rand, sequelize } = require('../../support');
- const dialect = sequelize.dialect;
- describe('InstanceValidator', () => {
- describe('validations', () => {
- const checks = {
- is: {
- spec: { args: ['[a-z]', 'i'] },
- fail: '0',
- pass: 'a',
- },
- not: {
- spec: { args: ['[a-z]', 'i'] },
- fail: 'a',
- pass: '0',
- },
- isEmail: {
- fail: 'a',
- pass: 'abc@abc.com',
- },
- isUrl: {
- fail: 'abc',
- pass: 'http://abc.com',
- },
- isIP: {
- fail: 'abc',
- pass: '129.89.23.1',
- },
- isIPv4: {
- fail: 'abc',
- pass: '129.89.23.1',
- },
- isIPv6: {
- fail: '1111:2222:3333::5555:',
- pass: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156',
- },
- isAlpha: {
- stringOrBoolean: true,
- spec: { args: 'en-GB' },
- fail: '012',
- pass: 'abc',
- },
- isAlphanumeric: {
- stringOrBoolean: true,
- spec: { args: 'en-GB' },
- fail: '_abc019',
- pass: 'abc019',
- },
- isNumeric: {
- fail: 'abc',
- pass: '019',
- },
- isInt: {
- fail: '9.2',
- pass: '-9',
- },
- isLowercase: {
- fail: 'AB',
- pass: 'ab',
- },
- isUppercase: {
- fail: 'ab',
- pass: 'AB',
- },
- isDecimal: {
- fail: 'a',
- pass: '0.2',
- },
- isFloat: {
- fail: 'a',
- pass: '9.2',
- },
- isNull: {
- fail: '0',
- pass: null,
- },
- notEmpty: {
- fail: ' ',
- pass: 'a',
- },
- equals: {
- spec: { args: 'bla bla bla' },
- fail: 'bla',
- pass: 'bla bla bla',
- },
- contains: {
- spec: { args: 'bla' },
- fail: 'la',
- pass: '0bla23',
- },
- notContains: {
- spec: { args: 'bla' },
- fail: '0bla23',
- pass: 'la',
- },
- regex: {
- spec: { args: ['[a-z]', 'i'] },
- fail: '0',
- pass: 'a',
- },
- notRegex: {
- spec: { args: ['[a-z]', 'i'] },
- fail: 'a',
- pass: '0',
- },
- len: {
- spec: { args: [2, 4] },
- fail: ['1', '12345'],
- pass: ['12', '123', '1234'],
- raw: true,
- },
- len$: {
- spec: [2, 4],
- fail: ['1', '12345'],
- pass: ['12', '123', '1234'],
- raw: true,
- },
- isUUID: {
- spec: { args: 4 },
- fail: 'f47ac10b-58cc-3372-a567-0e02b2c3d479',
- pass: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
- },
- isDate: {
- fail: 'not a date',
- pass: '2011-02-04',
- },
- isAfter: {
- spec: { args: '2011-11-05' },
- fail: '2011-11-04',
- pass: '2011-11-06',
- },
- isBefore: {
- spec: { args: '2011-11-05' },
- fail: '2011-11-06',
- pass: '2011-11-04',
- },
- isIn: {
- spec: { args: 'abcdefghijk' },
- fail: 'ghik',
- pass: 'ghij',
- },
- notIn: {
- spec: { args: 'abcdefghijk' },
- fail: 'ghij',
- pass: 'ghik',
- },
- max: {
- spec: { args: 23 },
- fail: '24',
- pass: '23',
- },
- max$: {
- spec: 23,
- fail: '24',
- pass: '23',
- },
- min: {
- spec: { args: 23 },
- fail: '22',
- pass: '23',
- },
- min$: {
- spec: 23,
- fail: '22',
- pass: '23',
- },
- isCreditCard: {
- fail: '401288888888188f',
- pass: '4012888888881881',
- },
- };
- const applyFailTest = function applyFailTest(validatorDetails, i, validator) {
- const failingValue = validatorDetails.fail[i];
- it(`correctly specifies an instance as invalid using a value of "${failingValue}" for the validation "${validator}"`, async function () {
- const validations = {};
- const message = `${validator}(${failingValue})`;
- validations[validator] = validatorDetails.spec || {};
- validations[validator].msg = message;
- const UserFail = this.sequelize.define(`User${rand()}`, {
- name: {
- type: DataTypes.STRING,
- validate: validations,
- },
- });
- const failingUser = UserFail.build({ name: failingValue });
- const _errors = await expect(failingUser.validate()).to.be.rejected;
- expect(_errors.get('name')[0].message).to.equal(message);
- expect(_errors.get('name')[0].value).to.equal(failingValue);
- });
- };
- const applyPassTest = function applyPassTest(validatorDetails, j, validator, type) {
- const succeedingValue = validatorDetails.pass[j];
- it(`correctly specifies an instance as valid using a value of "${succeedingValue}" for the validation "${validator}"`, async function () {
- const validations = {};
- const message = `${validator}(${succeedingValue})`;
- validations[validator] = validatorDetails.spec || {};
- switch (type) {
- case 'msg': {
- validations[validator].msg = message;
- break;
- }
- case 'args': {
- validations[validator].args = validations[validator].args || true;
- validations[validator].msg = message;
- break;
- }
- case 'true': {
- validations[validator] = true;
- break;
- }
- // No default
- }
- const UserSuccess = this.sequelize.define(`User${rand()}`, {
- name: {
- type: DataTypes.STRING,
- validate: validations,
- },
- });
- const successfulUser = UserSuccess.build({ name: succeedingValue });
- await expect(successfulUser.validate()).not.to.be.rejected;
- });
- };
- for (let validator in checks) {
- if (checks.hasOwnProperty(validator)) {
- validator = validator.replace(/\$$/, '');
- const validatorDetails = checks[validator];
- if (!validatorDetails.raw) {
- validatorDetails.fail = Array.isArray(validatorDetails.fail)
- ? validatorDetails.fail
- : [validatorDetails.fail];
- validatorDetails.pass = Array.isArray(validatorDetails.pass)
- ? validatorDetails.pass
- : [validatorDetails.pass];
- }
- for (let i = 0; i < validatorDetails.fail.length; i++) {
- applyFailTest(validatorDetails, i, validator);
- }
- for (let i = 0; i < validatorDetails.pass.length; i++) {
- applyPassTest(validatorDetails, i, validator);
- applyPassTest(validatorDetails, i, validator, 'msg');
- applyPassTest(validatorDetails, i, validator, 'args');
- if (validatorDetails.stringOrBoolean || validatorDetails.spec === undefined) {
- applyPassTest(validatorDetails, i, validator, 'true');
- }
- }
- }
- }
- });
- if (dialect.supports.dataTypes.DECIMAL) {
- describe('DECIMAL validator', () => {
- let User;
- before(function () {
- User = sequelize.define('user', {
- decimal: DataTypes.DECIMAL(10, 2),
- });
- this.stub = sinon.stub(sequelize, 'queryRaw').callsFake(async () => [User.build({}), 1]);
- });
- after(function () {
- this.stub.restore();
- });
- it('should allow decimal as a string', async () => {
- await expect(
- User.create({
- decimal: '12.6',
- }),
- ).not.to.be.rejected;
- });
- it('should allow decimal big numbers as a string', async () => {
- await expect(
- User.create({
- decimal: '2321312301230128391820831289123012',
- }),
- ).not.to.be.rejected;
- });
- it('should allow decimal as scientific notation', async () => {
- await Promise.all([
- expect(
- User.create({
- decimal: '2321312301230128391820e219',
- }),
- ).not.to.be.rejected,
- expect(
- User.create({
- decimal: '2321312301230128391820e+219',
- }),
- ).not.to.be.rejected,
- ]);
- });
- });
- }
- describe('datatype validations', () => {
- let User;
- before(function () {
- User = sequelize.define('user', {
- integer: DataTypes.INTEGER,
- name: DataTypes.STRING,
- awesome: DataTypes.BOOLEAN,
- uid: DataTypes.UUID,
- date: DataTypes.DATE,
- });
- this.stub = sinon.stub(sequelize, 'queryRaw').callsFake(async () => [User.build({}), 1]);
- });
- after(function () {
- this.stub.restore();
- });
- describe('should not throw', () => {
- describe('create', () => {
- it('should allow number as a string', async () => {
- await expect(
- User.create({
- integer: '12',
- }),
- ).not.to.be.rejected;
- });
- it('should allow dates as a string', async () => {
- await expect(
- User.findOne({
- where: {
- date: '2000-12-16',
- },
- }),
- ).not.to.be.rejected;
- });
- });
- describe('findAll', () => {
- it('should allow $in', async () => {
- if (!dialect.supports.dataTypes.ARRAY) {
- return;
- }
- await expect(
- User.findAll({
- where: {
- name: {
- [Op.like]: {
- [Op.any]: ['foo%', 'bar%'],
- },
- },
- },
- }),
- ).not.to.be.rejected;
- });
- it('should allow $like for uuid if cast', async () => {
- await expect(
- User.findAll({
- where: {
- 'uid::text': {
- [Op.like]: '12345678%',
- },
- },
- }),
- ).not.to.be.rejected;
- });
- });
- });
- describe('should throw validationError', () => {
- describe('create', () => {
- it('should throw when passing string', async () => {
- const error = await expect(
- User.create({
- integer: 'jan',
- }),
- ).to.be.rejectedWith(Sequelize.ValidationError, `'jan' is not a valid integer`);
- expect(error)
- .to.have.property('errors')
- .that.is.an('array')
- .with.lengthOf(1)
- .and.with.property(0)
- .that.is.an.instanceOf(Sequelize.ValidationErrorItem)
- .and.include({
- type: 'Validation error',
- path: 'integer',
- value: 'jan',
- validatorKey: 'INTEGER validator',
- });
- });
- it('should throw when passing decimal', async () => {
- await expect(
- User.create({
- integer: 4.5,
- }),
- )
- .to.be.rejectedWith(Sequelize.ValidationError)
- .which.eventually.have.property('errors')
- .that.is.an('array')
- .with.lengthOf(1)
- .and.with.property(0)
- .that.is.an.instanceOf(Sequelize.ValidationErrorItem)
- .and.include({
- type: 'Validation error',
- path: 'integer',
- value: 4.5,
- validatorKey: 'INTEGER validator',
- });
- });
- });
- describe('update', () => {
- it('should throw when passing string', async () => {
- await expect(User.update({ integer: 'jan' }, { where: {} }))
- .to.be.rejectedWith(Sequelize.ValidationError)
- .which.eventually.have.property('errors')
- .that.is.an('array')
- .with.lengthOf(1)
- .and.with.property(0)
- .that.is.an.instanceOf(Sequelize.ValidationErrorItem)
- .and.include({
- type: 'Validation error',
- path: 'integer',
- value: 'jan',
- validatorKey: 'INTEGER validator',
- });
- });
- it('should throw when passing decimal', async () => {
- await expect(
- User.update(
- {
- integer: 4.5,
- },
- { where: {} },
- ),
- )
- .to.be.rejectedWith(Sequelize.ValidationError)
- .which.eventually.have.property('errors')
- .that.is.an('array')
- .with.lengthOf(1)
- .and.with.property(0)
- .that.is.an.instanceOf(Sequelize.ValidationErrorItem)
- .and.include({
- type: 'Validation error',
- path: 'integer',
- value: 4.5,
- validatorKey: 'INTEGER validator',
- });
- });
- });
- });
- });
- describe('custom validation functions', () => {
- let User;
- before(function () {
- User = sequelize.define(
- 'user',
- {
- integer: {
- type: DataTypes.INTEGER,
- validate: {
- customFn(val, next) {
- if (val < 0) {
- next('integer must be greater or equal zero');
- } else {
- next();
- }
- },
- },
- },
- name: DataTypes.STRING,
- },
- {
- validate: {
- customFn() {
- if (this.get('name') === 'error') {
- throw new Error('Error from model validation promise');
- }
- },
- },
- },
- );
- this.stub = sinon.stub(sequelize, 'queryRaw').resolves([User.build(), 1]);
- });
- after(function () {
- this.stub.restore();
- });
- describe('should not throw', () => {
- describe('create', () => {
- it('custom validation functions are successful', async () => {
- await expect(
- User.create({
- integer: 1,
- name: 'noerror',
- }),
- ).not.to.be.rejected;
- });
- });
- describe('update', () => {
- it('custom validation functions are successful', async () => {
- await expect(
- User.update(
- {
- integer: 1,
- name: 'noerror',
- },
- { where: {} },
- ),
- ).not.to.be.rejected;
- });
- });
- });
- describe('should throw validationerror', () => {
- describe('create', () => {
- it('custom attribute validation function fails', async () => {
- await expect(
- User.create({
- integer: -1,
- }),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- });
- it('custom model validation function fails', async () => {
- await expect(
- User.create({
- name: 'error',
- }),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- });
- });
- describe('update', () => {
- it('custom attribute validation function fails', async () => {
- await expect(
- User.update(
- {
- integer: -1,
- },
- { where: {} },
- ),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- });
- it('when custom model validation function fails', async () => {
- await expect(
- User.update(
- {
- name: 'error',
- },
- { where: {} },
- ),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- });
- });
- });
- });
- describe('custom validation functions returning promises', () => {
- let User;
- before(function () {
- User = sequelize.define(
- 'user',
- {
- name: DataTypes.STRING,
- },
- {
- validate: {
- async customFn() {
- if (this.get('name') === 'error') {
- throw new Error('Error from model validation promise');
- }
- },
- },
- },
- );
- this.stub = sinon.stub(sequelize, 'queryRaw').resolves([User.build(), 1]);
- });
- after(function () {
- this.stub.restore();
- });
- describe('should not throw', () => {
- describe('create', () => {
- it('custom model validation functions are successful', async () => {
- await expect(
- User.create({
- name: 'noerror',
- }),
- ).not.to.be.rejected;
- });
- });
- describe('update', () => {
- it('custom model validation functions are successful', async () => {
- await expect(
- User.update(
- {
- name: 'noerror',
- },
- { where: {} },
- ),
- ).not.to.be.rejected;
- });
- });
- });
- describe('should throw validationerror', () => {
- describe('create', () => {
- it('custom model validation function fails', async () => {
- await expect(
- User.create({
- name: 'error',
- }),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- });
- });
- describe('update', () => {
- it('when custom model validation function fails', async () => {
- await expect(
- User.update(
- {
- name: 'error',
- },
- { where: {} },
- ),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- });
- });
- });
- });
- describe('custom validation functions and null values', () => {
- before(function () {
- this.customValidator = sinon.fake(function (value) {
- if (value === null && this.integer !== 10) {
- throw new Error("name can't be null unless integer is 10");
- }
- });
- });
- describe('with allowNull set to true', () => {
- before(function () {
- this.User = sequelize.define('user', {
- integer: DataTypes.INTEGER,
- name: {
- type: DataTypes.STRING,
- allowNull: true,
- validate: {
- customValidator: this.customValidator,
- },
- },
- });
- this.stub = sinon.stub(sequelize, 'queryRaw').resolves([this.User.build(), 1]);
- });
- after(function () {
- this.stub.restore();
- });
- describe('should call validator and not throw', () => {
- beforeEach(function () {
- this.customValidator.resetHistory();
- });
- it('on create', async function () {
- await expect(
- this.User.create({
- integer: 10,
- name: null,
- }),
- ).not.to.be.rejected;
- await expect(this.customValidator).to.have.been.calledOnce;
- });
- it('on update', async function () {
- await expect(
- this.User.update(
- {
- integer: 10,
- name: null,
- },
- { where: {} },
- ),
- ).not.to.be.rejected;
- await expect(this.customValidator).to.have.been.calledOnce;
- });
- });
- describe('should call validator and throw ValidationError', () => {
- beforeEach(function () {
- this.customValidator.resetHistory();
- });
- it('on create', async function () {
- await expect(
- this.User.create({
- integer: 11,
- name: null,
- }),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- await expect(this.customValidator).to.have.been.calledOnce;
- });
- it('on update', async function () {
- await expect(
- this.User.update(
- {
- integer: 11,
- name: null,
- },
- { where: {} },
- ),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- await expect(this.customValidator).to.have.been.calledOnce;
- });
- });
- });
- describe('with allowNull set to false', () => {
- before(function () {
- this.User = sequelize.define('user', {
- integer: DataTypes.INTEGER,
- name: {
- type: DataTypes.STRING,
- allowNull: false,
- validate: {
- customValidator: this.customValidator,
- },
- },
- });
- this.stub = sinon.stub(sequelize, 'queryRaw').resolves([this.User.build(), 1]);
- });
- after(function () {
- this.stub.restore();
- });
- describe('should not call validator and throw ValidationError', () => {
- beforeEach(function () {
- this.customValidator.resetHistory();
- });
- it('on create', async function () {
- await expect(
- this.User.create({
- integer: 99,
- name: null,
- }),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- await expect(this.customValidator).to.have.not.been.called;
- });
- it('on update', async function () {
- await expect(
- this.User.update(
- {
- integer: 99,
- name: null,
- },
- { where: {} },
- ),
- ).to.be.rejectedWith(Sequelize.ValidationError);
- await expect(this.customValidator).to.have.not.been.called;
- });
- });
- describe('should call validator and not throw', () => {
- beforeEach(function () {
- this.customValidator.resetHistory();
- });
- it('on create', async function () {
- await expect(
- this.User.create({
- integer: 99,
- name: 'foo',
- }),
- ).not.to.be.rejected;
- await expect(this.customValidator).to.have.been.calledOnce;
- });
- it('on update', async function () {
- await expect(
- this.User.update(
- {
- integer: 99,
- name: 'foo',
- },
- { where: {} },
- ),
- ).not.to.be.rejected;
- await expect(this.customValidator).to.have.been.calledOnce;
- });
- });
- });
- });
- });
|