/* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.context.properties.bind.validation; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.validation.Valid; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.MockConfigurationPropertySource; import org.springframework.boot.origin.Origin; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; import org.springframework.validation.annotation.Validated; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.instanceOf; /** * Tests for {@link ValidationBindHandler}. * * @author Phillip Webb * @author Madhura Bhave */ public class ValidationBindHandlerTests { @Rule public ExpectedException thrown = ExpectedException.none(); private List<ConfigurationPropertySource> sources = new ArrayList<>(); private ValidationBindHandler handler; private Binder binder; @Before public void setup() { this.binder = new Binder(this.sources); LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); validator.afterPropertiesSet(); this.handler = new ValidationBindHandler(validator); } @Test public void bindShouldBindWithoutHandler() { this.sources.add(new MockConfigurationPropertySource("foo.age", 4)); ExampleValidatedBean bean = this.binder .bind("foo", Bindable.of(ExampleValidatedBean.class)).get(); assertThat(bean.getAge()).isEqualTo(4); } @Test public void bindShouldFailWithHandler() { this.sources.add(new MockConfigurationPropertySource("foo.age", 4)); this.thrown.expect(BindException.class); this.thrown.expectCause(instanceOf(BindValidationException.class)); this.binder.bind("foo", Bindable.of(ExampleValidatedBean.class), this.handler); } @Test public void bindShouldValidateNestedProperties() throws Exception { this.sources.add(new MockConfigurationPropertySource("foo.nested.age", 4)); this.thrown.expect(BindException.class); this.thrown.expectCause(instanceOf(BindValidationException.class)); this.binder.bind("foo", Bindable.of(ExampleValidatedWithNestedBean.class), this.handler); } @Test public void bindShouldFailWithAccessToOrigin() { this.sources.add(new MockConfigurationPropertySource("foo.age", 4, "file")); BindValidationException cause = bindAndExpectValidationError( () -> this.binder.bind(ConfigurationPropertyName.of("foo"), Bindable.of(ExampleValidatedBean.class), this.handler)); ObjectError objectError = cause.getValidationErrors().getAllErrors().get(0); assertThat(Origin.from(objectError).toString()).isEqualTo("file"); } @Test public void bindShouldFailWithAccessToBoundProperties() throws Exception { MockConfigurationPropertySource source = new MockConfigurationPropertySource(); source.put("foo.nested.name", "baz"); source.put("foo.nested.age", "4"); source.put("faf.bar", "baz"); this.sources.add(source); BindValidationException cause = bindAndExpectValidationError( () -> this.binder.bind(ConfigurationPropertyName.of("foo"), Bindable.of(ExampleValidatedWithNestedBean.class), this.handler)); Set<ConfigurationProperty> boundProperties = cause.getValidationErrors() .getBoundProperties(); assertThat(boundProperties).extracting((p) -> p.getName().toString()) .contains("foo.nested.age", "foo.nested.name"); } @Test public void bindShouldFailWithAccessToName() throws Exception { this.sources.add(new MockConfigurationPropertySource("foo.nested.age", "4")); BindValidationException cause = bindAndExpectValidationError( () -> this.binder.bind(ConfigurationPropertyName.of("foo"), Bindable.of(ExampleValidatedWithNestedBean.class), this.handler)); assertThat(cause.getValidationErrors().getName().toString()) .isEqualTo("foo.nested"); } @Test public void bindShouldFailIfExistingValueIsInvalid() throws Exception { ExampleValidatedBean existingValue = new ExampleValidatedBean(); BindValidationException cause = bindAndExpectValidationError( () -> this.binder.bind(ConfigurationPropertyName.of("foo"), Bindable .of(ExampleValidatedBean.class).withExistingValue(existingValue), this.handler)); FieldError fieldError = (FieldError) cause.getValidationErrors().getAllErrors() .get(0); assertThat(fieldError.getField()).isEqualTo("age"); } @Test public void bindShouldNotValidateWithoutAnnotation() throws Exception { ExampleNonValidatedBean existingValue = new ExampleNonValidatedBean(); this.binder.bind(ConfigurationPropertyName.of("foo"), Bindable .of(ExampleNonValidatedBean.class).withExistingValue(existingValue), this.handler); } private BindValidationException bindAndExpectValidationError(Runnable action) { try { action.run(); } catch (BindException ex) { ex.printStackTrace(); BindValidationException cause = (BindValidationException) ex.getCause(); return cause; } throw new IllegalStateException("Did not throw"); } public static class ExampleNonValidatedBean { @Min(5) private int age; public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } } @Validated public static class ExampleValidatedBean { @Min(5) private int age; public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } } @Validated public static class ExampleValidatedWithNestedBean { @Valid private ExampleNested nested = new ExampleNested(); public ExampleNested getNested() { return this.nested; } public void setNested(ExampleNested nested) { this.nested = nested; } } public static class ExampleNested { private String name; @Min(5) private int age; @NotNull private String address; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } public String getAddress() { return this.address; } public void setAddress(String address) { this.address = address; } } }