1package org.codehaus.groovy.grails.orm.hibernate
2
3import org.codehaus.groovy.grails.commons.*
4import grails.validation.ValidationException
5import org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor
6
7class SavePersistentMethodTests extends AbstractGrailsHibernateTests {
8
9    void testFlush() {
10        def bookClass = ga.getDomainClass("grails.tests.SaveBook")
11        def authorClass = ga.getDomainClass("grails.tests.SaveAuthor")
12        def addressClass = ga.getDomainClass("grails.tests.SaveAddress")
13
14        def book = bookClass.newInstance()
15        book.title = "Foo"
16        def author = authorClass.newInstance()
17        book.author = author
18        author.name = "Bar"
19        def address = addressClass.newInstance()
20        author.address = address
21        address.location = "Foo Bar"
22        assertNotNull author.save()
23
24        assertNotNull book.save(flush:true)
25        assertNotNull book.id
26    }
27
28    void testToOneCascadingValidation() {
29        def bookClass = ga.getDomainClass("grails.tests.SaveBook")
30        def authorClass = ga.getDomainClass("grails.tests.SaveAuthor")
31        def addressClass = ga.getDomainClass("grails.tests.SaveAddress")
32
33        def book = bookClass.newInstance()
34
35        assertNull book.save()
36        assertNull book.save(deepValidate:false)
37
38        book.title = "Foo"
39
40        assertNotNull book.save()
41
42        def author = authorClass.newInstance()
43        author.name = "Bar"
44        author.save()
45
46        book.author = author
47
48        // will validate book is owned by author
49        assertNotNull book.save()
50        assertNotNull book.save(deepValidate:false)
51
52        def address = addressClass.newInstance()
53
54        author.address = address
55
56        assertNull author.save()
57
58        address.location = "Foo Bar"
59        assertNotNull author.save()
60        assertNotNull author.save(deepValidate:false)
61    }
62
63    void testToManyCascadingValidation() {
64        def bookClass = ga.getDomainClass("grails.tests.SaveBook")
65        def authorClass = ga.getDomainClass("grails.tests.SaveAuthor")
66        def addressClass = ga.getDomainClass("grails.tests.SaveAddress")
67
68        def author = authorClass.newInstance()
69
70        assertNull author.save()
71        author.name = "Foo"
72
73        assertNotNull author.save()
74
75        def address = addressClass.newInstance()
76        author.address = address
77
78        assertNull author.save()
79
80        address.location = "Foo Bar"
81        assertNotNull author.save()
82
83        def book = bookClass.newInstance()
84
85        author.addToBooks(book)
86        assertNull author.save()
87
88        book.title = "TDGTG"
89        assertNotNull author.save()
90        assertNotNull author.save(deepValidate:false)
91    }
92
93    void testValidationAfterBindingErrors() {
94        def teamClass = ga.getDomainClass('grails.tests.Team')
95        def team = teamClass.newInstance()
96        team.properties = [homePage: 'invalidurl']
97        assertNull 'validation should have failed', team.save()
98        assertEquals 'wrong number of errors found', 2, team.errors.errorCount
99        assertEquals 'wrong number of homePage errors found', 1, team.errors.getFieldErrors('homePage')?.size()
100        def homePageError = team.errors.getFieldError('homePage')
101        assertTrue 'did not find typeMismatch error', 'typeMismatch' in homePageError.codes
102
103        team.homePage = new URL('http://grails.org')
104        assertNull 'validation should have failed', team.save()
105        assertEquals 'wrong number of errors found', 1, team.errors.errorCount
106        assertEquals 'wrong number of homePage errors found', 0, team.errors.getFieldErrors('homePage')?.size()
107    }
108
109    void testFailOnErrorTrueWithValidationErrors() {
110        def teamClass = ga.getDomainClass('grails.tests.Team')
111        def team = teamClass.newInstance()
112        team.properties = [homePage: 'invalidurl']
113        def msg = shouldFail(ValidationException) {
114            team.save(failOnError: true)
115        }
116
117        // test errors object
118        try {
119            team.save(failOnError:true)
120        }
121        catch (ValidationException e) {
122            assertNotNull "should have a reference to the errors object", e.errors
123        }
124        assertEquals '''\
125Validation Error(s) occurred during save():
126- Field error in object 'grails.tests.Team' on field 'homePage': rejected value [null]; codes [typeMismatch.grails.tests.Team.homePage,typeMismatch.homePage,typeMismatch.java.net.URL,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [grails.tests.Team.homePage,homePage]; arguments []; default message [homePage]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.net.URL' for property 'homePage'; nested exception is java.lang.IllegalArgumentException: Could not retrieve URL for class path resource [invalidurl]: class path resource [invalidurl] cannot be resolved to URL because it does not exist]
127- Field error in object 'grails.tests.Team' on field 'name': rejected value [null]; codes [grails.tests.Team.name.nullable.error.grails.tests.Team.name,grails.tests.Team.name.nullable.error.name,grails.tests.Team.name.nullable.error.java.lang.String,grails.tests.Team.name.nullable.error,team.name.nullable.error.grails.tests.Team.name,team.name.nullable.error.name,team.name.nullable.error.java.lang.String,team.name.nullable.error,grails.tests.Team.name.nullable.grails.tests.Team.name,grails.tests.Team.name.nullable.name,grails.tests.Team.name.nullable.java.lang.String,grails.tests.Team.name.nullable,team.name.nullable.grails.tests.Team.name,team.name.nullable.name,team.name.nullable.java.lang.String,team.name.nullable,nullable.grails.tests.Team.name,nullable.name,nullable.java.lang.String,nullable]; arguments [name,class grails.tests.Team]; default message [Property [{0}] of class [{1}] cannot be null]
128''', msg
129    }
130
131    void testFailOnErrorFalseWithValidationErrors() {
132        def teamClass = ga.getDomainClass('grails.tests.Team')
133        def team = teamClass.newInstance()
134        team.properties = [homePage: 'invalidurl']
135        assertNull 'save should have returned null', team.save(failOnError: false)
136    }
137
138    void testFailOnErrorConfigTrueWithValidationErrors() {
139        def config = new ConfigSlurper().parse("grails.gorm.failOnError = true")
140
141        ConfigurationHolder.config = config
142        def teamClass = ga.getDomainClass('grails.tests.Team')
143        def team = teamClass.newInstance()
144        team.properties = [homePage: 'invalidurl']
145        def msg = shouldFail(ValidationException) {
146            team.save()
147        }
148        assertEquals '''\
149Validation Error(s) occurred during save():
150- Field error in object 'grails.tests.Team' on field 'homePage': rejected value [null]; codes [typeMismatch.grails.tests.Team.homePage,typeMismatch.homePage,typeMismatch.java.net.URL,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [grails.tests.Team.homePage,homePage]; arguments []; default message [homePage]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.net.URL' for property 'homePage'; nested exception is java.lang.IllegalArgumentException: Could not retrieve URL for class path resource [invalidurl]: class path resource [invalidurl] cannot be resolved to URL because it does not exist]
151- Field error in object 'grails.tests.Team' on field 'name': rejected value [null]; codes [grails.tests.Team.name.nullable.error.grails.tests.Team.name,grails.tests.Team.name.nullable.error.name,grails.tests.Team.name.nullable.error.java.lang.String,grails.tests.Team.name.nullable.error,team.name.nullable.error.grails.tests.Team.name,team.name.nullable.error.name,team.name.nullable.error.java.lang.String,team.name.nullable.error,grails.tests.Team.name.nullable.grails.tests.Team.name,grails.tests.Team.name.nullable.name,grails.tests.Team.name.nullable.java.lang.String,grails.tests.Team.name.nullable,team.name.nullable.grails.tests.Team.name,team.name.nullable.name,team.name.nullable.java.lang.String,team.name.nullable,nullable.grails.tests.Team.name,nullable.name,nullable.java.lang.String,nullable]; arguments [name,class grails.tests.Team]; default message [Property [{0}] of class [{1}] cannot be null]
152''', msg
153    }
154
155    void testFailOnErrorConfigTrueWithValidationErrorsAndAutoFlush() {
156        def interceptor = appCtx.getBean("eventTriggeringInterceptor")
157        interceptor.failOnError=true
158        def teamClass = ga.getDomainClass('grails.tests.Team').clazz
159        def team = teamClass.newInstance(name:"Manchester United", homePage:new URL("http://www.manutd.com/"))
160        assertNotNull "should have saved", team.save(flush:true)
161
162        session.clear()
163
164        team = teamClass.get(1)
165        team.name = ''
166        shouldFail(ValidationException) {
167            session.flush()
168        }
169    }
170
171    void testFailOnErrorConfigWithPackagesAndAutoFlush() {
172        ClosureEventTriggeringInterceptor interceptor = appCtx.getBean("eventTriggeringInterceptor")
173        interceptor.failOnError=true
174        interceptor.failOnErrorPackages = ['foo.bar']
175        def teamClass = ga.getDomainClass('grails.tests.Team').clazz
176        def team = teamClass.newInstance(name:"Manchester United", homePage:new URL("http://www.manutd.com/"))
177        assertNotNull "should have saved", team.save(flush:true)
178
179        session.clear()
180
181        team = teamClass.get(1)
182        team.name = ''
183        // should not throw exception for the grails.test package
184        session.flush()
185
186        interceptor.failOnErrorPackages = ['grails.tests']
187		interceptor.eventListeners.clear()
188
189        session.clear()
190        team = teamClass.get(1)
191        team.name = ''
192        // should not throw exception for the grails.test package
193        shouldFail(ValidationException) {
194            session.flush()
195        }
196    }
197
198    void testFailOnErrorConfigIncludesMatchingPackageWithValidationErrors() {
199        def config = new ConfigSlurper().parse("grails.gorm.failOnError = ['com.foo', 'grails.tests', 'com.bar']")
200
201        ConfigurationHolder.config = config
202        def teamClass = ga.getDomainClass('grails.tests.Team')
203        def team = teamClass.newInstance()
204        team.properties = [homePage: 'invalidurl']
205        def msg = shouldFail(ValidationException) {
206            team.save()
207        }
208        assertEquals '''\
209Validation Error(s) occurred during save():
210- Field error in object 'grails.tests.Team' on field 'homePage': rejected value [null]; codes [typeMismatch.grails.tests.Team.homePage,typeMismatch.homePage,typeMismatch.java.net.URL,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [grails.tests.Team.homePage,homePage]; arguments []; default message [homePage]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.net.URL' for property 'homePage'; nested exception is java.lang.IllegalArgumentException: Could not retrieve URL for class path resource [invalidurl]: class path resource [invalidurl] cannot be resolved to URL because it does not exist]
211- Field error in object 'grails.tests.Team' on field 'name': rejected value [null]; codes [grails.tests.Team.name.nullable.error.grails.tests.Team.name,grails.tests.Team.name.nullable.error.name,grails.tests.Team.name.nullable.error.java.lang.String,grails.tests.Team.name.nullable.error,team.name.nullable.error.grails.tests.Team.name,team.name.nullable.error.name,team.name.nullable.error.java.lang.String,team.name.nullable.error,grails.tests.Team.name.nullable.grails.tests.Team.name,grails.tests.Team.name.nullable.name,grails.tests.Team.name.nullable.java.lang.String,grails.tests.Team.name.nullable,team.name.nullable.grails.tests.Team.name,team.name.nullable.name,team.name.nullable.java.lang.String,team.name.nullable,nullable.grails.tests.Team.name,nullable.name,nullable.java.lang.String,nullable]; arguments [name,class grails.tests.Team]; default message [Property [{0}] of class [{1}] cannot be null]
212''', msg
213    }
214
215    void testFailOnErrorConfigDoesNotIncludeMatchingPackageWithValidationErrors() {
216        def config = new ConfigSlurper().parse("grails.gorm.failOnError = ['com.foo', 'com.bar']")
217
218        ConfigurationHolder.config = config
219        def teamClass = ga.getDomainClass('grails.tests.Team')
220        def team = teamClass.newInstance()
221        team.properties = [homePage: 'invalidurl']
222        assertNull 'save should have returned null', team.save()
223    }
224
225    void testFailOnErrorConfigFalseWithValidationErrors() {
226        def config = new ConfigSlurper().parse("grails.gorm.failOnError = false")
227
228        ConfigurationHolder.config = config
229        def teamClass = ga.getDomainClass('grails.tests.Team')
230        def team = teamClass.newInstance()
231        team.properties = [homePage: 'invalidurl']
232        assertNull 'save should have returned null', team.save()
233    }
234
235    void testFailOnErrorConfigTrueArgumentFalseWithValidationErrors() {
236        def config = new ConfigSlurper().parse("grails.gorm.failOnError = true")
237
238        ConfigurationHolder.config = config
239        def teamClass = ga.getDomainClass('grails.tests.Team')
240        def team = teamClass.newInstance()
241        team.properties = [homePage: 'invalidurl']
242        assertNull 'save should have returned null', team.save(failOnError: false)
243    }
244
245    void testFailOnErrorConfigFalseArgumentTrueWithValidationErrors() {
246        def config = new ConfigSlurper().parse("grails.gorm.failOnError = false")
247
248        ConfigurationHolder.config = config
249        def teamClass = ga.getDomainClass('grails.tests.Team')
250        def team = teamClass.newInstance()
251        team.properties = [homePage: 'invalidurl']
252        def msg = shouldFail(ValidationException) {
253            team.save(failOnError: true)
254        }
255        assertEquals '''\
256Validation Error(s) occurred during save():
257- Field error in object 'grails.tests.Team' on field 'homePage': rejected value [null]; codes [typeMismatch.grails.tests.Team.homePage,typeMismatch.homePage,typeMismatch.java.net.URL,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [grails.tests.Team.homePage,homePage]; arguments []; default message [homePage]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.net.URL' for property 'homePage'; nested exception is java.lang.IllegalArgumentException: Could not retrieve URL for class path resource [invalidurl]: class path resource [invalidurl] cannot be resolved to URL because it does not exist]
258- Field error in object 'grails.tests.Team' on field 'name': rejected value [null]; codes [grails.tests.Team.name.nullable.error.grails.tests.Team.name,grails.tests.Team.name.nullable.error.name,grails.tests.Team.name.nullable.error.java.lang.String,grails.tests.Team.name.nullable.error,team.name.nullable.error.grails.tests.Team.name,team.name.nullable.error.name,team.name.nullable.error.java.lang.String,team.name.nullable.error,grails.tests.Team.name.nullable.grails.tests.Team.name,grails.tests.Team.name.nullable.name,grails.tests.Team.name.nullable.java.lang.String,grails.tests.Team.name.nullable,team.name.nullable.grails.tests.Team.name,team.name.nullable.name,team.name.nullable.java.lang.String,team.name.nullable,nullable.grails.tests.Team.name,nullable.name,nullable.java.lang.String,nullable]; arguments [name,class grails.tests.Team]; default message [Property [{0}] of class [{1}] cannot be null]
259''', msg
260    }
261
262    void testSaveWithoutValidation() {
263        def dcClass = ga.getDomainClass('grails.tests.SaveCustomValidation')
264        def dc = dcClass.newInstance()
265        dc.properties = [ title: 'Test' ]
266
267        // The custom validator for SaveCustomValidation throws an
268        // exception if it's triggered, but that shouldn't happen
269        // if we explicitly disable validation.
270        dc.save(validate: false)
271        session.flush()
272
273        // Once its attached to the session, dirty checking applies.
274        // Here we test that the validation doesn't occur even though
275        // the domain instance has been modified.
276        dc.title = "A different title"
277        dc.save(validate: false)
278        session.flush()
279
280        // Let's check that the validation kicks in if we don't disable it...
281        dc.title = "Another title"
282        shouldFail(IllegalStateException) {
283            dc.save()
284        }
285
286        // ...and make sure that this also happens with dirty-checking.
287        dc.title = "Dirty check"
288        shouldFail(IllegalStateException) {
289            session.flush()
290        }
291    }
292
293    void onSetUp() {
294        gcl.parseClass '''
295package grails.tests
296
297import grails.persistence.*
298
299@Entity
300class Team {
301    String name
302    URL homePage
303
304    static constraints = {
305        name blank:false
306    }
307}
308
309@Entity
310class SaveBook {
311    String title
312    SaveAuthor author
313    static belongsTo = SaveAuthor
314    static constraints = {
315       title(blank:false, size:1..255)
316       author(nullable:true)
317    }
318}
319
320@Entity
321class SaveAuthor {
322   String name
323   SaveAddress address
324   static hasMany = [books:SaveBook]
325   static constraints = {
326        address(nullable:true)
327        name(size:1..255, blank:false)
328   }
329}
330
331@Entity
332class SaveAddress {
333    SaveAuthor author
334    String location
335    static belongsTo = SaveAuthor
336    static constraints = {
337       author(nullable:true)
338       location(blank:false)
339    }
340}
341
342@Entity
343class SaveCustomValidation {
344    String title
345
346    static constraints = {
347        title(validator: { val, obj -> throw new IllegalStateException() })
348    }
349}
350'''
351    }
352
353    void onTearDown() {
354        ConfigurationHolder.config = null
355    }
356}
357