diff --git a/box.json b/box.json index ea538bc..72b3579 100644 --- a/box.json +++ b/box.json @@ -1,7 +1,7 @@ { "name":"ColdBox Validation", "author":"Ortus Solutions ", - "version":"2.0.0", + "version":"2.1.0", "location":"https://downloads.ortussolutions.com/ortussolutions/coldbox-modules/cbvalidation/@build.version@/cbvalidation-@build.version@.zip", "slug":"cbvalidation", "type":"modules", @@ -31,9 +31,9 @@ ".git*" ], "scripts":{ - "toMaster":"recipe build/toMaster.boxr", - "format":"cfformat run models/**/*.cfc,ModuleConfig.cfc,tests/specs/**/*.cfc,*.cfc --overwrite", - "format:check":"cfformat run models/**/*.cfc,ModuleConfig.cfc,tests/specs/**/*.cfc,*.cfc --check", - "lint":"cflint models/**.cfc --text --html --json --!exitOnError --suppress" + "toMaster":"recipe build/toMaster.boxr", + "format":"cfformat run models/**/*.cfc,ModuleConfig.cfc,tests/specs/**/*.cfc,*.cfc --overwrite", + "format:check":"cfformat run models/**/*.cfc,ModuleConfig.cfc,tests/specs/**/*.cfc,*.cfc --check", + "lint":"cflint models/**.cfc --text --html --json --!exitOnError --suppress" } } diff --git a/changelog.md b/changelog.md index 02b1401..5592f37 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # CHANGELOG +## 2.1.0 + +* `feature` : Added `constraintProfiles` to allow you to define which fields to validate according to defined profiles: https://github.com/coldbox-modules/cbvalidation/issues/37 +* `feature` : Updated `RequiredUnless` and `RequiredIf` to use struct literal notation instead of the weird parsing we did. +* `feature` : Added the `Unique` validator thanks to @elpete! +* `improvement` : Added `null` support for the `RequiredIf,RequiredUnless` validator values + ## 2.0.0 ### Features @@ -7,8 +14,8 @@ * No more manual discovery of validators, automated registration and lookup process, cleaned lots of code on this one! * New Validator: `Accepted` - The field under validation must be yes, on, 1, or true. This is useful for validating "Terms of Service" acceptance. * New Validator: `Alpha` - Only allows alphabetic characters -* New Validator: `RequiredUnless` with validation data: `anotherField:value,...` - The field under validation must be present and not empty unless the `anotherfield` field is equal to the passed `value`. -* New Validator: `RequiredIf` with validation data: `anotherField:value,...` - The field under validation must be present and not empty if the `anotherfield` field is equal to the passed `value`. +* New Validator: `RequiredUnless` with validation data as a struct literal `{ anotherField:value, ... }` - The field under validation must be present and not empty unless the `anotherfield` field is equal to the passed `value`. +* New Validator: `RequiredIf` with validation data as a struct literal `{ anotherField:value, ... }` - The field under validation must be present and not empty if the `anotherfield` field is equal to the passed `value`. * Accelerated validation by removing type checks. ACF chokes on interface checks ### Improvements diff --git a/models/Mixins.cfm b/models/Mixins.cfm index 414b38c..9f7a481 100644 --- a/models/Mixins.cfm +++ b/models/Mixins.cfm @@ -9,6 +9,7 @@ * @locale The i18n locale to use for validation messages * @excludeFields The fields to exclude from the validation * @includeFields The fields to include in the validation + * @profiles If passed, a list of profile names to use for validation constraints * * @return cbvalidation.model.result.IValidationResult */ @@ -26,6 +27,7 @@ function validate(){ * @locale The i18n locale to use for validation messages * @excludeFields The fields to exclude from the validation * @includeFields The fields to include in the validation + * @profiles If passed, a list of profile names to use for validation constraints * * @return The validated object or the structure fields that where validated * @throws ValidationException diff --git a/models/ValidationManager.cfc b/models/ValidationManager.cfc index e4a2148..4aa136e 100644 --- a/models/ValidationManager.cfc +++ b/models/ValidationManager.cfc @@ -117,6 +117,7 @@ component accessors="true" serialize="false" singleton { * @locale An optional locale to use for i18n messages * @excludeFields An optional list of fields to exclude from the validation. * @IncludeFields An optional list of fields to include in the validation. + * @profiles If passed, a list of profile names to use for validation constraints * * @return IValidationResult */ @@ -126,7 +127,8 @@ component accessors="true" serialize="false" singleton { any constraints = "", string locale = "", string excludeFields = "", - string includeFields = "" + string includeFields = "", + string profiles = "" ){ var targetName = ""; @@ -152,10 +154,38 @@ component accessors="true" serialize="false" singleton { locale : arguments.locale, targetName : targetName, resourceService : resourceService, - constraints : allConstraints + constraints : allConstraints, + profiles : arguments.profiles } ); + // Discover profiles, and update the includeFields list from it + if( len( arguments.profiles ) ){ + arguments.includeFields = arguments.profiles + .listToArray() + // Check if profiles defined in target and iterated one exists + .filter( function( profileKey ){ + return structKeyExists( target, "constraintProfiles" ) && structKeyExists( target.constraintProfiles, profileKey ); + } ) + // Incorporate fields from each profile + .map( function( profileKey ){ + // iterate all declared profile fields and incorporate into the includeFields + return target.constraintProfiles + .find( arguments.profileKey ) + .listToArray(); + } ) + // Reduce all fields into a single hashset to do a distinct collection + .reduce( function( result, item ){ + item + .each( function( thisField ){ + result.add( thisField ); + } ); + return result; + }, createObject( "java", "java.util.HashSet" ) ) + .toArray(); + arguments.includeFields = arrayToList( arguments.includeFields ); + } + // iterate over constraints defined for ( var thisField in allConstraints ) { var validateField = true; @@ -193,6 +223,7 @@ component accessors="true" serialize="false" singleton { * @locale An optional locale to use for i18n messages * @excludeFields An optional list of fields to exclude from the validation. * @IncludeFields An optional list of fields to include in the validation. + * @profiles If passed, a list of profile names to use for validation constraints * * @throws ValidationException * @return any,struct: The target object that was validated, or the structure fields that where validated. @@ -203,7 +234,8 @@ component accessors="true" serialize="false" singleton { any constraints = "", string locale = "", string excludeFields = "", - string includeFields = "" + string includeFields = "", + string profiles = "" ){ var vResults = this.validate( argumentCollection = arguments ); diff --git a/models/result/ValidationResult.cfc b/models/result/ValidationResult.cfc index 9e98f7a..76f4dd1 100644 --- a/models/result/ValidationResult.cfc +++ b/models/result/ValidationResult.cfc @@ -7,43 +7,49 @@ component accessors="true" { /** - * A collection of error objects represented in this result object - */ + * A collection of error objects represented in this result object + */ property name="errors" type="array"; /** - * Extra metadata you can store in the results object - */ + * Extra metadata you can store in the results object + */ property name="resultMetadata" type="struct"; /** - * The locale this result validation is using - */ + * The locale this result validation is using + */ property name="locale" type="string"; /** - * The name of the target object - */ + * The name of the target object + */ property name="targetName" type="string"; /** - * The constraints evaluated in the validation process - */ + * The constraints evaluated in the validation process + */ property name="constraints" type="struct"; /** - * The resource bundle object - */ + * The resource bundle object + */ property name="resourceService"; /** - * Constructor - */ + * The profiles used in the validation + */ + property name="profiles" type="string"; + + /** + * Constructor + */ ValidationResult function init( string locale = "", string targetName = "", any resourceService = "", - struct constraints = structNew() + struct constraints = structNew(), + string profiles = "" ){ variables.errors = []; variables.resultMetadata = {}; @@ -53,6 +59,7 @@ component accessors="true" { variables.targetName = arguments.targetName; variables.resourceService = arguments.resourceService; variables.constraints = arguments.constraints; + variables.profiles = arguments.profiles; return this; } diff --git a/models/validators/AcceptedValidator.cfc b/models/validators/AcceptedValidator.cfc index 644565c..8fff0c3 100644 --- a/models/validators/AcceptedValidator.cfc +++ b/models/validators/AcceptedValidator.cfc @@ -18,11 +18,12 @@ component accessors="true" singleton { /** * Will check if an incoming value validates - * @validationResultThe result object of the validation - * @targetThe target object to validate on - * @fieldThe field on the target object to validate on - * @targetValueThe target value to validate - * @validationDataThe validation data the validator was created with + * + * @validationResult The result object of the validation + * @target The target object to validate on + * @field The field on the target object to validate on + * @targetValue The target value to validate + * @validationData The validation data the validator was created with */ boolean function validate( required any validationResult, diff --git a/models/validators/RequiredIfValidator.cfc b/models/validators/RequiredIfValidator.cfc index 9181b7d..6da077a 100644 --- a/models/validators/RequiredIfValidator.cfc +++ b/models/validators/RequiredIfValidator.cfc @@ -2,7 +2,8 @@ * Copyright since 2020 by Ortus Solutions, Corp * www.ortussolutions.com * --- - * This validator checks if a field has value and not null + * This validator checks a struct of key-value pairs passed in the validation data. + * If those key-value pairs are equal then the target field will be required */ component accessors="true" extends="RequiredValidator" singleton { @@ -18,11 +19,12 @@ component accessors="true" extends="RequiredValidator" singleton { /** * Will check if an incoming value validates - * @validationResultThe result object of the validation - * @targetThe target object to validate on - * @fieldThe field on the target object to validate on - * @targetValueThe target value to validate - * @validationDataThe validation data the validator was created with + * + * @validationResult The result object of the validation + * @target The target object to validate on + * @field The field on the target object to validate on + * @targetValue The target value to validate + * @validationData The validation data the validator was created with */ boolean function validate( required any validationResult, @@ -31,24 +33,29 @@ component accessors="true" extends="RequiredValidator" singleton { any targetValue, any validationData ){ - // Validation Data Format: property:value,... - var validationArray = arguments.validationData.listToArray(); + // If you passed in simple data, conver it to a struct, simple values are not evaluated + if( isSimpleValue( arguments.validationData ) ){ + arguments.validationData = {}; + } + // Inflate to array to test multiple properties - var isRequired = validationArray - .map( function( item ){ + var isRequired = arguments.validationData + .map( function( key, value ){ // Get comparison values - var compareProperty = getToken( arguments.item, 1, ":" ); - var compareValue = getToken( arguments.item, 2, ":" ); - var comparePropertyValue = invoke( target, "get#compareProperty#" ); + var comparePropertyValue = invoke( target, "get#key#" ); + // Null checks + if( isNull( comparePropertyValue ) ){ + return isNull( arguments.value ); + } // Check if the compareValue is the same as the defined one - return ( compareValue == comparePropertyValue ? true : false ); + return ( arguments.value == comparePropertyValue ? true : false ); } ) // AND them all for a single result - .reduce( function( result, item ){ - return arguments.result && arguments.item; + .reduce( function( result, key, value ){ + return ( arguments.value && arguments.result ); }, true ); - if( !validationArray.len() || !isRequired ){ + if( !arguments.validationData.count() || !isRequired ){ return true; } diff --git a/models/validators/RequiredUnlessValidator.cfc b/models/validators/RequiredUnlessValidator.cfc index a4db4b9..268378a 100644 --- a/models/validators/RequiredUnlessValidator.cfc +++ b/models/validators/RequiredUnlessValidator.cfc @@ -2,7 +2,8 @@ * Copyright since 2020 by Ortus Solutions, Corp * www.ortussolutions.com * --- - * This validator checks if a field has value and not null + * This validator checks a struct of key-value pairs passed in the validation data. + * If those key-value pairs are equal then the target field will NOT be required */ component accessors="true" extends="RequiredValidator" singleton { @@ -18,11 +19,12 @@ component accessors="true" extends="RequiredValidator" singleton { /** * Will check if an incoming value validates - * @validationResultThe result object of the validation - * @targetThe target object to validate on - * @fieldThe field on the target object to validate on - * @targetValueThe target value to validate - * @validationDataThe validation data the validator was created with + * + * @validationResult The result object of the validation + * @target The target object to validate on + * @field The field on the target object to validate on + * @targetValue The target value to validate + * @validationData The validation data the validator was created with */ boolean function validate( required any validationResult, @@ -31,24 +33,30 @@ component accessors="true" extends="RequiredValidator" singleton { any targetValue, any validationData ){ - // Validation Data Format: property:value,... - var validationArray = arguments.validationData.listToArray(); - // Inflate to array to test multiple properties - var isOptional = validationArray - .map( function( item ){ + // If you passed in simple data, conver it to a struct, simple values are not evaluated + if( isSimpleValue( arguments.validationData ) ){ + arguments.validationData = {}; + } + + // Test the data + var isOptional = arguments.validationData + .map( function( key, value ){ // Get comparison values - var compareProperty = getToken( arguments.item, 1, ":" ); - var compareValue = getToken( arguments.item, 2, ":" ); - var comparePropertyValue = invoke( target, "get#compareProperty#" ); + var comparePropertyValue = invoke( target, "get#key#" ); + // Null checks + if( isNull( comparePropertyValue ) ){ + return isNull( arguments.value ); + } // Check if the compareValue is the same as the defined one - return ( compareValue == comparePropertyValue ? true : false ); + return ( arguments.value == comparePropertyValue ? true : false ); } ) // AND them all for a single result - .reduce( function( result, item ){ - return ( arguments.item && arguments.result ); + .reduce( function( result, key, value ){ + return ( arguments.value && arguments.result ); }, true ); - if( validationArray.len() && isOptional ){ + // If we have data, then test the optional + if( arguments.validationData.count() && isOptional ){ return true; } diff --git a/models/validators/UDFValidator.cfc b/models/validators/UDFValidator.cfc index e2431e4..008a12c 100644 --- a/models/validators/UDFValidator.cfc +++ b/models/validators/UDFValidator.cfc @@ -31,13 +31,13 @@ component accessors="true" singleton { any targetValue, any validationData ){ - // return true if no data to check, type needs a data element to be checked. - if ( isNull( arguments.targetValue ) || ( isSimpleValue( arguments.targetValue ) && !len( arguments.targetValue ) ) ) { - return true; - } + // Validate against the UDF/closure + var passed = arguments.validationData( + isNull( arguments.targetValue ) ? javacast( "null", "" ) : arguments.targetValue, + arguments.target + ); - // Validate against the UDF/closure - if ( arguments.validationData( arguments.targetValue, arguments.target ) ) { + if ( passed ) { return true; } @@ -45,7 +45,7 @@ component accessors="true" singleton { message : "The '#arguments.field#' value does not validate", field : arguments.field, validationType : getName(), - rejectedValue : ( isSimpleValue( arguments.targetValue ) ? arguments.targetValue : "" ), + rejectedValue : !isNull( arguments.targetValue ) && isSimpleValue( arguments.targetValue ) ? arguments.targetValue : "", validationData : arguments.validationData }; diff --git a/models/validators/UniqueValidator.cfc b/models/validators/UniqueValidator.cfc new file mode 100644 index 0000000..415dc08 --- /dev/null +++ b/models/validators/UniqueValidator.cfc @@ -0,0 +1,70 @@ +/** + * Copyright since 2020 by Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * This validator checks the database according to validation data for field uniqueness + * - table : The table name to seek + * - column : The column to evaluate for uniqueness or defaults to the name of the field + */ +component accessors="true" singleton { + + property name="name"; + + /** + * Constructor + */ + UniqueValidator function init(){ + variables.name = "Unique"; + return this; + } + + /** + * Will check if an incoming value validates + * + * @validationResult The result object of the validation + * @target The target object to validate on + * @field The field on the target object to validate on + * @targetValue The target value to validate + * @validationData The validation data the validator was created with + */ + boolean function validate( + required any validationResult, + required any target, + required string field, + any targetValue, + any validationData + ){ + // Default the target column + var targetColumn = ( isNull( arguments.validationData.column ) ? arguments.field : arguments.validationData.column ); + // Query it + var exists = queryExecute( + "SELECT 1 FROM #arguments.validationData.table# WHERE #targetColumn# = ?", + [ arguments.targetValue ] + ).recordCount > 0; + + if ( !exists ) { + return true; + } + + validationResult.addError( + validationResult.newError( + argumentCollection = { + message : "The #targetColumn# '#arguments.targetValue#' is already in use", + field : arguments.field, + validationType : getName(), + validationData : arguments.validationData + } + ) + ); + + return false; + } + + /** + * Get the name of the validator + */ + string function getName(){ + return "Unique"; + } + +} \ No newline at end of file diff --git a/readme.md b/readme.md index 2386347..54345ab 100644 --- a/readme.md +++ b/readme.md @@ -25,7 +25,69 @@ Leverage CommandBox to install: `box install cbvalidation` -The module will register several objects into WireBox using the `@cbvalidation` namespace. The validation manager is registered as `ValidationManager@cbvalidation`. It will also register several helper methods that can be used throughout the ColdBox application. +The module will register several objects into WireBox using the `@cbvalidation` namespace. The validation manager is registered as `ValidationManager@cbvalidation`. It will also register several helper methods that can be used throughout the ColdBox application: `validate(), validateOrFail(), getValidationManager()` + +## Mixins + +The module will also register several methods in your handlers/interceptors/layouts/views + +```js +/** + * Validate an object or structure according to the constraints rules. + * + * @target An object or structure to validate + * @fields The fields to validate on the target. By default, it validates on all fields + * @constraints A structure of constraint rules or the name of the shared constraint rules to use for validation + * @locale The i18n locale to use for validation messages + * @excludeFields The fields to exclude from the validation + * @includeFields The fields to include in the validation + * + * @return cbvalidation.model.result.IValidationResult + */ +function validate() + +/** + * Validate an object or structure according to the constraints rules and throw an exception if the validation fails. + * The validation errors will be contained in the `extendedInfo` of the exception in JSON format + * + * @target An object or structure to validate + * @fields The fields to validate on the target. By default, it validates on all fields + * @constraints A structure of constraint rules or the name of the shared constraint rules to use for validation + * @locale The i18n locale to use for validation messages + * @excludeFields The fields to exclude from the validation + * @includeFields The fields to include in the validation + * + * @return The validated object or the structure fields that where validated + * @throws ValidationException + */ +function validateOrFail() + +/** + * Retrieve the application's configured Validation Manager + */ +function getValidationManager() +``` + +## Settings + +Here are the module settings you can place in your `ColdBox.cfc` by using the `validation` settings structure: + +```js +validation = { + // The third-party validation manager to use, by default it uses CBValidation. + manager = "class path", + + // You can store global constraint rules here with unique names + sharedConstraints = { + name = { + field = { constraints here } + } + } + +} +``` + +You can read more about ColdBox Validation here: - https://coldbox-validation.ortusbooks.com/ ## Constraints @@ -68,10 +130,14 @@ this.constraints = { required : boolean [false], // The field under validation must be present and not empty if the `anotherfield` field is equal to the passed `value`. - requiredIf : anotherfield:value,anotherfield:value,... + requiredIf : { + anotherfield:value, anotherfield:value + } // The field under validation must be present and not empty unless the `anotherfield` field is equal to the passed - requiredUnless : anotherfield:value,anotherfield:value,... + requiredUnless : { + anotherfield:value, anotherfield:value + } // same as but with no case sameAsNoCase : propertyName @@ -87,6 +153,12 @@ this.constraints = { // UDF to use for validation, must return boolean accept the incoming value and target object, validate(value,target):boolean udf = variables.UDF or this.UDF or a closure. + + // Check if a column is unique in the database + unique = { + table : The table name, + column : The column to check, defaults to the property field in check + } // Custom validator, must implement coldbox.system.validation.validators.IValidator validator : path or wirebox id, example: 'mypath.MyValidator' or 'id:MyValidator' @@ -95,70 +167,6 @@ this.constraints = { } ``` -## Mixins - -The module will also register several methods in your handlers/interceptors/layouts/views - -```js -/** - * Validate an object or structure according to the constraints rules. - * - * @target An object or structure to validate - * @fields The fields to validate on the target. By default, it validates on all fields - * @constraints A structure of constraint rules or the name of the shared constraint rules to use for validation - * @locale The i18n locale to use for validation messages - * @excludeFields The fields to exclude from the validation - * @includeFields The fields to include in the validation - * - * @return cbvalidation.model.result.IValidationResult - */ -function validate() - -/** - * Validate an object or structure according to the constraints rules and throw an exception if the validation fails. - * The validation errors will be contained in the `extendedInfo` of the exception in JSON format - * - * @target An object or structure to validate - * @fields The fields to validate on the target. By default, it validates on all fields - * @constraints A structure of constraint rules or the name of the shared constraint rules to use for validation - * @locale The i18n locale to use for validation messages - * @excludeFields The fields to exclude from the validation - * @includeFields The fields to include in the validation - * - * @return The validated object or the structure fields that where validated - * @throws ValidationException - */ -function validateOrFail() - -/** - * Retrieve the application's configured Validation Manager - */ -function getValidationManager() -``` - -## Settings - -Here are the module settings you can place in your `ColdBox.cfc` by using the `validation` settings structure: - -```js -validation = { - // The third-party validation manager to use, by default it uses CBValidation. - manager = "class path", - - // You can store global constraint rules here with unique names - sharedConstraints = { - name = { - field = { constraints here } - } - } - -} -``` - -You can read more about ColdBox Validation here: - https://coldbox-validation.ortusbooks.com/ - ---- - ``` ******************************************************************************** Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp diff --git a/test-harness/Application.cfc b/test-harness/Application.cfc index adb1dc8..fa92fec 100644 --- a/test-harness/Application.cfc +++ b/test-harness/Application.cfc @@ -8,13 +8,13 @@ component{ // UPDATE THE NAME OF THE MODULE IN TESTING BELOW request.MODULE_NAME = "cbvalidation"; - + // Application properties this.name = hash( getCurrentTemplatePath() ); this.sessionManagement = true; this.sessionTimeout = createTimeSpan(0,0,15,0); this.setClientCookies = true; - + /************************************** LUCEE Specific Settings **************************************/ @@ -58,7 +58,6 @@ component{ // request start public boolean function onRequestStart(String targetPage){ - // Process ColdBox Request application.cbBootstrap.onRequestStart( arguments.targetPage ); @@ -77,4 +76,4 @@ component{ return application.cbBootstrap.onMissingTemplate( argumentCollection=arguments ); } -} \ No newline at end of file +} diff --git a/test-harness/box.json b/test-harness/box.json index c7b8708..9f09464 100644 --- a/test-harness/box.json +++ b/test-harness/box.json @@ -6,10 +6,8 @@ "description":"", "dependencies":{ "coldbox":"^5.0.0", - "cbi18n":"^1.4.0" - }, - "devDependencies":{ - "testbox":"^3.0.0" + "cbi18n":"^1.4.0", + "testbox":"^3.1.0+339" }, "installPaths":{ "coldbox":"coldbox/", @@ -19,4 +17,4 @@ "testbox":{ "runner":"http://localhost:60299/tests/runner.cfm" } -} \ No newline at end of file +} diff --git a/test-harness/handlers/Main.cfc b/test-harness/handlers/Main.cfc index 6303d64..3ae1f5a 100644 --- a/test-harness/handlers/Main.cfc +++ b/test-harness/handlers/Main.cfc @@ -57,8 +57,8 @@ component{ } /** - * validateOrFailWithObject - */ + * validateOrFailWithObject + */ function validateOrFailWithObject( event, rc, prc ){ var oModel = populateModel( "User" ); @@ -69,6 +69,19 @@ component{ return "Validated"; } + /** + * validateOrFailWithObjectProfiles + */ + function validateOrFailWithProfiles( event, rc, prc ){ + + var oModel = populateModel( "User" ); + + // validate + prc.object = validateOrFail( target=oModel, profiles=rc._profiles ); + + return "Validated"; + } + // Run on first init any function onAppInit( event, rc, prc ){ diff --git a/test-harness/models/User.cfc b/test-harness/models/User.cfc index 1e79ddc..abbf7bd 100644 --- a/test-harness/models/User.cfc +++ b/test-harness/models/User.cfc @@ -1,15 +1,21 @@ -component accessors="true"{ +component accessors="true" { property name="username" default=""; property name="password" default=""; + property name="email" default=""; + this.constraintProfiles = { + new : "username,password,email", + update : "username,email" + }; this.constraints = { - username = {required=true, size="2..20"}, - password = {required=true, size="2..20"} + username : { required : true, size : "2..20" }, + password : { required : true, size : "2..20" }, + email : { required : true, type : "email" } }; function init(){ return this; } -} \ No newline at end of file +} diff --git a/test-harness/tests/specs/ValidationIntegrations.cfc b/test-harness/tests/specs/ValidationIntegrations.cfc index c5cd1c6..a35571f 100644 --- a/test-harness/tests/specs/ValidationIntegrations.cfc +++ b/test-harness/tests/specs/ValidationIntegrations.cfc @@ -17,6 +17,8 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" { function run(){ describe( "Integrations Specs", function(){ beforeEach( function( currentSpec ){ + structDelete( application, "cbController" ); + structDelete( application, "wirebox" ); // Setup as a new ColdBox request, VERY IMPORTANT. ELSE EVERYTHING LOOKS LIKE THE SAME REQUEST. setup(); } ); @@ -71,6 +73,7 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" { params = { username : "luis", password : "luis", + email : "lmajano@ortussolutions.com", bogus : now(), anotherBogus : now() }, @@ -83,7 +86,63 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" { } ); } ); } ); + + + story( "I want to Validate with contraint profiles", function(){ + given( "a single profile", function(){ + then( "it must validate it with only those fields in that profile", function(){ + var e = this.request( + route = "/main/validateOrFailWithProfiles", + params = { + username : "luis", + email : "lmajano@ortussolutions.com", + bogus : now(), + anotherBogus : now(), + _profiles : "update" + }, + method = "post" + ); + + var object = e.getPrivateValue( "object" ); + debug( object ); + expect( object ).toBeComponent(); + }); + }); + given( "a multiple profiles", function(){ + then( "it must validate it with only distinct fields in those profiles", function(){ + var e = this.request( + route = "/main/validateOrFailWithProfiles", + params = { + username : "luis", + email : "lmajano@ortussolutions.com", + password : "luis", + anotherBogus : now(), + _profiles : "update,new,bogus" + }, + method = "post" + ); + + var object = e.getPrivateValue( "object" ); + debug( object ); + expect( object ).toBeComponent(); + }); + }); + given( "a single profile and invalid data", function(){ + then( "then throw the exception", function(){ + + expect( function(){ + this.request( route = "/main/validateOrFailWithProfiles", params = { + username : "luis" + }, method = "post" ); + } ).toThrow(); + + }); + }); + }); + + } ); + } } diff --git a/test-harness/tests/specs/validators/AcceptedValidatorTest.cfc b/test-harness/tests/specs/validators/AcceptedValidatorTest.cfc index e2141f4..a43c8dc 100644 --- a/test-harness/tests/specs/validators/AcceptedValidatorTest.cfc +++ b/test-harness/tests/specs/validators/AcceptedValidatorTest.cfc @@ -3,7 +3,7 @@ */ component extends="coldbox.system.testing.BaseModelTest" model="cbvalidation.models.validators.AcceptedValidator" { -/*********************************** LIFE CYCLE Methods ***********************************/ + /*********************************** LIFE CYCLE Methods ***********************************/ // executes before all suites+specs in the run() method function beforeAll(){ @@ -13,43 +13,42 @@ component extends="coldbox.system.testing.BaseModelTest" model="cbvalidation.mod // executes after all suites+specs in the run() method function afterAll(){ - } -/*********************************** BDD SUITES ***********************************/ + /*********************************** BDD SUITES ***********************************/ function run( testResults, testBox ){ // all your suites go here. describe( "Accepted", function(){ - it( "can evaluate true when the value is 1,true,on and yes", function(){ var result = createMock( "cbvalidation.models.result.ValidationResult" ).init(); + expect( model.validate( result, this, "testField", "1", "" ) ).toBeTrue(); expect( - model.validate( result, this, "testField", "1", "" ) - ).toBeTrue(); - expect( - model.validate( result, this, "testField", "true", "" ) - ).toBeTrue(); - expect( - model.validate( result, this, "testField", "on", "" ) - ).toBeTrue(); - expect( - model.validate( result, this, "testField", "yes", "" ) + model.validate( + result, + this, + "testField", + "true", + "" + ) ).toBeTrue(); + expect( model.validate( result, this, "testField", "on", "" ) ).toBeTrue(); + expect( model.validate( result, this, "testField", "yes", "" ) ).toBeTrue(); expect( - model.validate( result, this, "testField", "false", "" ) - ).toBeFalse(); - expect( - model.validate( result, this, "testField", "n", "" ) - ).toBeFalse(); - expect( - model.validate( result, this, "testField", "no", "" ) + model.validate( + result, + this, + "testField", + "false", + "" + ) ).toBeFalse(); + expect( model.validate( result, this, "testField", "n", "" ) ).toBeFalse(); + expect( model.validate( result, this, "testField", "no", "" ) ).toBeFalse(); } ); - - }); + } ); } } diff --git a/test-harness/tests/specs/validators/AlphaValidatorTest.cfc b/test-harness/tests/specs/validators/AlphaValidatorTest.cfc index f952d33..8a55fcb 100644 --- a/test-harness/tests/specs/validators/AlphaValidatorTest.cfc +++ b/test-harness/tests/specs/validators/AlphaValidatorTest.cfc @@ -13,30 +13,45 @@ component extends="coldbox.system.testing.BaseModelTest" model="cbvalidation.mod // executes after all suites+specs in the run() method function afterAll(){ - } -/*********************************** BDD SUITES ***********************************/ + /*********************************** BDD SUITES ***********************************/ function run( testResults, testBox ){ // all your suites go here. describe( "Accepted", function(){ - it( "can evaluate true when alpha", function(){ var result = createMock( "cbvalidation.models.result.ValidationResult" ).init(); expect( - model.validate( result, this, "testField", "alpha", "" ) + model.validate( + result, + this, + "testField", + "alpha", + "" + ) ).toBeTrue(); expect( - model.validate( result, this, "testField", "asdf22", "" ) + model.validate( + result, + this, + "testField", + "asdf22", + "" + ) ).toBeFalse(); expect( - model.validate( result, this, "testField", "234--$", "" ) + model.validate( + result, + this, + "testField", + "234--$", + "" + ) ).toBeFalse(); } ); - - }); + } ); } - } \ No newline at end of file +} diff --git a/test-harness/tests/specs/validators/MethodValidatorTest.cfc b/test-harness/tests/specs/validators/MethodValidatorTest.cfc index 14d89b5..0bcd7f3 100644 --- a/test-harness/tests/specs/validators/MethodValidatorTest.cfc +++ b/test-harness/tests/specs/validators/MethodValidatorTest.cfc @@ -12,9 +12,8 @@ component extends="coldbox.system.testing.BaseModelTest" model="cbvalidation.mod } function testValidate(){ - var result = createMock( "cbvalidation.models.result.ValidationResult" ).init(); - var mock = createStub().$( "validate", false ).$( "coolValidate", true ); + var mock = createStub().$( "validate", false ).$( "coolValidate", true ); // call coolvalidate diff --git a/test-harness/tests/specs/validators/RequiredIfValidatorTest.cfc b/test-harness/tests/specs/validators/RequiredIfValidatorTest.cfc index f9e2114..570f9d6 100644 --- a/test-harness/tests/specs/validators/RequiredIfValidatorTest.cfc +++ b/test-harness/tests/specs/validators/RequiredIfValidatorTest.cfc @@ -13,45 +13,90 @@ component extends="coldbox.system.testing.BaseModelTest" model="cbvalidation.mod // executes after all suites+specs in the run() method function afterAll(){ - } -/*********************************** BDD SUITES ***********************************/ + /*********************************** BDD SUITES ***********************************/ function run( testResults, testBox ){ // all your suites go here. describe( "Accepted", function(){ - it( "can make targets required if the properties passed have the right value", function(){ var mock = createStub() .$( "getName", "luis" ) - .$( "getRole", "admin" ); + .$( "getRole", "admin" ) + .$( "getMissing", javacast( "null", "" ) ); var result = createMock( "cbvalidation.models.result.ValidationResult" ).init(); expect( - model.validate( result, mock, "testField", "", "name:luis" ) + model.validate( + result, + mock, + "testField", + javacast( "null", "" ), + { missing : javacast( "null", "" ) } + ) + ).toBeFalse(); + + expect( + model.validate( + result, + mock, + "testField", + "", + { name : "luis" } + ) ).toBeFalse(); expect( - model.validate( result, mock, "testField", "", "name:luis,role:admin" ) + model.validate( + result, + mock, + "testField", + "", + { name : "luis", role : "admin" } + ) ).toBeFalse(); expect( - model.validate( result, mock, "testField", "test", "name:luis,role:admin" ) + model.validate( + result, + mock, + "testField", + "test", + { name : "luis", role : "admin" } + ) ).toBeTrue(); expect( - model.validate( result, mock, "testField", "shouldPass", "name:luis,role:admin" ) + model.validate( + result, + mock, + "testField", + "shouldPass", + { name : "luis", role : "admin" } + ) ).toBeTrue(); expect( - model.validate( result, mock, "testField", "", "name:luis" ) + model.validate( + result, + mock, + "testField", + "", + { name : "luis" } + ) ).toBeFalse(); - - + expect( + model.validate( + result, + mock, + "testField", + "shouldPass", + { missing : javacast( "null", "" ) } + ) + ).toBeTrue(); } ); - - }); + } ); } - } \ No newline at end of file +} diff --git a/test-harness/tests/specs/validators/RequiredUnlessValidatorTest.cfc b/test-harness/tests/specs/validators/RequiredUnlessValidatorTest.cfc index d2f0a65..f729dab 100644 --- a/test-harness/tests/specs/validators/RequiredUnlessValidatorTest.cfc +++ b/test-harness/tests/specs/validators/RequiredUnlessValidatorTest.cfc @@ -1,7 +1,10 @@ /** * My BDD Test */ -component extends="coldbox.system.testing.BaseModelTest" model="cbvalidation.models.validators.RequiredUnlessValidator" { +component + extends="coldbox.system.testing.BaseModelTest" + model ="cbvalidation.models.validators.RequiredUnlessValidator" +{ /*********************************** LIFE CYCLE Methods ***********************************/ @@ -13,56 +16,110 @@ component extends="coldbox.system.testing.BaseModelTest" model="cbvalidation.mod // executes after all suites+specs in the run() method function afterAll(){ - } -/*********************************** BDD SUITES ***********************************/ + /*********************************** BDD SUITES ***********************************/ function run( testResults, testBox ){ // all your suites go here. - describe( "Accepted", function(){ - + describe( "RequiredUnless", function(){ it( "can make targets required unless the properties passed have the right value", function(){ var mock = createStub() .$( "getName", "luis" ) - .$( "getRole", "admin" ); + .$( "getRole", "admin" ) + .$( "getMissing", javacast( "null", "" ) ); var result = createMock( "cbvalidation.models.result.ValidationResult" ).init(); - expect( - model.validate( result, mock, "testField", "", "" ) - ).toBeFalse(); - expect( - model.validate( result, mock, "testField", "", "name:luis,role:admin" ) - ).toBeTrue(); + // Empty string for validation data + expect( model.validate( result, mock, "testField", "", "" ) ).toBeFalse(); + + // Empty struct for validation data + expect( model.validate( result, mock, "testField", "", {} ) ).toBeFalse(); + + expect( - model.validate( result, mock, "testField", "shouldPass", "name:luis,role:admin" ) + model.validate( + result, + mock, + "testField", + "shouldPass", + { "name" : "luis", "role" : "admin" } + ) ).toBeTrue(); expect( - model.validate( result, mock, "testField", "", "name:luis,role:admin" ) + model.validate( + result, + mock, + "testField", + "", + { "name" : "luis", "role" : "admin" } + ) ).toBeTrue(); expect( - model.validate( result, mock, "testField", "", "name:luis" ) + model.validate( + result, + mock, + "testField", + "", + { "name" : "luis" } + ) ).toBeTrue(); expect( - model.validate( result, mock, "testField", "", "name:luiss" ) + model.validate( + result, + mock, + "testField", + "", + { "name" : "luiss" } + ) ).toBeFalse(); expect( - model.validate( result, mock, "testField", javaCast( "null", "" ), "name:luiss" ) + model.validate( + result, + mock, + "testField", + javacast( "null", "" ), + { "name" : "luiss" } + ) ).toBeFalse(); expect( - model.validate( result, mock, "testField", javaCast( "null", "" ), "name:luis" ) + model.validate( + result, + mock, + "testField", + javacast( "null", "" ), + { "name" : "luis" } + ) ).toBeTrue(); - } ); + expect( + model.validate( + result, + mock, + "testField", + javacast( "null", "" ), + { missing : javacast( "null", "" ) } + ) + ).toBeTrue(); - }); + expect( + model.validate( + result, + mock, + "testField", + "not null", + { missing : javacast( "null", "" ) } + ) + ).toBeTrue(); + } ); + } ); } - } \ No newline at end of file +} diff --git a/test-harness/tests/specs/validators/UDFValidatorTest.cfc b/test-harness/tests/specs/validators/UDFValidatorTest.cfc index 361d17f..5aaf303 100644 --- a/test-harness/tests/specs/validators/UDFValidatorTest.cfc +++ b/test-harness/tests/specs/validators/UDFValidatorTest.cfc @@ -33,6 +33,16 @@ component extends="coldbox.system.testing.BaseModelTest" model="cbvalidation.mod variables.validate2 ); assertEquals( true, r ); + + // null + r = model.validate( + result, + this, + "test", + javacast( "null", "" ), + variables.validate3 + ); + assertEquals( false, r ); } private function validate( value, target ){ @@ -43,4 +53,8 @@ component extends="coldbox.system.testing.BaseModelTest" model="cbvalidation.mod return arguments.value gt 4; } + private function validate3( value, target ){ + return false; + } + }