Skip to content

Commit

Permalink
HV-1939 Test constructor validation on inner classes declared in methods
Browse files Browse the repository at this point in the history
  • Loading branch information
yrodiere authored and gsmet committed Jun 16, 2023
1 parent 58466ed commit 8dab6f2
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.test.internal.engine.methodvalidation;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertThat;
import static org.hibernate.validator.testutil.ConstraintViolationAssert.pathWith;
import static org.hibernate.validator.testutil.ConstraintViolationAssert.violationOf;
import static org.hibernate.validator.testutils.ValidatorUtil.getValidator;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Set;

import org.hibernate.validator.test.internal.engine.methodvalidation.model.Customer;
import org.hibernate.validator.test.internal.engine.methodvalidation.service.CustomerRepositoryImpl;

import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Payload;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.executable.ExecutableValidator;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class MethodInnerClassConstructorValidationTest {

protected ExecutableValidator executableValidator;
private final CustomerRepositoryImplMetamodel metamodel = new CustomerRepositoryImplMetamodel();

@BeforeMethod
public void setUp() {
this.executableValidator = getValidator().forExecutables();
}

@Test
public void constructorParameterValidationYieldsConstraintViolation() throws Exception {
Set<? extends ConstraintViolation<?>> violations = executableValidator.validateConstructorParameters(
metamodel.clazz().getConstructor( CustomerRepositoryImplMetamodel.class, String.class ),
new Object[] { metamodel, null }
);

assertThat( violations ).containsOnlyViolations(
violationOf( NotNull.class )
.withMessage( "must not be null" )
.withInvalidValue( null )
.withRootBeanClass( metamodel.clazz() )
.withPropertyPath( pathWith()
.constructor( metamodel.clazz() )
.parameter( "id", 1 )
)
);
}

@Test
public void cascadedConstructorParameterValidationYieldsConstraintViolation() throws Exception {
Set<? extends ConstraintViolation<?>> violations = executableValidator.validateConstructorParameters(
metamodel.clazz().getConstructor( CustomerRepositoryImplMetamodel.class, Customer.class ),
new Object[] { metamodel, new Customer( null ) }
);

assertThat( violations ).containsOnlyViolations(
violationOf( NotNull.class )
.withMessage( "must not be null" )
.withInvalidValue( null )
.withRootBeanClass( metamodel.clazz() )
.withPropertyPath( pathWith()
.constructor( metamodel.clazz() )
.parameter( "customer", 1 )
.property( "name" )
)
);
}

@Test
public void constructorReturnValueValidationYieldsConstraintViolation() throws Exception {
Constructor<?> constructor = metamodel.clazz().getDeclaredConstructor( CustomerRepositoryImplMetamodel.class );
Object customerRepository = constructor.newInstance( metamodel );
Set<? extends ConstraintViolation<?>> violations = executableValidator.validateConstructorReturnValue(
constructor,
customerRepository
);

assertThat( violations ).containsOnlyViolations(
violationOf( ValidB2BRepository.class )
.withMessage( "{ValidB2BRepository.message}" )
.withInvalidValue( customerRepository )
.withRootBeanClass( metamodel.clazz() )
.withPropertyPath( pathWith()
.constructor( metamodel.clazz() )
.returnValue()
)
);
}

@Test
public void cascadedConstructorReturnValueValidationYieldsConstraintViolation() throws Exception {
Constructor<?> constructor = metamodel.clazz().getDeclaredConstructor(
CustomerRepositoryImplMetamodel.class, String.class );
Object customerRepository = constructor.newInstance( metamodel, null );
Set<? extends ConstraintViolation<?>> violations = executableValidator.validateConstructorReturnValue(
constructor,
customerRepository
);

assertThat( violations ).containsOnlyViolations(
violationOf( NotNull.class )
.withMessage( "must not be null" )
.withInvalidValue( null )
.withRootBeanClass( metamodel.clazz() )
.withPropertyPath( pathWith()
.constructor( metamodel.clazz() )
.returnValue()
.property( "customer" )
)
);
}

private static class CustomerRepositoryImplMetamodel {
public Class<?> clazz() {
class CustomerRepositoryImpl {
@NotNull
private final Customer customer = null;

@ValidB2BRepository
public CustomerRepositoryImpl() {
}

@Valid
public CustomerRepositoryImpl(@NotNull String id) {
}

public CustomerRepositoryImpl(@Valid Customer customer) {
}
}
return CustomerRepositoryImpl.class;
}

public Object newInstance()
throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
return clazz().getDeclaredConstructor( CustomerRepositoryImplMetamodel.class ).newInstance( this );
}
}

@Constraint(validatedBy = { ValidB2BRepositoryValidator.class })
@Target({ TYPE, CONSTRUCTOR })
@Retention(RUNTIME)
public @interface ValidB2BRepository {
String message() default "{ValidB2BRepository.message}";

Class<?>[] groups() default { };

Class<? extends Payload>[] payload() default { };
}

public static class ValidB2BRepositoryValidator
implements ConstraintValidator<ValidB2BRepository, Object> {
@Override
public boolean isValid(Object repository, ConstraintValidatorContext context) {
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
import java.util.List;
import java.util.Set;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.metadata.ConstraintDescriptor;
import jakarta.validation.metadata.ConstructorDescriptor;
import jakarta.validation.metadata.ParameterDescriptor;
import jakarta.validation.metadata.ReturnValueDescriptor;

import org.hibernate.validator.test.internal.metadata.Customer;
import org.hibernate.validator.test.internal.metadata.CustomerRepository;
import org.hibernate.validator.test.internal.metadata.CustomerRepositoryExt;
import org.hibernate.validator.test.internal.metadata.CustomerRepositoryExt.ValidB2BRepository;
import org.testng.annotations.Test;
Expand Down Expand Up @@ -81,4 +84,43 @@ public void testGetReturnValueDescriptor() {
ConstraintDescriptor<?> descriptor = constraintDescriptors.iterator().next();
assertThat( descriptor.getAnnotation().annotationType() ).isEqualTo( ValidB2BRepository.class );
}

@Test
public void testMethodInnerClassGetParameterDescriptors() {
class MethodInnerClass extends CustomerRepository {
public MethodInnerClass(@NotNull String foo, @Valid Customer customer) {
}
}

ConstructorDescriptor constructorDescriptor = getConstructorDescriptor(
MethodInnerClass.class,
ConstructorDescriptorTest.class,
String.class,
Customer.class
);

List<ParameterDescriptor> parameterDescriptors = constructorDescriptor.getParameterDescriptors();
assertThat( parameterDescriptors ).hasSize( 3 );

// This is the parameter representing the enclosing instance.
ParameterDescriptor parameterDescriptor0 = parameterDescriptors.get( 0 );
assertThat( parameterDescriptor0.getElementClass() ).isEqualTo( ConstructorDescriptorTest.class );
assertThat( parameterDescriptor0.getIndex() ).isEqualTo( 0 );
assertThat( parameterDescriptor0.hasConstraints() ).isFalse();
assertThat( parameterDescriptor0.isCascaded() ).isFalse();

ParameterDescriptor parameterDescriptor1 = parameterDescriptors.get( 1 );
assertThat( parameterDescriptor1.getElementClass() ).isEqualTo( String.class );
assertThat( parameterDescriptor1.getIndex() ).isEqualTo( 1 );
assertThat( parameterDescriptor1.getName() ).isEqualTo( "foo" );
assertThat( parameterDescriptor1.hasConstraints() ).isTrue();
assertThat( parameterDescriptor1.isCascaded() ).isFalse();

ParameterDescriptor parameterDescriptor2 = parameterDescriptors.get( 2 );
assertThat( parameterDescriptor2.getElementClass() ).isEqualTo( Customer.class );
assertThat( parameterDescriptor2.getIndex() ).isEqualTo( 2 );
assertThat( parameterDescriptor2.getName() ).isEqualTo( "customer" );
assertThat( parameterDescriptor2.hasConstraints() ).isFalse();
assertThat( parameterDescriptor2.isCascaded() ).isTrue();
}
}

0 comments on commit 8dab6f2

Please sign in to comment.