Add method validation to Spring MVC
See gh-29825
This commit is contained in:
committed by
rstoyanchev
parent
cb04c3b335
commit
bd054a4918
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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
|
||||
*
|
||||
* https://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.web.method;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link HandlerMethod}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class HandlerMethodTests {
|
||||
|
||||
@Test
|
||||
void shouldValidateArgsWithConstraintsDirectlyOnClass() {
|
||||
Object target = new MyClass();
|
||||
testShouldValidateArguments(target, List.of("addIntValue", "addPersonAndIntValue"), true);
|
||||
testShouldValidateArguments(target, List.of("addPerson", "getPerson", "getIntValue", "addPersonNotValidated"), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldValidateArgsWithConstraintsOnInterface() {
|
||||
Object target = new MyInterfaceImpl();
|
||||
testShouldValidateArguments(target, List.of("addIntValue", "addPersonAndIntValue"), true);
|
||||
testShouldValidateArguments(target, List.of("addPerson", "addPersonNotValidated", "getPerson", "getIntValue"), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldValidateReturnValueWithConstraintsDirectlyOnClass() {
|
||||
Object target = new MyClass();
|
||||
testShouldValidateReturnValue(target, List.of("getPerson", "getIntValue"), true);
|
||||
testShouldValidateReturnValue(target, List.of("addPerson", "addIntValue", "addPersonNotValidated"), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldValidateReturnValueWithConstraintsOnInterface() {
|
||||
Object target = new MyInterfaceImpl();
|
||||
testShouldValidateReturnValue(target, List.of("getPerson", "getIntValue"), true);
|
||||
testShouldValidateReturnValue(target, List.of("addPerson", "addIntValue", "addPersonNotValidated"), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classLevelValidatedAnnotation() {
|
||||
Object target = new MyValidatedClass();
|
||||
testShouldValidateArguments(target, List.of("addPerson"), false);
|
||||
testShouldValidateReturnValue(target, List.of("getPerson"), false);
|
||||
}
|
||||
|
||||
private static void testShouldValidateArguments(Object target, List<String> methodNames, boolean expected) {
|
||||
for (String methodName : methodNames) {
|
||||
assertThat(getHandlerMethod(target, methodName).shouldValidateArguments()).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
private static void testShouldValidateReturnValue(Object target, List<String> methodNames, boolean expected) {
|
||||
for (String methodName : methodNames) {
|
||||
assertThat(getHandlerMethod(target, methodName).shouldValidateReturnValue()).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
private static HandlerMethod getHandlerMethod(Object target, String methodName) {
|
||||
Method method = ClassUtils.getMethod(target.getClass(), methodName, (Class<?>[]) null);
|
||||
return new HandlerMethod(target, method);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private record Person(@Size(min = 1, max = 10) String name) {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class MyClass {
|
||||
|
||||
public void addPerson(@Valid Person person) {
|
||||
}
|
||||
|
||||
public void addIntValue(@Max(10) int value) {
|
||||
}
|
||||
|
||||
public void addPersonAndIntValue(@Valid Person person, @Max(10) int value) {
|
||||
}
|
||||
|
||||
public void addPersonNotValidated(Person person) {
|
||||
}
|
||||
|
||||
@Valid
|
||||
public Person getPerson() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Max(10)
|
||||
public int getIntValue() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private interface MyInterface {
|
||||
|
||||
void addPerson(@Valid Person person);
|
||||
|
||||
void addIntValue(@Max(10) int value);
|
||||
|
||||
void addPersonAndIntValue(@Valid Person person, @Max(10) int value);
|
||||
|
||||
void addPersonNotValidated(Person person);
|
||||
|
||||
@Valid
|
||||
Person getPerson();
|
||||
|
||||
@Max(10)
|
||||
int getIntValue();
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class MyInterfaceImpl implements MyInterface {
|
||||
|
||||
@Override
|
||||
public void addPerson(Person person) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIntValue(int value) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPersonAndIntValue(Person person, int value) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPersonNotValidated(Person person) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Person getPerson() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntValue() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Validated
|
||||
private static class MyValidatedClass {
|
||||
|
||||
public void addPerson(@Valid Person person) {
|
||||
}
|
||||
|
||||
@Valid
|
||||
public Person getPerson() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -162,10 +162,10 @@ public class ModelAttributeMethodProcessorTests {
|
||||
public void resolveArgumentViaDefaultConstructor() throws Exception {
|
||||
WebDataBinder dataBinder = new WebRequestDataBinder(null);
|
||||
WebDataBinderFactory factory = mock();
|
||||
given(factory.createBinder(any(), notNull(), eq("attrName"))).willReturn(dataBinder);
|
||||
given(factory.createBinder(any(), notNull(), eq("attrName"), any())).willReturn(dataBinder);
|
||||
|
||||
this.processor.resolveArgument(this.paramNamedValidModelAttr, this.container, this.request, factory);
|
||||
verify(factory).createBinder(any(), notNull(), eq("attrName"));
|
||||
verify(factory).createBinder(any(), notNull(), eq("attrName"), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -176,7 +176,7 @@ public class ModelAttributeMethodProcessorTests {
|
||||
|
||||
StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
|
||||
WebDataBinderFactory factory = mock();
|
||||
given(factory.createBinder(this.request, target, name)).willReturn(dataBinder);
|
||||
given(factory.createBinder(this.request, target, name, this.paramNamedValidModelAttr)).willReturn(dataBinder);
|
||||
|
||||
this.processor.resolveArgument(this.paramNamedValidModelAttr, this.container, this.request, factory);
|
||||
|
||||
@@ -195,7 +195,7 @@ public class ModelAttributeMethodProcessorTests {
|
||||
|
||||
StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
|
||||
WebDataBinderFactory factory = mock();
|
||||
given(factory.createBinder(this.request, target, name)).willReturn(dataBinder);
|
||||
given(factory.createBinder(this.request, target, name, this.paramNamedValidModelAttr)).willReturn(dataBinder);
|
||||
|
||||
this.processor.resolveArgument(this.paramNamedValidModelAttr, this.container, this.request, factory);
|
||||
|
||||
@@ -211,7 +211,7 @@ public class ModelAttributeMethodProcessorTests {
|
||||
|
||||
StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
|
||||
WebDataBinderFactory factory = mock();
|
||||
given(factory.createBinder(this.request, target, name)).willReturn(dataBinder);
|
||||
given(factory.createBinder(this.request, target, name, this.paramBindingDisabledAttr)).willReturn(dataBinder);
|
||||
|
||||
this.processor.resolveArgument(this.paramBindingDisabledAttr, this.container, this.request, factory);
|
||||
|
||||
@@ -229,12 +229,12 @@ public class ModelAttributeMethodProcessorTests {
|
||||
dataBinder.getBindingResult().reject("error");
|
||||
|
||||
WebDataBinderFactory binderFactory = mock();
|
||||
given(binderFactory.createBinder(this.request, target, name)).willReturn(dataBinder);
|
||||
given(binderFactory.createBinder(this.request, target, name, this.paramNonSimpleType)).willReturn(dataBinder);
|
||||
|
||||
assertThatExceptionOfType(MethodArgumentNotValidException.class).isThrownBy(() ->
|
||||
this.processor.resolveArgument(this.paramNonSimpleType, this.container, this.request, binderFactory));
|
||||
|
||||
verify(binderFactory).createBinder(this.request, target, name);
|
||||
verify(binderFactory).createBinder(this.request, target, name, this.paramNonSimpleType);
|
||||
}
|
||||
|
||||
@Test // SPR-9378
|
||||
@@ -249,7 +249,7 @@ public class ModelAttributeMethodProcessorTests {
|
||||
|
||||
StubRequestDataBinder dataBinder = new StubRequestDataBinder(testBean, name);
|
||||
WebDataBinderFactory binderFactory = mock();
|
||||
given(binderFactory.createBinder(this.request, testBean, name)).willReturn(dataBinder);
|
||||
given(binderFactory.createBinder(this.request, testBean, name, this.paramModelAttr)).willReturn(dataBinder);
|
||||
|
||||
this.processor.resolveArgument(this.paramModelAttr, this.container, this.request, binderFactory);
|
||||
|
||||
@@ -278,7 +278,7 @@ public class ModelAttributeMethodProcessorTests {
|
||||
ServletWebRequest requestWithParam = new ServletWebRequest(mockRequest);
|
||||
|
||||
WebDataBinderFactory factory = mock();
|
||||
given(factory.createBinder(any(), any(), eq("testBeanWithConstructorArgs")))
|
||||
given(factory.createBinder(any(), any(), eq("testBeanWithConstructorArgs"), any()))
|
||||
.willAnswer(invocation -> {
|
||||
WebRequestDataBinder binder = new WebRequestDataBinder(invocation.getArgument(1));
|
||||
// Add conversion service which will convert "1,2" to a list
|
||||
@@ -297,10 +297,10 @@ public class ModelAttributeMethodProcessorTests {
|
||||
|
||||
WebDataBinder dataBinder = new WebRequestDataBinder(target);
|
||||
WebDataBinderFactory factory = mock();
|
||||
given(factory.createBinder(this.request, target, expectedAttrName)).willReturn(dataBinder);
|
||||
given(factory.createBinder(this.request, target, expectedAttrName, param)).willReturn(dataBinder);
|
||||
|
||||
this.processor.resolveArgument(param, this.container, this.request, factory);
|
||||
verify(factory).createBinder(this.request, target, expectedAttrName);
|
||||
verify(factory).createBinder(this.request, target, expectedAttrName, param);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user