package com.nike.backstopper.apierror.contract.jsr303convention; import com.nike.internal.util.Pair; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import org.junit.Test; import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.validation.Constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.Payload; import javax.validation.constraints.AssertFalse; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import static com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase.extractMessageFromAnnotation; import static com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase.generateExclusionForAnnotatedElementAndAnnotationClass; import static com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase.getSubAnnotationListForAnnotationsOfClassType; import static com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase.getSubAnnotationListForElementsOfOwnerClass; import static com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase.getSubAnnotationListUsingExclusionFilters; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertTrue; /** * Verifies the logic in {@link ReflectionBasedJsr303AnnotationTrollerBase} to make sure that its reflection magic works the way we expect it to. * * @author Nic Munroe */ public class ReflectionMagicWorksTest { // This is static to make sure that it only gets created once in a JUnit context (rather than once per test method) because this is potentially time consuming. private static final ReflectionBasedJsr303AnnotationTrollerBase TROLLER = new ReflectionBasedJsr303AnnotationTrollerBase() { @Override protected List<Class<?>> ignoreAllAnnotationsAssociatedWithTheseProjectClasses() { return null; } @Override protected List<Predicate<Pair<Annotation, AnnotatedElement>>> specificAnnotationDeclarationExclusionsForProject() throws Exception { return null; } }; private static final List<Predicate<Pair<Annotation, AnnotatedElement>>> STRICT_MEMBER_CHECK_EXCLUSIONS = StrictMemberCheck.getStrictMemberCheckExclusionsForNonCompliantDeclarations(); // ============== THE FOLLOWING TESTS VERIFY THAT ReflectionBasedJsr303AnnotationTrollerBase's CRAZY REFLECTION MAGIC STUFF WORKS AS EXPECTED. ======================== /** * Makes sure that the Reflections helper stuff is working properly and capturing all annotation possibilities (class type, constructor, constructor param, method, method param, and field). */ @Test public void verifyThatTheReflectionsConfigurationIsCapturingAllAnnotationPossibilities() { List<Pair<Annotation, AnnotatedElement>> annotationOptionsClassAnnotations = getSubAnnotationListForElementsOfOwnerClass(TROLLER.allConstraintAnnotationsMasterList, DifferentValidationAnnotationOptions.class); assertThat(annotationOptionsClassAnnotations.size(), is(10)); assertThat(getSubAnnotationListForAnnotationsOfClassType(annotationOptionsClassAnnotations, SomeClassLevelJsr303Annotation.class).size(), is(2)); assertThat(getSubAnnotationListForAnnotationsOfClassType(annotationOptionsClassAnnotations, OtherClassLevelJsr303Annotation.class).size(), is(1)); assertThat(getSubAnnotationListForAnnotationsOfClassType(annotationOptionsClassAnnotations, AssertTrue.class).size(), is(1)); assertThat(getSubAnnotationListForAnnotationsOfClassType(annotationOptionsClassAnnotations, AssertFalse.class).size(), is(1)); assertThat(getSubAnnotationListForAnnotationsOfClassType(annotationOptionsClassAnnotations, NotNull.class).size(), is(2)); assertThat(getSubAnnotationListForAnnotationsOfClassType(annotationOptionsClassAnnotations, Min.class).size(), is(2)); assertThat(getSubAnnotationListForAnnotationsOfClassType(annotationOptionsClassAnnotations, Max.class).size(), is(1)); } /** * Verifies that {@link ReflectionBasedJsr303AnnotationTrollerBase#getSubAnnotationListUsingExclusionFilters(java.util.List, java.util.List, java.util.List)} helper method for this test class is working properly when passing * in annotatedElementOwnerClassesToExclude exclusion filter. */ @Test public void verifyThatExclusionFilterMethodIsExcludingSpecifiedClasses() { List<Pair<Annotation, AnnotatedElement>> annotationOptionsClassAnnotations = getSubAnnotationListForElementsOfOwnerClass(TROLLER.allConstraintAnnotationsMasterList, DifferentValidationAnnotationOptions.class); List<Pair<Annotation, AnnotatedElement>> strictMemberCheckClassAnnotations = getSubAnnotationListForElementsOfOwnerClass(TROLLER.allConstraintAnnotationsMasterList, StrictMemberCheck.class); List<Pair<Annotation, AnnotatedElement>> combinedAnnotations = new ArrayList<>(annotationOptionsClassAnnotations); combinedAnnotations.addAll(strictMemberCheckClassAnnotations); assertThat(getSubAnnotationListUsingExclusionFilters(combinedAnnotations, Arrays.<Class<?>>asList(DifferentValidationAnnotationOptions.class), null).size(), is(strictMemberCheckClassAnnotations.size())); assertThat(getSubAnnotationListUsingExclusionFilters(combinedAnnotations, Arrays.<Class<?>>asList(StrictMemberCheck.class), null).size(), is(annotationOptionsClassAnnotations.size())); assertThat(getSubAnnotationListUsingExclusionFilters(combinedAnnotations, Arrays.<Class<?>>asList(DifferentValidationAnnotationOptions.class, StrictMemberCheck.class), null).size(), is(0)); } /** * Another test for {@link ReflectionBasedJsr303AnnotationTrollerBase#getSubAnnotationListUsingExclusionFilters(java.util.List, java.util.List, java.util.List)}, this time for the specificAnnotationDeclarationExclusionMatchers * exclusion filter. */ @Test public void verifyThatExclusionFilterMethodIsExcludingSpecifiedMembers() throws NoSuchFieldException, NoSuchMethodException { List<Pair<Annotation, AnnotatedElement>> strictMemberCheckClassAnnotations = getSubAnnotationListForElementsOfOwnerClass(TROLLER.allConstraintAnnotationsMasterList, StrictMemberCheck.class); assertTrue(strictMemberCheckClassAnnotations.size() > 4); assertThat( getSubAnnotationListUsingExclusionFilters(strictMemberCheckClassAnnotations, null, STRICT_MEMBER_CHECK_EXCLUSIONS).size(), is(strictMemberCheckClassAnnotations.size() - 4)); } /** * Another test for {@link ReflectionBasedJsr303AnnotationTrollerBase#getSubAnnotationListUsingExclusionFilters(java.util.List, java.util.List, java.util.List)} */ @Test public void verifyThatExclusionFilterMethodIsExcludingBoth() throws NoSuchFieldException, NoSuchMethodException { List<Pair<Annotation, AnnotatedElement>> annotationOptionsClassAnnotations = getSubAnnotationListForElementsOfOwnerClass(TROLLER.allConstraintAnnotationsMasterList, DifferentValidationAnnotationOptions.class); List<Pair<Annotation, AnnotatedElement>> strictMemberCheckClassAnnotations = getSubAnnotationListForElementsOfOwnerClass(TROLLER.allConstraintAnnotationsMasterList, StrictMemberCheck.class); List<Pair<Annotation, AnnotatedElement>> combinedAnnotations = new ArrayList<>(annotationOptionsClassAnnotations); combinedAnnotations.addAll(strictMemberCheckClassAnnotations); assertTrue(strictMemberCheckClassAnnotations.size() > 4); assertThat( getSubAnnotationListUsingExclusionFilters(strictMemberCheckClassAnnotations, Arrays.<Class<?>>asList(DifferentValidationAnnotationOptions.class), STRICT_MEMBER_CHECK_EXCLUSIONS).size(), is(strictMemberCheckClassAnnotations.size() - 4)); } @SomeClassLevelJsr303Annotation.List( { @SomeClassLevelJsr303Annotation(message = "I am a class annotated with a constraint in a list 1"), @SomeClassLevelJsr303Annotation(message = "I am a class annotated with a constraint in a list 2") } ) @OtherClassLevelJsr303Annotation(message = "I am a class annotated with a constraint NOT in a list") public static class DifferentValidationAnnotationOptions { @Min(value = 1, message = "I am a field annotated with a constraint") private Integer annotatedField; @AssertTrue(message = "I am a constructor annotated with a constraint even though it doesn't really make sense") public DifferentValidationAnnotationOptions(String nonAnnotatedConstructorParam, @NotNull(message = "I am a constructor param annotated with a constraint 1") String annotatedConstructorParam1, @NotNull(message = "I am a constructor param annotated with a constraint 2") String annotatedConstructorParam2, String alsoNotAnnotatedConstructorParam) { } @AssertFalse(message = "I am an annotated method") public boolean annotatedMethod(String nonAnnotatedMethodParam, @Max(value = 42, message = "I am an annotated method param 1") Integer annotatedMethodParam1, @Min(value = 42, message = "I am an annotated method param 2") Integer annotatedMethodParam2, String alsoNotAnnotatedMethodParam) { return true; } } @SomeClassLevelJsr303Annotation.List( { @SomeClassLevelJsr303Annotation(message = "I am not a ApiError enum name - class annotation"), @SomeClassLevelJsr303Annotation(message = "INVALID_COUNT_VALUE") } ) @OtherClassLevelJsr303Annotation(message = "I am also not a ApiError enum name - class annotation 2") public static class StrictMemberCheck { @Min(value = 1, message="INVALID_COUNT_VALUE") public Integer compliantField; @Min(value = 1, message="I am not a ApiError enum name - field annotation") private Integer nonCompliantField; @NotNull(message = "TYPE_CONVERSION_ERROR") private String compliantMethod() { return null; } @NotNull(message = "I am not a ApiError enum name - method annotation") public String nonCompliantMethod() { return null; } /** * @return The list of annotation exclusions (generated by {@link ReflectionBasedJsr303AnnotationTrollerBase#generateExclusionForAnnotatedElementAndAnnotationClass(java.lang.reflect.AnnotatedElement, Class)}) * for this StrictMemberCheck class that are intentionally not compliant with the "all JSR 303 messages should point to {@link com.nike.backstopper.apierror.ApiError}" requirement * (and should therefore not cause the unit tests based on {@link ReflectionBasedJsr303AnnotationTrollerBase} to fail). */ public static List<Predicate<Pair<Annotation, AnnotatedElement>>> getStrictMemberCheckExclusionsForNonCompliantDeclarations() { try { List<Predicate<Pair<Annotation, AnnotatedElement>>> strictExclusionsList = new ArrayList<>(); strictExclusionsList.addAll( Arrays.asList(generateExclusionForAnnotatedElementAndAnnotationClass(StrictMemberCheck.class.getDeclaredField("nonCompliantField"), Min.class), generateExclusionForAnnotatedElementAndAnnotationClass(StrictMemberCheck.class.getDeclaredMethod("nonCompliantMethod"), NotNull.class), generateExclusionForAnnotatedElementAndAnnotationClass(StrictMemberCheck.class, OtherClassLevelJsr303Annotation.class), Predicates.and(generateExclusionForAnnotatedElementAndAnnotationClass(StrictMemberCheck.class, SomeClassLevelJsr303Annotation.class), new Predicate<Pair<Annotation, AnnotatedElement>>() { public boolean apply(Pair<Annotation, AnnotatedElement> input) { // At this point we know it's StrictMemberCheck class and a SomeClassLevelJsr303Annotation annotation. We want to exclude the one we know is bad that we // don't care about. String message = extractMessageFromAnnotation(input.getLeft()); return message.equals("I am not a ApiError enum name - class annotation"); } }))); return strictExclusionsList; } catch (Exception e) { throw new RuntimeException(e); } } } @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = SomeClassLevelJsr303AnnotationValidator.class) public @interface SomeClassLevelJsr303Annotation { String message() default "{SomeClassLevelJsr303Annotation.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default {}; @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @interface List { SomeClassLevelJsr303Annotation[] value(); } } public class SomeClassLevelJsr303AnnotationValidator implements ConstraintValidator<SomeClassLevelJsr303Annotation, Object> { @Override public void initialize(SomeClassLevelJsr303Annotation constraintAnnotation) { } @Override public boolean isValid(Object value, ConstraintValidatorContext context) { return true; } } @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = OtherClassLevelJsr303AnnotationValidator.class) public @interface OtherClassLevelJsr303Annotation { String message() default "{OtherClassLevelJsr303Annotation.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default {}; @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @interface List { OtherClassLevelJsr303Annotation[] value(); } } public class OtherClassLevelJsr303AnnotationValidator implements ConstraintValidator<OtherClassLevelJsr303Annotation, Object> { @Override public void initialize(OtherClassLevelJsr303Annotation constraintAnnotation) { } @Override public boolean isValid(Object value, ConstraintValidatorContext context) { return true; } } }