-
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(valitor): Added tests for validator process to ensure correct fu…
…nctionality under various conditions such as schema validation passing with no errors when all rules are met handling errors when fields fail validation skipping validation for schemas already validated by conditions throwing errors when conflicting schema options are used like pick and omit applied validation on fields with schema options respecting pick option skipping validation for omitted fields handling nested schema validation correctly validating fields with conditional dependencies between schemas testing handling of empty or undefined data and validating complex patterns in rules these tests cover different scenarios of validation like required rules field validation with pick and omit options skipping validated schemas validating nested schemas and ensuring error messages are correctly returned for failing rules
- Loading branch information
Showing
3 changed files
with
428 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
import { describe, it, beforeEach, afterEach, after } from 'mocha'; | ||
import { expect } from 'chai'; | ||
import sinon from 'sinon'; | ||
import { RuleAction } from '../../../dist/validator/RuleAction'; | ||
import { FieldValidator } from '../../../dist/validator/FieldValidator'; | ||
import { DefaultMessages } from '../../../dist/validator/config'; | ||
import { ValidationField } from '../../../dist/common/interfaces'; | ||
|
||
describe('FieldValidator.validateField', () => { | ||
let ruleActionStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
ruleActionStub = sinon.stub(RuleAction, 'applyRule'); | ||
}); | ||
|
||
afterEach(() => { | ||
ruleActionStub.restore(); | ||
ruleActionStub.reset(); | ||
}); | ||
after(() => { | ||
sinon.restore(); | ||
}); | ||
|
||
// General use case: Validates a simple required field | ||
it('should return no errors when all rules pass', async () => { | ||
ruleActionStub.returns(true); | ||
|
||
const result = await FieldValidator.validateField('validValue', { rules: ['required'], options: {} }, 'testField', false, {}); | ||
|
||
expect(result).to.be.empty; | ||
}); | ||
|
||
// General use case: Fails on a single rule | ||
it('should return an error when a rule fails', async () => { | ||
ruleActionStub.withArgs({ ruleName: 'required' }, undefined).returns(false); | ||
|
||
const result = await FieldValidator.validateField(undefined, { rules: ['required'], options: {} }, 'testField', false, {}); | ||
|
||
expect(result).to.deep.equal(['The testField field is required and cannot be left blank.']); | ||
}); | ||
|
||
// Edge case: Inherits default rules | ||
it('should apply inherited default rules', async () => { | ||
ruleActionStub.withArgs({ ruleName: 'min', param: '5' }, '123').returns(false); | ||
|
||
const result = await FieldValidator.validateField( | ||
'123', | ||
{ rules: ['inherit'], options: {} }, | ||
'testField', | ||
false, | ||
{}, | ||
['min:5'], // Default rules | ||
); | ||
|
||
expect(result).to.deep.equal([DefaultMessages.min?.replace('{value}', '5').replace('{field}', 'testField')]); | ||
}); | ||
|
||
// Unique case: Validates custom function | ||
it('should return an error when custom validation fails', async () => { | ||
const customStub = sinon.stub().resolves(false); | ||
|
||
const result = await FieldValidator.validateField('value', { rules: [], options: { custom: customStub }, messages: { custom: 'Custom error message' } }, 'testField', false, {}); | ||
|
||
expect(result).to.deep.equal(['Custom error message']); | ||
sinon.assert.calledOnce(customStub); | ||
}); | ||
|
||
// General use case: Validates multiple rules | ||
it('should validate multiple rules and return all errors when `returnFirstError` is false', async () => { | ||
ruleActionStub.withArgs({ ruleName: 'required' }, '').returns(false); | ||
ruleActionStub.withArgs({ ruleName: 'min', param: '5' }, '').returns(false); | ||
|
||
const result = await FieldValidator.validateField('', { rules: ['required', 'min:5'], options: {} }, 'testField', false, {}); | ||
|
||
expect(result).to.deep.equal([DefaultMessages.required?.replace('{field}', 'testField'), DefaultMessages.min?.replace('{value}', '5')?.replace('{field}', 'testField')]); | ||
}); | ||
|
||
// Edge case: Stops validation on the first error | ||
it('should stop on the first error when `returnFirstError` is true', async () => { | ||
ruleActionStub.withArgs({ ruleName: 'required' }, '').returns(false); | ||
|
||
const result = await FieldValidator.validateField('', { rules: ['required', 'min:5'], options: {} }, 'testField', true, {}); | ||
|
||
expect(result).to.deep.equal([DefaultMessages.required?.replace('{field}', 'testField')]); | ||
}); | ||
|
||
describe('FieldValidator', () => { | ||
describe('General Use Cases', () => { | ||
it('should return an error for missing required value', async () => { | ||
const value = ''; | ||
const fieldRules: ValidationField = { rules: ['required'] }; | ||
const result = await FieldValidator.validateField(value, fieldRules, 'testField', false, {}); | ||
expect(result).to.have.lengthOf(1); | ||
expect(result[0]).to.equal(DefaultMessages.required?.replace('{field}', 'testField')); | ||
}); | ||
|
||
it('should return an error for values outside min or max range', async () => { | ||
const value = 3; | ||
const fieldRules: ValidationField = { rules: ['min:5'] }; | ||
const result = await FieldValidator.validateField(value, fieldRules, 'testField', false, {}); | ||
expect(result).to.have.lengthOf(1); | ||
expect(result[0]).to.equal(DefaultMessages.min?.replace('{field}', 'testField').replace('{value}', '5')); | ||
}); | ||
}); | ||
|
||
it('should handle empty rules gracefully', async () => { | ||
const value = 'test'; | ||
const fieldRules: ValidationField = { rules: [] }; | ||
const result = await FieldValidator.validateField(value, fieldRules, 'testField', false, {}); | ||
expect(result).to.be.empty; | ||
}); | ||
|
||
it('should validate custom rules asynchronously', async () => { | ||
const value = 'test'; | ||
const fieldRules: ValidationField = { | ||
rules: [], | ||
options: { | ||
custom: async (val) => val === 'test', | ||
}, | ||
}; | ||
const result = await FieldValidator.validateField(value, fieldRules, 'testField', false, {}); | ||
expect(result).to.be.empty; | ||
}); | ||
|
||
it('should return an error for failed custom validation', async () => { | ||
const value = 'invalid'; | ||
const fieldRules: ValidationField = { | ||
rules: [], | ||
options: { | ||
custom: async (val) => val === 'test', | ||
}, | ||
messages: { | ||
custom: 'Custom validation failed for {field}.', | ||
}, | ||
}; | ||
const result = await FieldValidator.validateField(value, fieldRules, 'testField', false, {}); | ||
expect(result).to.have.lengthOf(1); | ||
expect(result[0]).to.equal('Custom validation failed for testField.'); | ||
}); | ||
|
||
it('should handle dependent field validation', async () => { | ||
const value = 'child'; | ||
const fieldRules: ValidationField = { | ||
rules: [], | ||
options: { | ||
dependsOn: { | ||
field: 'parentField', | ||
operator: 'equal', | ||
value: 'parent', | ||
}, | ||
}, | ||
messages: { | ||
dependsOn: '{field} requires {dependentField} to have a specific value.', | ||
}, | ||
}; | ||
const allData = { parentField: 'parent' }; | ||
const result = await FieldValidator.validateField(value, fieldRules, 'testField', false, allData); | ||
expect(result).to.be.empty; | ||
}); | ||
|
||
it('should return an error for failed dependent field validation', async () => { | ||
const value = 'child'; | ||
const fieldRules: ValidationField = { | ||
rules: [], | ||
options: { | ||
dependsOn: { | ||
field: 'parentField', | ||
operator: 'equal', | ||
value: 'parent', | ||
}, | ||
}, | ||
messages: { | ||
dependsOn: '{field} requires {dependentField} to have a specific value.', | ||
}, | ||
}; | ||
const allData = { parentField: 'differentValue' }; | ||
const result = await FieldValidator.validateField(value, fieldRules, 'testField', false, allData); | ||
expect(result).to.have.lengthOf(1); | ||
expect(result[0]).to.equal('testField requires parentField to have a specific value.'); | ||
}); | ||
|
||
describe('Return First Error Mode', () => { | ||
it('should return only the first error when returnFirstError is true', async () => { | ||
const value = ''; | ||
const fieldRules: ValidationField = { rules: ['required', 'string'] }; | ||
const result = await FieldValidator.validateField(value, fieldRules, 'testField', true, {}); | ||
expect(result).to.have.lengthOf(1); | ||
expect(result[0]).to.equal(DefaultMessages.required?.replace('{field}', 'testField')); | ||
}); | ||
|
||
it('should return all errors when returnFirstError is false', async () => { | ||
const value = ''; | ||
const fieldRules: ValidationField = { rules: ['required', 'string'] }; | ||
const result = await FieldValidator.validateField(value, fieldRules, 'testField', false, {}); | ||
expect(result).to.have.lengthOf(2); | ||
expect(result[0]).to.equal(DefaultMessages.required?.replace('{field}', 'testField')); | ||
expect(result[1]).to.equal(DefaultMessages.string?.replace('{field}', 'testField')); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import { expect } from 'chai'; | ||
import sinon from 'sinon'; | ||
import 'mocha'; | ||
import { RuleAction } from '../../../dist/validator/RuleAction'; | ||
import { describe, it } from 'mocha'; | ||
import Reflector from '../../../dist/metadata/'; | ||
import { createMockReflector } from '../../mocks/reflector.mock'; | ||
|
||
describe('RuleAction', () => { | ||
describe('applyRule', () => { | ||
it('should validate "required" rule correctly', () => { | ||
expect(RuleAction.applyRule({ ruleName: 'required', param: '' }, 'value')).to.be.true; | ||
expect(RuleAction.applyRule({ ruleName: 'required', param: '' }, null)).to.be.false; | ||
expect(RuleAction.applyRule({ ruleName: 'required', param: '' }, undefined)).to.be.false; | ||
expect(RuleAction.applyRule({ ruleName: 'required', param: '' }, '')).to.be.false; | ||
}); | ||
|
||
it('should validate "string" rule correctly', () => { | ||
expect(RuleAction.applyRule({ ruleName: 'string', param: '' }, 'test')).to.be.true; | ||
expect(RuleAction.applyRule({ ruleName: 'string', param: '' }, 123)).to.be.false; | ||
expect(RuleAction.applyRule({ ruleName: 'string', param: '' }, null)).to.be.false; | ||
}); | ||
|
||
it('should validate "integer" rule correctly', () => { | ||
expect(RuleAction.applyRule({ ruleName: 'integer', param: '' }, 42)).to.be.true; | ||
expect(RuleAction.applyRule({ ruleName: 'integer', param: '' }, 42.5)).to.be.false; | ||
expect(RuleAction.applyRule({ ruleName: 'integer', param: '' }, '42')).to.be.false; | ||
}); | ||
|
||
it('should validate "email" rule correctly', () => { | ||
expect(RuleAction.applyRule({ ruleName: 'email', param: '' }, 'test@example.com')).to.be.true; | ||
expect(RuleAction.applyRule({ ruleName: 'email', param: '' }, 'test.com')).to.be.false; | ||
expect(RuleAction.applyRule({ ruleName: 'email', param: '' }, '')).to.be.false; | ||
}); | ||
|
||
it('should validate "min" and "max" rules correctly', () => { | ||
expect(RuleAction.applyRule({ ruleName: 'min', param: '5' }, 'hello')).to.be.true; | ||
expect(RuleAction.applyRule({ ruleName: 'min', param: '5' }, 'hi')).to.be.false; | ||
expect(RuleAction.applyRule({ ruleName: 'max', param: '5' }, 'hello')).to.be.true; | ||
expect(RuleAction.applyRule({ ruleName: 'max', param: '5' }, 'hello world')).to.be.false; | ||
}); | ||
|
||
it('should validate "regex" rule correctly', () => { | ||
expect(RuleAction.applyRule({ ruleName: 'regex', param: '^test$' }, 'test')).to.be.true; | ||
expect(RuleAction.applyRule({ ruleName: 'regex', param: '^test$' }, 'fail')).to.be.false; | ||
}); | ||
|
||
it('should validate "url" rule correctly', () => { | ||
expect(RuleAction.applyRule({ ruleName: 'url', param: '' }, 'http://example.com')).to.be.true; | ||
expect(RuleAction.applyRule({ ruleName: 'url', param: '' }, 'invalid-url')).to.be.false; | ||
}); | ||
}); | ||
|
||
describe('applyDependencyRule', () => { | ||
it('should validate "equal" dependency correctly', () => { | ||
expect(RuleAction.applyDependencyRule({ operator: 'equal', dependencyValue: 'test' }, 'test')).to.be.true; | ||
expect(RuleAction.applyDependencyRule({ operator: 'equal', dependencyValue: 'test' }, 'fail')).to.be.false; | ||
}); | ||
|
||
it('should validate "greaterThan" dependency correctly', () => { | ||
expect(RuleAction.applyDependencyRule({ operator: 'greaterThan', dependencyValue: 10 }, 15)).to.be.true; | ||
expect(RuleAction.applyDependencyRule({ operator: 'greaterThan', dependencyValue: 10 }, 5)).to.be.false; | ||
}); | ||
|
||
it('should validate "exists" dependency correctly', () => { | ||
expect(RuleAction.applyDependencyRule({ operator: 'exists' }, 'value')).to.be.true; | ||
expect(RuleAction.applyDependencyRule({ operator: 'exists' }, undefined)).to.be.false; | ||
}); | ||
}); | ||
|
||
describe('filter', () => { | ||
let mockReflector: ReturnType<typeof createMockReflector>['mockReflector']; | ||
let restoreReflector: ReturnType<typeof createMockReflector>['restore']; | ||
|
||
beforeEach(() => { | ||
const mock = createMockReflector(); | ||
mockReflector = mock.mockReflector; | ||
restoreReflector = mock.restore; | ||
}); | ||
afterEach(() => { | ||
sinon.restore(); | ||
restoreReflector(); | ||
}); | ||
it('should filter rules by pick option', () => { | ||
const mockRules = { | ||
username: { rules: 'string|min:5|max:20' }, | ||
email: { rules: 'string|email' }, | ||
age: { rules: 'integer|min:18' }, | ||
}; | ||
|
||
(Reflector.get as any).withArgs('validation:rules', sinon.match.any).returns(mockRules); | ||
|
||
class MockSchema {} | ||
|
||
const result = RuleAction.filter<any>(MockSchema, ['username', 'email']); | ||
|
||
expect(result).to.have.keys(['username', 'email']); | ||
expect(result).to.not.have.key('age'); | ||
}); | ||
|
||
it('should filter rules by omit option', () => { | ||
const mockRules = { | ||
username: { rules: 'string|min:5|max:20' }, | ||
email: { rules: 'string|email' }, | ||
age: { rules: 'integer|min:18' }, | ||
}; | ||
(Reflector.get as any).withArgs('validation:rules', sinon.match.any).returns(mockRules); | ||
const result = RuleAction.filter<any>(class MockSchema {}, undefined, ['age']); | ||
expect(result).to.have.keys(['username', 'email']); | ||
}); | ||
|
||
it('should return all rules if no filter options are provided', () => { | ||
const mockRules = { | ||
username: { rules: 'string|min:5|max:20' }, | ||
email: { rules: 'string|email' }, | ||
age: { rules: 'integer|min:18' }, | ||
}; | ||
(Reflector.get as any).withArgs('validation:rules', sinon.match.any).returns(mockRules); | ||
const result = RuleAction.filter(class MockSchema {}, undefined, undefined); | ||
expect(result).to.have.keys(['username', 'email', 'age']); | ||
}); | ||
}); | ||
|
||
describe('Edge Cases', () => { | ||
it('should handle empty string with "min" rule', () => { | ||
expect(RuleAction.applyRule({ ruleName: 'min', param: '0' }, '')).to.be.true; | ||
expect(RuleAction.applyRule({ ruleName: 'min', param: '1' }, '')).to.be.false; | ||
}); | ||
|
||
it('should handle null or undefined for rules', () => { | ||
expect(RuleAction.applyRule({ ruleName: 'required', param: '' }, null)).to.be.false; | ||
expect(RuleAction.applyRule({ ruleName: 'required', param: '' }, undefined)).to.be.false; | ||
}); | ||
|
||
it('should validate "regex" rule with complex patterns', () => { | ||
expect(RuleAction.applyRule({ ruleName: 'regex', param: '^[a-z]{3,5}$' }, 'test')).to.be.true; | ||
expect(RuleAction.applyRule({ ruleName: 'regex', param: '^[a-z]{3,5}$' }, 'toolong')).to.be.false; | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.