UNPKG

felicity

Version:

Javascript object instantiation from Joi schema

1,334 lines (1,078 loc) 58.1 kB
'use strict'; const Felicity = require('../lib'); const Joi = require('../lib/joi'); const Lab = require('@hapi/lab'); const Uuid = require('uuid'); const Moment = require('moment'); const { describe, expect, it } = exports.lab = Lab.script(); const ExpectValidation = require('./test_helpers').expectValidation.bind({}, expect); const generateUuid = (parent, helpers) => Uuid.v4(); generateUuid.description = 'Generates UUIDs'; describe('Felicity Example', () => { it('should return a string', () => { const schema = Joi.string(); const example = Felicity.example(schema); expect(example).to.be.a.string(); ExpectValidation(example, schema); }); it('should return a string that ignores regex lookarounds', () => { const lookAheadPattern = /abcd(?=efg)/; const schema = Joi.string().regex(lookAheadPattern); const example = Felicity.example(schema); expect(example).to.equal('abcd'); expect(example.match(lookAheadPattern)).to.equal(null); }); it('should throw validation error on regex lookarounds when provided strictExample config', () => { const schema = Joi.string().regex(/abcd(?=efg)/); const options = { config: { strictExample: true } }; const callExample = function () { return Felicity.example(schema, options); }; expect(callExample).to.throw('\"value\" with value \"abcd\" fails to match the required pattern: /abcd(?=efg)/'); }); it('should not throw validation error on supported regex when provided strictExample config', () => { const schema = Joi.string().regex(/abcd/); const options = { config: { strictExample: true } }; const callExample = function () { return Felicity.example(schema, options); }; expect(callExample).to.not.throw(); ExpectValidation(callExample(), schema); }); it('should return a number', () => { const schema = Joi.number(); const example = Felicity.example(schema); expect(example).to.be.a.number(); ExpectValidation(example, schema); }); it('should return a boolean', () => { const schema = Joi.boolean(); const example = Felicity.example(schema); expect(example).to.be.a.boolean(); ExpectValidation(example, schema); }); it('should return a buffer', () => { const schema = Joi.binary(); const example = Felicity.example(schema); expect(example).to.be.a.buffer(); ExpectValidation(example, schema); }); it('should return a date', () => { const schema = Joi.date(); const example = Felicity.example(schema); expect(example).to.be.a.date(); ExpectValidation(example, schema); }); it('should return a moment formatted date', () => { const fmt = 'HH:mm'; const schema = Joi.date().format(fmt); const example = Felicity.example(schema); const moment = new Moment(example, fmt, true); expect(example).to.be.a.string(); expect(moment.isValid()).to.equal(true); ExpectValidation(example, schema); }); it('should return a function', () => { const schema = Joi.func(); const example = Felicity.example(schema); expect(example).to.be.a.function(); ExpectValidation(example, schema); }); it('should return function keys', (done) => { const schema = Joi.object().keys({ func: Joi.func() }); const example = Felicity.example(schema); expect(example.func).to.be.a.function(); ExpectValidation(example, schema, done); }); describe('Array', () => { it('should return an array', () => { const schema = Joi.array(); const example = Felicity.example(schema); expect(example).to.be.an.array(); ExpectValidation(example, schema); }); describe('items', () => { it('should return optional items when includeOptional is set to true', () => { const schema = Joi.array().items(Joi.string().optional()); const example = Felicity.example(schema); expect(example).to.be.an.array(); expect(example[0]).to.be.a.string(); ExpectValidation(example, schema); }); it('should return objects with optional keys when includeOptional is set to true ', () => { const schema = Joi.array().items(Joi.object().keys({ key1: Joi.string(), key2: Joi.string().optional() })); const options = { config: { includeOptional: true } }; const example = Felicity.example(schema, options); expect(example).to.be.an.array(); expect(example[0].key1).to.exist(); expect(example[0].key2).to.exist(); ExpectValidation(example, schema); }); it('should return ordered object items with optional keys when includeOptional is set to true ', () => { const schema = Joi.array().ordered(Joi.object().keys({ key1: Joi.string(), key2: Joi.string().optional() })); const options = { config: { includeOptional: true } }; const example = Felicity.example(schema, options); expect(example).to.be.an.array(); expect(example[0].key1).to.exist(); expect(example[0].key2).to.exist(); ExpectValidation(example, schema); }); }); }); it('should return an object with default values', () => { const schema = Joi.object().keys({ string: Joi.string().required().default('-----'), number: Joi.number().default(4) }); const example = Felicity.example(schema); expect(example.string).to.equal('-----'); expect(example.number).to.equal(4); ExpectValidation(example, schema); }); it('should return an object with null default', () => { const schema = Joi.object().keys({ string: Joi.string().allow(null).default(null) }); const example = Felicity.example(schema, { config: { ignoreValids: true } }); expect(example.string).to.equal(null); ExpectValidation(example, schema); }); it('should return an object with dynamic defaults', () => { const generateDefaultString = () => { return '-----'; }; generateDefaultString.description = 'generates default'; const generateDefaultNumber = () => { return 4; }; generateDefaultNumber.description = 'generates default'; const generateDefaultBool = () => { return true; }; generateDefaultBool.description = 'generates default'; const schema = Joi.object().keys({ string: Joi.string().required().default(generateDefaultString), number: Joi.number().default(generateDefaultNumber), bool: Joi.boolean().default(generateDefaultBool) }); const example = Felicity.example(schema); expect(example.string).to.equal('-----'); expect(example.number).to.equal(4); expect(example.bool).to.equal(true); ExpectValidation(example, schema); }); it('should return an object with specified valid properties', () => { const schema = Joi.object().keys({ string: Joi.string().required().valid('-----'), number: Joi.number().valid(4) }); const example = Felicity.example(schema); expect(example.string).to.equal('-----'); expect(example.number).to.equal(4); ExpectValidation(example, schema); }); it('should return an object with "allowed" values', () => { const schema = Joi.object().keys({ string: Joi.string().allow(null).required() }); const example = Felicity.example(schema); expect(example.string).to.equal(null); ExpectValidation(example, schema); }); it('should ignore "allowed" values when provided "ignoreValids" config', () => { const schema = Joi.object().keys({ string: Joi.string().allow(null).required() }); const options = { config: { ignoreValids: true } }; const example = Felicity.example(schema, options); expect(example.string).to.not.equal(null); expect(example.string).to.be.a.string(); ExpectValidation(example, schema); }); it('should return an object with custom type', () => { const Class1 = function () {}; Class1.prototype.testFunc = function () {}; const schema = Joi.object().instance(Class1); const example = Felicity.example(schema); expect(example).to.be.an.instanceof(Class1); expect(example.testFunc).to.be.a.function(); ExpectValidation(example, schema); }); it('should not return an object with default values when provided ignoreDefaults config', () => { const schema = Joi.object().keys({ string: Joi.string().alphanum().required().default('-----'), number: Joi.number().default(4) }); const options = { config: { ignoreDefaults: true } }; const example = Felicity.example(schema, options); expect(example.string).to.not.equal('-----'); expect(example.number).to.not.equal(4); ExpectValidation(example, schema); }); it('should return an object without optional keys', () => { const schema = Joi.object().keys({ required: Joi.string().required(), present: Joi.string(), optional: Joi.string().optional() }); const example = Felicity.example(schema); expect(example.required).to.be.a.string(); expect(example.present).to.be.a.string(); expect(example.optional).to.be.undefined(); ExpectValidation(example, schema); }); it('should return an object without optional keys when using .options({ presence: "optional" }) syntax', () => { const schema = Joi.object().keys({ required: Joi.string().required(), parentOptional: Joi.string(), optional: Joi.string().optional() }).options({ presence: 'optional' }); const example = Felicity.example(schema); expect(example.required).to.be.a.string(); expect(example.parentOptional).to.be.undefined(); expect(example.optional).to.be.undefined(); ExpectValidation(example, schema); }); it('should return an object with optional keys when given includeOptional config', () => { const schema = Joi.object().keys({ required: Joi.string().required(), present: Joi.string(), optional: Joi.string().optional() }); const options = { config: { includeOptional: true } }; const example = Felicity.example(schema, options); expect(example.required).to.be.a.string(); expect(example.present).to.be.a.string(); expect(example.optional).to.be.a.string(); ExpectValidation(example, schema); }); it('should return the Joi facebook example', () => { const passwordPattern = /^[a-zA-Z0-9]{3,30}$/; const schema = Joi.object().keys({ username: Joi.string().alphanum().min(3).max(30).insensitive().required(), password: Joi.string().regex(passwordPattern), access_token: [Joi.string(), Joi.number()], birthyear: Joi.number().integer().min(1900).max(2013), email: Joi.string().email() }).with('username', 'birthyear').without('password', 'access_token'); const example = Felicity.example(schema); expect(example.password.match(passwordPattern)).to.not.equal(null); ExpectValidation(example, schema); }); it('should return the Joi facebook example with an optional key', () => { const passwordPattern = /^[a-zA-Z0-9]{3,30}$/; const schema = Joi.object().keys({ username: Joi.string().alphanum().min(3).max(30).required(), password: Joi.string().regex(passwordPattern), access_token: [Joi.string(), Joi.number()], birthyear: Joi.number().integer().min(1900).max(2013).optional(), email: Joi.string().email() }).with('username', 'birthyear').without('password', 'access_token'); const example = Felicity.example(schema); expect(example.password.match(passwordPattern)).to.not.equal(null); expect(example.birthyear).to.be.a.number(); ExpectValidation(example, schema); }); }); describe('Felicity EntityFor', () => { it('should fail when calling without proper schema', () => { expect(Felicity.entityFor).to.throw(Error, 'You must provide a Joi schema'); }); it('should return a constructor function', () => { const schema = {}; const Constructor = Felicity.entityFor(schema); expect(Constructor).to.be.a.function(); const skeleton = new Constructor(); expect(skeleton).to.be.an.object(); }); it('should enforce "new" instantiation on returned Constructor', () => { const schema = {}; const Constructor = Felicity.entityFor(schema); expect(Constructor).to.be.a.function(); expect(() => { return Constructor(); }).to.throw(TypeError); }); it('should error on non-object schema', () => { const numberSchema = Joi.number().max(1); const entityFor = function () { return Felicity.entityFor(numberSchema); }; expect(entityFor).to.throw(Error, 'Joi schema must describe an object for constructor functions'); }); it('should provide an example with dynamic defaults', () => { const generateDynamic = () => 'dynamic default'; generateDynamic.description = 'generates a default'; const schema = Joi.object().keys({ version: Joi.string().min(5).default('1.0.0'), number: Joi.number().default(10), identity: Joi.object().keys({ id: Joi.string().guid().default(generateUuid) }), array: Joi.array().items(Joi.object().keys({ id: Joi.string().guid().default(generateUuid) })), condition: Joi.alternatives().conditional('version', { is: Joi.string(), then: Joi.string().default('defaultValue'), otherwise: Joi.number() }), dynamicCondition: Joi.alternatives().conditional('version', { is: Joi.string(), then: Joi.string().default(generateDynamic), otherwise: Joi.number() }) }); const Entity = Felicity.entityFor(schema); const example = Entity.example(); expect(example.version).to.equal('1.0.0'); expect(example.number).to.equal(10); expect(example.array.length).to.be.above(0); expect(example.array[0].id).to.be.a.string(); expect(example.identity.id).to.be.a.string(); expect(example.condition).to.equal('defaultValue'); expect(example.dynamicCondition).to.equal('dynamic default'); }); describe('Constructor functions', () => { it('should accept override options', () => { const defaultOptions = { includeOptional: false, ignoreDefaults: true }; const schema = Joi.object().keys({ version: Joi.string().optional(), name: Joi.string().default('default value') }); const Entity = Felicity.entityFor(schema, { config: defaultOptions }); const instance = new Entity(); const instanceWithOptional = new Entity(null, { includeOptional: true }); const instanceWithDefault = new Entity(null, { ignoreDefaults: false }); const instanceWithBothOptions = new Entity(null, { includeOptional: true, ignoreDefaults: false }); expect(instance.version).to.equal(undefined); expect(instance.name).to.equal(null); expect(instanceWithOptional.version).to.equal(null); expect(instanceWithOptional.name).to.equal(null); expect(instanceWithDefault.version).to.equal(undefined); expect(instanceWithDefault.name).to.equal('default value'); expect(instanceWithBothOptions.version).to.equal(null); expect(instanceWithBothOptions.name).to.equal('default value'); }); it('should accept options', () => { const schema = Joi.object().keys({ version: Joi.string().optional(), name: Joi.string().default('default value') }); const Entity = Felicity.entityFor(schema); const instance = new Entity(); const instanceWithOptional = new Entity(null, { includeOptional: true }); const instanceWithDefault = new Entity(null, { ignoreDefaults: true }); const instanceWithBothOptions = new Entity(null, { includeOptional: true, ignoreDefaults: true }); expect(instance.version).to.equal(undefined); expect(instance.name).to.equal('default value'); expect(instanceWithOptional.version).to.equal(null); expect(instanceWithOptional.name).to.equal('default value'); expect(instanceWithDefault.version).to.equal(undefined); expect(instanceWithDefault.name).to.equal(null); expect(instanceWithBothOptions.version).to.equal(null); expect(instanceWithBothOptions.name).to.equal(null); }); it('should validate input when given validateInput: true', () => { const subSchema = Joi.object().keys({ name: Joi.string(), title: Joi.string() }).options({ presence: 'required' }); const schema = Joi.object().keys({ title: Joi.string(), director: Joi.number(), producers: Joi.array().items(subSchema).allow(null, '').optional().default(null) }); const Constructor = Felicity.entityFor(schema); const input = { name: 'Blade Runner', director: 'Denis Villeneuve', writers: [ { name: 'Hampton Fancher' } ] }; const instance = new Constructor(input); expect(instance.title).to.equal(null); expect(instance.producers).to.equal(null); expect(instance.director).to.equal(input.director); expect(() => new Constructor(input, { validateInput: true })).to.throw(`"director" must be a number. "name" is not allowed. "writers" is not allowed`); }); it('should validate and assign input when validateInput is true and input passes validation', () => { const schema = Joi.object().keys({ title: Joi.string(), director: Joi.string() }); const Constructor = Felicity.entityFor(schema); const input = { title: 'American Beauty', director: 'Sam Mendes' }; const instance = new Constructor(input, { validateInput: true }); expect(instance.title).to.equal(input.title); expect(instance.director).to.equal(input.director); }); it('should not validate input when given validateInput: false', () => { const subSchema = Joi.object().keys({ name: Joi.string(), title: Joi.string() }).options({ presence: 'required' }); const schema = Joi.object().keys({ title: Joi.string(), director: Joi.number(), producers: Joi.array().items(subSchema).allow(null, '').optional().default(null) }); const Constructor = Felicity.entityFor(schema, { config: { validateInput: true } }); const input = { name: 'Blade Runner', director: 'Denis Villeneuve', writers: [ { name: 'Hampton Fancher' } ] }; expect(() => new Constructor(input)).to.throw(`"director" must be a number. "name" is not allowed. "writers" is not allowed`); const instance = new Constructor(input, { validateInput: false }); expect(instance.title).to.equal(null); expect(instance.producers).to.equal(null); expect(instance.director).to.equal(input.director); }); }); describe('Constructor instances', () => { it('should accept override options when validating', () => { const schema = Joi.object().keys({ a: Joi.string() }); const options = { stripUnknown: true }; const Subject = Felicity.entityFor(schema); const subject = new Subject({ a: 'a' }); subject.b = 'b'; subject.validate((err) => { expect(err).to.be.null(); }, options); }); it('should return a validation object', () => { const schema = Joi.object().keys({ name: Joi.string().required() }); const Thing = Felicity.entityFor(schema); const thing = new Thing(); expect(thing.validate().success).to.exist().and.equal(false); expect(thing.validate().errors).to.exist().and.be.an.array(); expect(thing.validate().value).to.exist().and.equal({ name: null }); }); it('should follow the standard Node callback signature for .validate', () => { const schema = Joi.object().keys({ name: Joi.string().required() }); const Thing = Felicity.entityFor(schema); const thing = new Thing(); thing.validate((err, result) => { expect(err).to.exist(); expect(err).to.exist().and.be.an.array(); expect(result).to.be.null(); const validThing = new Thing({ name: 'pass' }); validThing.validate((err, validationResult) => { expect(err).to.be.null(); expect(validationResult).to.be.an.object(); expect(validationResult.success).to.exist().and.equal(true); expect(validationResult.value).to.exist().and.be.an.object(); expect(validationResult.errors).to.be.undefined(); }); }); }); it('should not trigger V8 JSON.stringify bug in Node v4.x', () => { const schema = Joi.object(); const Thing = Felicity.entityFor(schema); const thing = new Thing(); expect(JSON.stringify(thing, null, null)).to.equal('{}'); }); }); describe('"Action" schema options', () => { it('should not interfere with String.truncate', () => { const schema = Joi.object().keys({ name: Joi.string().max(5).truncate() }); const Constructor = Felicity.entityFor(schema); const instance = new Constructor(); const example = instance.example(); const validation = Constructor.validate({ name: 'longer than 5 chars' }); expect(instance.name).to.equal(null); expect(example.name.length).to.be.at.most(5); expect(validation.errors).to.equal(undefined); expect(validation.value).to.equal({ name: 'longe' }); ExpectValidation(example, schema); }); it('should not interfere with String.replace', () => { const schema = Joi.object().keys({ name: Joi.string().replace(/b/gi, 'a') }); const Constructor = Felicity.entityFor(schema); const instance = new Constructor(); const example = instance.example(); const validation = Constructor.validate({ name: 'abbabba' }); expect(instance.name).to.equal(null); expect(validation.errors).to.equal(undefined); expect(validation.value).to.equal({ name: 'aaaaaaa' }); ExpectValidation(example, schema); }); it('should not interfere with String.trim', () => { const schema = Joi.object().keys({ name: Joi.string().trim() }); const Constructor = Felicity.entityFor(schema); const instance = new Constructor({ name: ' abbabba ' }); const example = instance.example(); const validation = instance.validate(); expect(instance.name).to.equal('abbabba'); expect(validation.errors).to.equal(undefined); expect(validation.value).to.equal({ name: 'abbabba' }); ExpectValidation(example, schema); }); }); describe('"Presence" object binary schema options', () => { it('should not interfere with and', () => { const schema = Joi.object().keys({ a: Joi.string(), b: Joi.string() }).and('a', 'b'); const Constructor = Felicity.entityFor(schema); const instance = new Constructor({ a: 'abc', b: 'xyz' }); const example = instance.example(); ExpectValidation(example, schema); }); it('should not interfere with or', () => { const schema = Joi.object().keys({ a: Joi.string(), b: Joi.string() }).or('a', 'b'); const Constructor = Felicity.entityFor(schema); const instance = new Constructor({ a: 'abc', b: 'xyz' }); const example = instance.example(); ExpectValidation(example, schema); }); }); describe('"Presence" object property check schema options', () => { it('should not interfere with unknown when set to true', () => { const schema = Joi.object().keys({ a: Joi.string(), b: Joi.string() }).unknown(true); const Constructor = Felicity.entityFor(schema); const instance = new Constructor({ a: 'abc', b: 'xyz' }); const example = instance.example(); ExpectValidation(example, schema); }); it('should not interfere with unknown when set to false', () => { const schema = Joi.object().keys({ a: Joi.string(), b: Joi.string() }).unknown(false); const Constructor = Felicity.entityFor(schema); const instance = new Constructor({ a: 'abc', b: 'xyz' }); const example = instance.example(); ExpectValidation(example, schema); }); }); describe('Conditional', () => { it('should default to the "true" driver', () => { const schema = Joi.object().keys({ driver: true, myConditional: Joi.when('driver', { is: true, then: Joi.string().required(), otherwise: Joi.number().required() }) }); const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.myConditional).to.equal(null); expect(felicityInstance.validate).to.be.a.function(); }); }); describe('Object', () => { it('should return an object with no keys', () => { const schema = Joi.object().keys(); const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance).to.be.an.object(); expect(felicityInstance.validate).to.be.a.function(); }); it('should return an object with keys', () => { const schema = Joi.object().keys({ key1: Joi.object().keys().required() }); const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.key1).to.equal({}); expect(felicityInstance.validate).to.be.a.function(); }); it('should return an object without .strip\'ed keys', () => { const schema = Joi.object().keys({ key1: Joi.object().keys().required(), key2: Joi.any().strip() }); const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.key1).to.equal({}); expect(felicityInstance.key2).to.equal(undefined); expect(felicityInstance.validate).to.be.a.function(); }); it('should return an object with mixed-type keys', () => { const schema = Joi.object().keys({ innerObject: Joi.object().keys({ innerArray: Joi.array().items(Joi.number()).min(3).max(6).required(), number: Joi.number() }), string: Joi.string().email().required(), date: Joi.date().raw().required(), bool: Joi.boolean().required(), func: Joi.func().required(), conditional: Joi.alternatives().conditional('bool', { is: true, then: Joi.object().keys().required(), otherwise: Joi.boolean().required() }), any: Joi.any(), anyStrip: Joi.any().strip(), anyForbid: Joi.any().forbidden() }); const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.innerObject).to.be.an.object(); expect(felicityInstance.innerObject.innerArray).to.equal([]); expect(felicityInstance.innerObject.number).to.equal(0); expect(felicityInstance.string).to.equal(null); expect(felicityInstance.date).to.equal(null); expect(felicityInstance.bool).to.equal(false); expect(felicityInstance.func).to.equal(null); expect(felicityInstance.conditional).to.equal({}); expect(felicityInstance.any).to.equal(null); expect(felicityInstance.anyStrip).to.equal(undefined); expect(felicityInstance.anyForbid).to.be.undefined(); expect(felicityInstance.validate).to.be.a.function(); const mockInstance = felicityInstance.example(); ExpectValidation(mockInstance, schema); }); it('should return an object with mixed-type keys for non-compiled schema', () => { const schema = Joi.object().keys({ innerObject: Joi.object().keys({ innerArray: Joi.array().items(Joi.number()).min(3).max(6).required(), number: Joi.number() }), string: Joi.string().email().required(), date: Joi.date().raw().required(), bool: Joi.boolean().required(), conditional: Joi.alternatives().conditional('bool', { is: true, then: Joi.object().keys().required(), otherwise: Joi.boolean().required() }), optional: Joi.string().optional(), otherCond: Joi.alternatives().conditional('bool', { is: true, then: Joi.string().required(), otherwise: Joi.boolean().required() }) }); const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.innerObject).to.be.an.object(); expect(felicityInstance.innerObject.innerArray).to.equal([]); expect(felicityInstance.innerObject.number).to.equal(0); expect(felicityInstance.string).to.equal(null); expect(felicityInstance.date).to.equal(null); expect(felicityInstance.bool).to.equal(false); expect(felicityInstance.conditional).to.equal({}); expect(felicityInstance.optional).to.be.undefined(); expect(felicityInstance.otherCond).to.equal(null); expect(felicityInstance.validate).to.be.a.function(); }); it('should not include keys with "optional" flag', () => { const schema = Joi.object().keys({ key1: Joi.string().required(), key2: Joi.string(), key3: Joi.string().optional() }); const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.key1).to.equal(null); expect(felicityInstance.key2).to.equal(null); expect(felicityInstance.key3).to.not.exist(); expect(felicityInstance.validate).to.be.a.function(); }); it('should not include keys with "optional" flag when using .options({ presence: "optional" }) syntax', () => { const schema = Joi.object().keys({ key1: Joi.string().required(), key2: Joi.string(), key3: Joi.string().optional(), key4: Joi.object().keys({ a: Joi.string() }).options({ presence: 'optional' }) }).options({ presence: 'optional' }); const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.key1).to.equal(null); expect(felicityInstance.key2).to.not.exist(); expect(felicityInstance.key3).to.not.exist(); expect(felicityInstance.key4).to.not.exist(); expect(felicityInstance.validate).to.be.a.function(); }); it('should include keys with "optional" flag if provided includeOptional config', () => { const schema = Joi.object().keys({ key1: Joi.string().required(), key2: Joi.string(), key3: Joi.string().optional() }); const options = { config: { includeOptional: true } }; const Entity = Felicity.entityFor(schema, options); const felicityInstance = new Entity(); expect(felicityInstance.key1).to.equal(null); expect(felicityInstance.key2).to.equal(null); expect(felicityInstance.key3).to.equal(null); expect(felicityInstance.validate).to.be.a.function(); }); it('should utilize default values', () => { const schema = Joi.object().keys({ version: Joi.string().min(5).default('1.0.0'), number: Joi.number().default(10), identity: Joi.object().keys({ id: Joi.string().default('abcdefg') }), condition: Joi.alternatives().conditional('version', { is: Joi.string(), then: Joi.string().default('defaultValue'), otherwise: Joi.number() }) }); const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.version).to.equal('1.0.0'); expect(felicityInstance.number).to.equal(10); expect(felicityInstance.identity.id).to.equal('abcdefg'); expect(felicityInstance.condition).to.equal('defaultValue'); }); it('should not utilize default values when provided ignoreDefaults config', () => { const schema = Joi.object().keys({ version: Joi.string().min(5).default('1.0.0'), number: Joi.number().default(10), identity: Joi.object().keys({ id: Joi.string().default('abcdefg') }), condition: Joi.alternatives().conditional('version', { is: Joi.string(), then: Joi.string().default('defaultValue'), otherwise: Joi.number() }) }); const options = { config: { ignoreDefaults: true } }; const Entity = Felicity.entityFor(schema, options); const felicityInstance = new Entity(); expect(felicityInstance.version).to.equal(null); expect(felicityInstance.number).to.equal(0); expect(felicityInstance.identity.id).to.equal(null); expect(felicityInstance.condition).to.equal(null); }); it('should utilize default values for non-compiled schema', () => { const schema = { version: Joi.string().min(5).default('1.0.0'), number: Joi.number().default(10), identity: Joi.object().keys({ id: Joi.string().default('abcdefg') }), condition: Joi.alternatives().conditional('version', { is: Joi.string(), then: Joi.string().default('defaultValue'), otherwise: Joi.number() }) }; const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.version).to.equal('1.0.0'); expect(felicityInstance.number).to.equal(10); expect(felicityInstance.identity.id).to.equal('abcdefg'); expect(felicityInstance.condition).to.equal('defaultValue'); }); it('should not utilize default values for non-compiled schema when provided ignoreDefaults config', () => { const schema = { version: Joi.string().min(5).default('1.0.0'), number: Joi.number().default(10), identity: Joi.object().keys({ id: Joi.string().default('abcdefg') }), condition: Joi.alternatives().conditional('version', { is: Joi.string(), then: Joi.string().default('defaultValue'), otherwise: Joi.number() }) }; const options = { config: { ignoreDefaults: true } }; const Entity = Felicity.entityFor(schema, options); const felicityInstance = new Entity(); expect(felicityInstance.version).to.equal(null); expect(felicityInstance.number).to.equal(0); expect(felicityInstance.identity.id).to.equal(null); expect(felicityInstance.condition).to.equal(null); }); it('should utilize dynamic default values', () => { const schema = Joi.object().keys({ version: Joi.string().min(5).default('1.0.0'), number: Joi.number().default(10), identity: Joi.object().keys({ id: Joi.string().guid().default(generateUuid) }), condition: Joi.alternatives().conditional('version', { is: Joi.string(), then: Joi.string().default('defaultValue'), otherwise: Joi.number() }) }); const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.version).to.equal('1.0.0'); expect(felicityInstance.number).to.equal(10); expect(felicityInstance.identity.id).to.be.a.string(); expect(felicityInstance.condition).to.equal('defaultValue'); }); it('should not utilize dynamic default values when provided ignoreDefaults config', () => { const schema = Joi.object().keys({ version: Joi.string().min(5).default('1.0.0'), number: Joi.number().default(10), identity: Joi.object().keys({ id: Joi.string().guid().default(generateUuid) }), condition: Joi.alternatives().conditional('version', { is: Joi.string(), then: Joi.string().default('defaultValue'), otherwise: Joi.number() }) }); const options = { config: { ignoreDefaults: true } }; const Entity = Felicity.entityFor(schema, options); const felicityInstance = new Entity(); expect(felicityInstance.version).to.equal(null); expect(felicityInstance.number).to.equal(0); expect(felicityInstance.identity.id).to.equal(null); expect(felicityInstance.condition).to.equal(null); }); it('should utilize dynamic default values for non-compiled schema', () => { const schema = { version: Joi.string().min(5).default('1.0.0'), number: Joi.number().default(10), identity: Joi.object().keys({ id: Joi.string().guid().default(generateUuid) }), condition: Joi.alternatives().conditional('version', { is: Joi.string(), then: Joi.string().default('defaultValue'), otherwise: Joi.number() }) }; const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.version).to.equal('1.0.0'); expect(felicityInstance.number).to.equal(10); expect(felicityInstance.identity.id).to.be.a.string(); expect(felicityInstance.condition).to.equal('defaultValue'); }); it('should not utilize dynamic default values for non-compiled schema when provided ignoreDefaults config', () => { const schema = { version: Joi.string().min(5).default('1.0.0'), number: Joi.number().default(10), identity: Joi.object().keys({ id: Joi.string().guid().default(generateUuid) }), condition: Joi.alternatives().conditional('version', { is: Joi.string(), then: Joi.string().default('defaultValue'), otherwise: Joi.number() }) }; const options = { config: { ignoreDefaults: true } }; const Entity = Felicity.entityFor(schema, options); const felicityInstance = new Entity(); expect(felicityInstance.version).to.equal(null); expect(felicityInstance.number).to.equal(0); expect(felicityInstance.identity.id).to.equal(null); expect(felicityInstance.condition).to.equal(null); }); it('should return an object with alternatives keys', () => { const schema = Joi.object({ id: Joi.alternatives().try(Joi.number().integer().min(1), Joi.string().guid().lowercase()).required(), func: Joi.alternatives().conditional('id', { is: Joi.any(), then: Joi.func() }) }); const Entity = Felicity.entityFor(schema); const felicityInstance = new Entity(); expect(felicityInstance.id).to.equal(0); expect(felicityInstance.func).to.equal(null); }); }); describe('Input', () => { it('should include valid input', () => { const schema = { string: Joi.string().guid().required(), number: Joi.number().multiple(13).min(26).required(), object: Joi.object().keys({ id: Joi.string().min(3).default('OKC').required(), code: Joi.number().required() }).required() }; const hydratedInput = { string: Uuid.v4(), number: 39, object: { id: 'ATX', code: 200 } }; const felicityInstance = new (Felicity.entityFor(schema))(hydratedInput); expect(felicityInstance.string).to.equal(hydratedInput.string); expect(felicityInstance.number).to.equal(hydratedInput.number); expect(felicityInstance.object).to.equal(hydratedInput.object); }); it('should include valid input with strictInput set to true', () => { const schema = { string: Joi.string().guid().required(), number: Joi.number().multiple(13).min(26).required(), object: Joi.object().keys({ id: Joi.string().min(3).default('OKC').required(), code: Joi.number().required() }).required() }; const hydratedInput = { string: Uuid.v4(), number: 39, object: { id: 'ATX', code: 200 } }; const felicityInstance = new (Felicity.entityFor(schema, { config: { strictInput: true } }))(hydratedInput); expect(felicityInstance.string).to.equal(hydratedInput.string); expect(felicityInstance.number).to.equal(hydratedInput.number); expect(felicityInstance.object).to.equal(hydratedInput.object); }); it('should not strip unknown input values', () => { const schema = Joi.object().keys({ innerObject: Joi.object().keys({ innerArray: Joi.array().items(Joi.number()).min(3).max(6).required(), number: Joi.number().required().default(3), innerString: Joi.string().required() }), string: Joi.string().email().required(), date: Joi.date().raw().required(), binary: Joi.binary().required(), bool: Joi.boolean().required(), conditional: Joi.alternatives().conditional('bool', { is: true, then: Joi.object().keys().required(), otherwise: Joi.boolean().required() }), array: Joi.array().items(Joi.number()) }); const hydrationData = { innerObject: { innerString: false }, string: 'example@email.com', date: 'not a date', binary: 74, fake: true, bool: false, conditional: true, array: ['a', 'b', 'c'] }; const felicityInstance = new (Felicity.entityFor(schema))(hydrationData); expect(felicityInstance.innerObject).to.be.an.object(); expect(felicityInstance.innerObject.innerArray).to.equal([]); expect(felicityInstance.innerObject.innerString).to.equal(hydrationData.innerObject.innerString); expect(felicityInstance.innerObject.number).to.equal(3); expect(felicityInstance.string).to.equal(hydrationData.string); expect(felicityInstance.date).to.equal(hydrationData.date); expect(felicityInstance.binary).to.equal(hydrationData.binary); expect(felicityInstance.fake).to.equal(hydrationData.fake); expect(felicityInstance.bool).to.equal(hydrationData.bool); expect(felicityInstance.conditional).to.equal(hydrationData.conditional); expect(felicityInstance.array).to.equal(hydrationData.array); expect(felicityInstance.validate).to.be.a.function(); }); it('should strip unknown and invalid input values with strictInput set to true', () => { const schema = Joi.object().keys({ innerObject: Joi.object().keys({ innerArray: Joi.array().items(Joi.number()).min(3).max(6).required(), number: Joi.number().required().default(3), innerString: Joi.string().required() }), string: Joi.string().email().required(), date: Joi.date(