build-test-data icon indicating copy to clipboard operation
build-test-data copied to clipboard

ValidateableDataBuilder#satisfyConstrained and unique vs. nullable vs. blank

Open zyro23 opened this issue 7 years ago • 1 comments

given a domain

class Foo {
	String name
	static constraints = {
		name blank: false, unique: true
	}
}

and a spec

@Build(Foo)
class FooSpec extends Specification implements DomainUnitTest<Foo>, BuildDataTest {
	def "test blank 'x' vs. unique constraint"() {
		when:
		Foo.build()
		Foo.build()
		Foo.build()
		currentSession.flush()

		then:
		def e = thrown(ValidationException)
		e.errors.getFieldError("name").rejectedValue == "name"
	}
}

fails with

Condition not satisfied:

e.errors.getFieldError("name").rejectedValue == "name"
| |      |                     |             |
| |      |                     x             false
| |      |                                   4 differences (0% similarity)
| |      |                                   (x---)
| |      |                                   (name)
| |      Field error in object 'myapp.Foo' on field 'name': rejected value [x]; codes [myapp.Foo.name.unique.error.myapp.Foo.name,myapp.Foo.name.unique.error.name,myapp.Foo.name.unique.error.java.lang.String,myapp.Foo.name.unique.error,foo.name.unique.error.myapp.Foo.name,foo.name.unique.error.name,foo.name.unique.error.java.lang.String,foo.name.unique.error,myapp.Foo.name.unique.myapp.Foo.name,myapp.Foo.name.unique.name,myapp.Foo.name.unique.java.lang.String,myapp.Foo.name.unique,foo.name.unique.myapp.Foo.name,foo.name.unique.name,foo.name.unique.java.lang.String,foo.name.unique,unique.myapp.Foo.name,unique.name,unique.java.lang.String,unique]; arguments [name,class myapp.Foo,x]; default message [null]
| org.grails.datastore.mapping.validation.ValidationErrors: 1 errors
| Field error in object 'myapp.Foo' on field 'name': rejected value [x]; codes [myapp.Foo.name.unique.error.myapp.Foo.name,myapp.Foo.name.unique.error.name,myapp.Foo.name.unique.error.java.lang.String,myapp.Foo.name.unique.error,foo.name.unique.error.myapp.Foo.name,foo.name.unique.error.name,foo.name.unique.error.java.lang.String,foo.name.unique.error,myapp.Foo.name.unique.myapp.Foo.name,myapp.Foo.name.unique.name,myapp.Foo.name.unique.java.lang.String,myapp.Foo.name.unique,foo.name.unique.myapp.Foo.name,foo.name.unique.name,foo.name.unique.java.lang.String,foo.name.unique,unique.myapp.Foo.name,unique.name,unique.java.lang.String,unique]; arguments [name,class myapp.Foo,x]; default message [null]
grails.validation.ValidationException: Validation error occurred during call to save():
- Field error in object 'myapp.Foo' on field 'name': rejected value [x]; codes [myapp.Foo.name.unique.error.myapp.Foo.name,myapp.Foo.name.unique.error.name,myapp.Foo.name.unique.error.java.lang.String,myapp.Foo.name.unique.error,foo.name.unique.error.myapp.Foo.name,foo.name.unique.error.name,foo.name.unique.error.java.lang.String,foo.name.unique.error,myapp.Foo.name.unique.myapp.Foo.name,myapp.Foo.name.unique.name,myapp.Foo.name.unique.java.lang.String,myapp.Foo.name.unique,foo.name.unique.myapp.Foo.name,foo.name.unique.name,foo.name.unique.java.lang.String,foo.name.unique,unique.myapp.Foo.name,unique.name,unique.java.lang.String,unique]; arguments [name,class myapp.Foo,x]; default message [null]

Expected :name

Actual   :x

log:

2018-05-29 08:16:11.773 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.nullable constraint, field before adjustment: null
2018-05-29 08:16:11.775 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.name field after adjustment for nullable: name
2018-05-29 08:16:11.982 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.nullable constraint, field before adjustment: null
2018-05-29 08:16:11.982 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.name field after adjustment for nullable: name
2018-05-29 08:16:11.992 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.blank constraint, field before adjustment: name
2018-05-29 08:16:11.992 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.name field after adjustment for blank: x
2018-05-29 08:16:11.993 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.nullable constraint, field before adjustment: null
2018-05-29 08:16:11.993 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.name field after adjustment for nullable: name
2018-05-29 08:16:11.995 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.blank constraint, field before adjustment: name
2018-05-29 08:16:11.995 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.name field after adjustment for blank: x
2018-05-29 08:16:11.995 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.unique constraint, field before adjustment: x
2018-05-29 08:16:11.995  WARN --- [           main] g.b.builders.ValidateableDataBuilder     : Unable to find property generator handler for constraint unique!
  • first Foo.build()
    • name: "name" - unique - constraints satisfied
  • second Foo.build()
    • name: "x" - unique - constraints satisfied
  • third Foo.build()
    • name: "x" - not unique - constraints not satisfied

so when the unique constraint is not satisfied, the BlankConstraintHandler gets applied, that was unexpected.

as we have to take care of proper unique value generation anyway this is not blocking. just that the second build() passed and then the third fails with a value of x instead with the unique propertyName name is confusing.

referencing sample app in a minute.

zyro23 avatar May 29 '18 06:05 zyro23

@zyro23 Thanks for the sample app. I'll take a look.

longwa avatar Jun 05 '18 17:06 longwa