From 589404581331b68f9334458badc0087a4ca7de96 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Tue, 3 Sep 2024 08:42:43 +0200 Subject: [PATCH 01/11] add initial validation implementation --- .gitignore | 1 + .idea/encodings.xml | 6 + pom.xml | 30 ++ .../validation/core/BaseRuleBuilder.java | 59 ++++ .../validation/core/BaseValidationAssert.java | 83 ++++++ .../testing/validation/core/Rule.java | 52 ++++ .../validation/core/RuleValidator.java | 256 ++++++++++++++++++ .../validation/core/ValidationRulesData.java | 7 + .../testing/validation/core/ValueSource.java | 7 + .../testing/validation/rule/BetweenRule.java | 20 ++ .../testing/validation/rule/FutureRule.java | 16 ++ .../testing/validation/rule/MaxRule.java | 16 ++ .../testing/validation/rule/MinRule.java | 16 ++ .../validation/rule/NegativeOrZeroRule.java | 16 ++ .../testing/validation/rule/NegativeRule.java | 20 ++ .../testing/validation/rule/NotBlankRule.java | 16 ++ .../testing/validation/rule/NotEmptyRule.java | 16 ++ .../testing/validation/rule/NotNullRule.java | 16 ++ .../testing/validation/rule/NullableRule.java | 15 + .../testing/validation/rule/PastRule.java | 16 ++ .../validation/rule/PositiveOrZeroRule.java | 16 ++ .../testing/validation/rule/PositiveRule.java | 20 ++ .../validation/rule/UncheckedRule.java | 16 ++ .../validation/rule/ValidBeanRule.java | 15 + .../source/BiggerThanValueSource.java | 103 +++++++ .../validation/source/BlankValueSource.java | 33 +++ .../validation/source/EmptyValueSource.java | 45 +++ .../validation/source/FutureValueSource.java | 66 +++++ .../validation/source/InertValueSource.java | 12 + .../source/LessThanValueSource.java | 104 +++++++ .../validation/source/NullValueSource.java | 26 ++ .../validation/source/PastValueSource.java | 67 +++++ .../validation/source/ZeroValueSource.java | 43 +++ .../validation/TestValidationAssert.java | 14 + .../validation/ValidationAssertTest.java | 255 +++++++++++++++++ 35 files changed, 1519 insertions(+) create mode 100644 .idea/encodings.xml create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/core/Rule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/core/ValidationRulesData.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/core/ValueSource.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/BetweenRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/FutureRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/MaxRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/MinRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeOrZeroRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/NotBlankRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/NotEmptyRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/NotNullRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/NullableRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/PastRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveOrZeroRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/UncheckedRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/rule/ValidBeanRule.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/source/InertValueSource.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java create mode 100644 src/test/java/it/aboutbits/springboot/testing/validation/TestValidationAssert.java create mode 100644 src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java diff --git a/.gitignore b/.gitignore index 26c6288..0dab500 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ build/ ### Local Development ### application-local.yml +/src/main/java/it/aboutbits/springboot/testing/demo_project/ diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..63e9001 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8df6b57..dab0479 100644 --- a/pom.xml +++ b/pom.xml @@ -3,6 +3,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.2 + + + it.aboutbits spring-boot-testing BUILD-SNAPSHOT @@ -13,7 +20,30 @@ + + org.springframework.boot + spring-boot-starter-validation + + + + + org.projectlombok + lombok + true + + + + + org.jetbrains + annotations + 24.1.0 + + + + org.springframework.boot + spring-boot-starter-test + diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java new file mode 100644 index 0000000..4b67de6 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java @@ -0,0 +1,59 @@ +package it.aboutbits.springboot.testing.validation.core; + +import it.aboutbits.springboot.testing.validation.rule.BetweenRule; +import it.aboutbits.springboot.testing.validation.rule.FutureRule; +import it.aboutbits.springboot.testing.validation.rule.MaxRule; +import it.aboutbits.springboot.testing.validation.rule.MinRule; +import it.aboutbits.springboot.testing.validation.rule.NegativeOrZeroRule; +import it.aboutbits.springboot.testing.validation.rule.NegativeRule; +import it.aboutbits.springboot.testing.validation.rule.NotBlankRule; +import it.aboutbits.springboot.testing.validation.rule.NotEmptyRule; +import it.aboutbits.springboot.testing.validation.rule.NotNullRule; +import it.aboutbits.springboot.testing.validation.rule.NullableRule; +import it.aboutbits.springboot.testing.validation.rule.PastRule; +import it.aboutbits.springboot.testing.validation.rule.PositiveOrZeroRule; +import it.aboutbits.springboot.testing.validation.rule.PositiveRule; +import it.aboutbits.springboot.testing.validation.rule.UncheckedRule; +import it.aboutbits.springboot.testing.validation.rule.ValidBeanRule; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor +public abstract class BaseRuleBuilder> implements + ValidationRulesData, + BetweenRule, + FutureRule, + MaxRule, + MinRule, + NegativeOrZeroRule, + NegativeRule, + NotBlankRule, + NotEmptyRule, + NotNullRule, + NullableRule, + PastRule, + PositiveOrZeroRule, + PositiveRule, + UncheckedRule, + ValidBeanRule { + @Getter(AccessLevel.PACKAGE) + private final List rules = new ArrayList<>(); + + @Setter(AccessLevel.PACKAGE) + private Runnable triggerValidation; + + @Override + public void addRule(@NonNull Rule rule) { + rules.add(rule); + } + + public void isCompliant() { + triggerValidation.run(); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java new file mode 100644 index 0000000..b9f652e --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java @@ -0,0 +1,83 @@ +package it.aboutbits.springboot.testing.validation.core; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import static org.assertj.core.api.Assertions.assertThat; + +@RequiredArgsConstructor +public abstract class BaseValidationAssert> implements AutoCloseable { + @Getter(AccessLevel.PROTECTED) + private final R ruleBuilder; + + protected static final Set> NON_BEAN_TYPES = new HashSet<>(); + + private Object parameterUnderTest; + + @Setter(AccessLevel.PRIVATE) + private Consumer functionToCallWithParameter = null; + + protected boolean hasBeenCalled = false; + + public static void registerNonBeanType(Class type) { + NON_BEAN_TYPES.add(type); + } + + public

CallBuilder that(@NonNull P parameterUnderTest) { + this.parameterUnderTest = parameterUnderTest; + ruleBuilder.setTriggerValidation(this::assertValidation); + return new CallBuilder<>(this, parameterUnderTest); + } + + @Override + public void close() throws RuntimeException { + assertThat(hasBeenCalled) + .withFailMessage("Validation was never invoked by calling 'isCompliant()'.") + .isTrue(); + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + public static final class CallBuilder, P> { + private final BaseValidationAssert parent; + private final P parameterUnderTest; + + public R calling(@NonNull Consumer

functionToCallWithParameter) { + parent.setFunctionToCallWithParameter((Consumer) functionToCallWithParameter); + return parent.ruleBuilder; + } + + public R usingBeanValidation() { + return parent.ruleBuilder; + } + + public R calling( + @NonNull BiConsumer functionToCallWithParameter, + @NonNull ID id + ) { + parent.setFunctionToCallWithParameter( + p -> functionToCallWithParameter.accept(id, (P) p) + ); + return parent.ruleBuilder; + } + } + + private void assertValidation() { + hasBeenCalled = true; + new RuleValidator<>().assertValidation( + new RuleValidator.AssertionParameter<>( + parameterUnderTest, + functionToCallWithParameter, + ruleBuilder.getRules(), + NON_BEAN_TYPES + ) + ); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/Rule.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/Rule.java new file mode 100644 index 0000000..48cdbdf --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/Rule.java @@ -0,0 +1,52 @@ +package it.aboutbits.springboot.testing.validation.core; + +import it.aboutbits.springboot.testing.validation.source.InertValueSource; +import lombok.AccessLevel; +import lombok.Getter; + +public final class Rule { + @Getter(AccessLevel.PACKAGE) + private final String property; + + @Getter(AccessLevel.PACKAGE) + private final Class valueSource; + + @Getter(AccessLevel.PACKAGE) + private final Object[] args; + + @Getter(AccessLevel.PACKAGE) + private boolean requireValid = false; + + @Getter(AccessLevel.PACKAGE) + private boolean requireNullable = false; + + public Rule(String property, Class valueSource, Object... args) { + checkPropertyName(property); + + this.property = property; + this.valueSource = valueSource; + this.args = args; + } + + public static Rule validAnnotated(String property) { + checkPropertyName(property); + + var rule = new Rule(property, InertValueSource.class); + rule.requireValid = true; + return rule; + } + + public static Rule nullableAnnotated(String property) { + checkPropertyName(property); + + var rule = new Rule(property, InertValueSource.class); + rule.requireNullable = true; + return rule; + } + + private static void checkPropertyName(String property) { + if (property.contains(".")) { + throw new IllegalArgumentException("Referencing sub-objects using dot notation is not supported."); + } + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java new file mode 100644 index 0000000..3094fce --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java @@ -0,0 +1,256 @@ +package it.aboutbits.springboot.testing.validation.core; + +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Valid; +import jakarta.validation.Validation; +import jakarta.validation.ValidatorFactory; +import lombok.NonNull; +import lombok.SneakyThrows; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +final class RuleValidator

{ + private static final ValidatorFactory VALIDATOR_FACTORY = Validation.buildDefaultValidatorFactory(); + + record AssertionParameter

( + @NonNull + P parameterUnderTest, + @Nullable + Consumer

functionToCallWithParameter, + @NonNull + List rules, + @NonNull + Set> nonBeanTypes + ) { + } + + void assertValidation(AssertionParameter

assertionParameter) { + var parameterUnderTest = assertionParameter.parameterUnderTest(); + var functionToCallWithParameter = assertionParameter.functionToCallWithParameter(); + var rules = assertionParameter.rules(); + var nonBeanTypes = assertionParameter.nonBeanTypes(); + + assertThat(rules) + .withFailMessage("Validation failed: no rules were defined.") + .isNotEmpty(); + + var validator = VALIDATOR_FACTORY.getValidator(); + + // Create a set to keep track of properties that have validation rules + var propertiesWithRules = new HashSet(); + + // Iterate through the rules and validate each property + for (var rule : rules) { + propertiesWithRules.add(rule.getProperty()); + + var values = getValues(rule, parameterUnderTest); + values.forEach(alteredValue -> { + // Create a copy of the original object + var copy = createCopyWithAlteredProperty(parameterUnderTest, rule.getProperty(), alteredValue); + + if (functionToCallWithParameter != null) { + assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy( + () -> functionToCallWithParameter.accept(copy) + ).withMessageContaining(rule.getProperty()); + } else { + + // Use Bean Validation to validate the copy + var violations = validator.validate(copy); + + // Check if there are any violations + assertThat(violations) + .withFailMessage( + "More than one property failed to validate during mutation. The supplied parameter is possibly contains invalid values.") + .hasSizeLessThan(2); + + assertThat(violations) + .withFailMessage("Validation failed for property: " + rule.getProperty() + " [" + alteredValue + "]") + .hasSize(1); + } + }); + } + + var propertiesWithValid = rules.stream() + .filter(Rule::isRequireValid) + .map(Rule::getProperty) + .collect(Collectors.toSet()); + + for (var property : propertiesWithValid) { + propertiesWithRules.add(property); + + assertThat(hasValidAnnotation(property, parameterUnderTest)) + .withFailMessage("Missing @Valid annotation for property: " + property) + .isTrue(); + } + + var propertiesWithNullable = rules.stream() + .filter(Rule::isRequireNullable) + .map(Rule::getProperty) + .collect(Collectors.toSet()); + + for (var property : propertiesWithNullable) { + propertiesWithRules.add(property); + + assertThat(hasNullableAnnotation(property, parameterUnderTest)) + .withFailMessage("Missing @Nullable annotation for property: " + property + ". Note: This does not work with `org.jetbrains.annotations.Nullable` because of their retention policy. Use `org.springframework.lang.Nullable` or `jakarta.annotation.Nullable` instead.") + .isTrue(); + } + + // Check if all properties have rules (you can also handle this differently based on your needs) + var allProperties = getAllPropertiesOf(parameterUnderTest); + + var missingProperties = new HashSet<>(allProperties); + missingProperties.removeAll(propertiesWithRules); + + assertThat(missingProperties) + .withFailMessage("Not all properties have validation rules: " + "[" + String.join( + ", ", + missingProperties + ) + "]") + .isEmpty(); + + checkIfNestedValidationIsEnabledForNestedRecords(parameterUnderTest, nonBeanTypes); + } + + private static

void checkIfNestedValidationIsEnabledForNestedRecords( + P parameterUnderTest, + Set> nonBeanTypes + ) { + var clazz = parameterUnderTest.getClass(); + + for (var field : clazz.getDeclaredFields()) { + var isRecord = Record.class.isAssignableFrom(field.getType()); + if (isRecord) { + var isBeanType = nonBeanTypes.stream().noneMatch(type -> type.isAssignableFrom(field.getType())); + + if (isBeanType) { + var maybeAnnotation = field.getAnnotation(Valid.class); + assertThat(maybeAnnotation).withFailMessage("Missing @Valid annotation for property: " + field.getName() + ". Note: This is implicitly assumed for nested records. You can configure `BaseValidationAssert.registerNonBeanType` to add exceptions.") + .isNotNull(); + } + } + } + } + + @NotNull + @SneakyThrows(ReflectiveOperationException.class) + private static

Stream getValues(Rule rule, P parameterUnderTest) { + var source = (ValueSource) rule.getValueSource().getDeclaredConstructors()[0].newInstance(); + + return source.values(getPropertyType(rule.getProperty(), parameterUnderTest), rule.getArgs()); + } + + @SuppressWarnings("unchecked") + private static T createCopyWithAlteredProperty(T original, String property, Object alteredValue) { + try { + // Get the class of the original object + var clazz = original.getClass(); + + // Get all the declared fields of the class + var fields = clazz.getDeclaredFields(); + + // Create an array to hold the values of the original object's properties + var propertyValues = new Object[fields.length]; + + // Get the types of the fields + var parameterTypes = new Class[fields.length]; + + // Populate the array with the current property values + for (var i = 0; i < fields.length; i++) { + fields[i].setAccessible(true); + propertyValues[i] = fields[i].get(original); + parameterTypes[i] = fields[i].getType(); + } + + // Get the constructor that accepts all properties as arguments + var constructor = clazz.getDeclaredConstructor(parameterTypes); + constructor.setAccessible(true); + + // Create an array to hold the new property values + var newPropertyValues = new Object[propertyValues.length]; + + // Find the index of the property to be altered + var propertyIndex = -1; + for (var i = 0; i < fields.length; i++) { + if (fields[i].getName().equals(property)) { + propertyIndex = i; + break; + } + } + + // Replace the value of the altered property + newPropertyValues[propertyIndex] = alteredValue; + + // Copy the other property values + for (var i = 0; i < propertyValues.length; i++) { + if (i != propertyIndex) { + newPropertyValues[i] = propertyValues[i]; + } + } + + // Create a copy with the altered property value + return (T) constructor.newInstance(newPropertyValues); + } catch (NoSuchMethodException e) { + throw new RuntimeException( + "Error creating copy with altered property. Maybe there is no all-args-constructor?", + e + ); + } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new RuntimeException("Error creating copy with altered property: " + property, e); + } + } + + private static Set getAllPropertiesOf(T object) { + var clazz = object.getClass(); + + var properties = new HashSet(); + for (var field : clazz.getDeclaredFields()) { + properties.add(field.getName()); + } + + return properties; + } + + private static Class getPropertyType(String propertyName, Object object) { + var field = getFieldOrFail(propertyName, object); + + return field.getType(); + } + + private static boolean hasValidAnnotation(String propertyName, T object) { + var field = getFieldOrFail(propertyName, object); + + return field.getAnnotation(Valid.class) != null; + } + + private static boolean hasNullableAnnotation(String propertyName, Object object) { + var field = getFieldOrFail(propertyName, object); + + return field.getAnnotation(org.springframework.lang.Nullable.class) != null + || field.getAnnotation(jakarta.annotation.Nullable.class) != null; + } + + private static @NotNull Field getFieldOrFail(String propertyName, Object object) { + var clazz = object.getClass(); + + Field field = null; + try { + field = clazz.getDeclaredField(propertyName); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Property does not exist: " + propertyName, e); + } + return field; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/ValidationRulesData.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/ValidationRulesData.java new file mode 100644 index 0000000..2af0435 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/ValidationRulesData.java @@ -0,0 +1,7 @@ +package it.aboutbits.springboot.testing.validation.core; + +import lombok.NonNull; + +public interface ValidationRulesData { + void addRule(@NonNull Rule rule); +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/ValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/ValueSource.java new file mode 100644 index 0000000..c8f2264 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/ValueSource.java @@ -0,0 +1,7 @@ +package it.aboutbits.springboot.testing.validation.core; + +import java.util.stream.Stream; + +public interface ValueSource { + Stream values(Class propertyClass, Object... args); +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/BetweenRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/BetweenRule.java new file mode 100644 index 0000000..3522933 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/BetweenRule.java @@ -0,0 +1,20 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.BiggerThanValueSource; +import it.aboutbits.springboot.testing.validation.source.LessThanValueSource; +import lombok.NonNull; + +public interface BetweenRule> extends ValidationRulesData { + default V between(@NonNull String property, long min, long max) { + addRule( + new Rule(property, BiggerThanValueSource.class, max) + ); + addRule( + new Rule(property, LessThanValueSource.class, min) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/FutureRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/FutureRule.java new file mode 100644 index 0000000..585c1bc --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/FutureRule.java @@ -0,0 +1,16 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.PastValueSource; +import lombok.NonNull; + +public interface FutureRule> extends ValidationRulesData { + default V future(@NonNull String property) { + addRule( + new Rule(property, PastValueSource.class) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/MaxRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/MaxRule.java new file mode 100644 index 0000000..1abaec0 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/MaxRule.java @@ -0,0 +1,16 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.BiggerThanValueSource; +import lombok.NonNull; + +public interface MaxRule> extends ValidationRulesData { + default V max(@NonNull String property, long max) { + addRule( + new Rule(property, BiggerThanValueSource.class, max) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/MinRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/MinRule.java new file mode 100644 index 0000000..e8e4c75 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/MinRule.java @@ -0,0 +1,16 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.LessThanValueSource; +import lombok.NonNull; + +public interface MinRule> extends ValidationRulesData { + default V min(@NonNull String property, long min) { + addRule( + new Rule(property, LessThanValueSource.class, min) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeOrZeroRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeOrZeroRule.java new file mode 100644 index 0000000..9ff380b --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeOrZeroRule.java @@ -0,0 +1,16 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.BiggerThanValueSource; +import lombok.NonNull; + +public interface NegativeOrZeroRule> extends ValidationRulesData { + default V negativeOrZero(@NonNull String property) { + addRule( + new Rule(property, BiggerThanValueSource.class, 0L) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeRule.java new file mode 100644 index 0000000..632cbc5 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeRule.java @@ -0,0 +1,20 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.BiggerThanValueSource; +import it.aboutbits.springboot.testing.validation.source.ZeroValueSource; +import lombok.NonNull; + +public interface NegativeRule> extends ValidationRulesData { + default V negative(@NonNull String property) { + addRule( + new Rule(property, BiggerThanValueSource.class, 0L) + ); + addRule( + new Rule(property, ZeroValueSource.class) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotBlankRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotBlankRule.java new file mode 100644 index 0000000..2aed992 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotBlankRule.java @@ -0,0 +1,16 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.BlankValueSource; +import lombok.NonNull; + +public interface NotBlankRule> extends ValidationRulesData { + default V notBlank(@NonNull String property) { + addRule( + new Rule(property, BlankValueSource.class) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotEmptyRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotEmptyRule.java new file mode 100644 index 0000000..8ed3e85 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotEmptyRule.java @@ -0,0 +1,16 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.EmptyValueSource; +import lombok.NonNull; + +public interface NotEmptyRule> extends ValidationRulesData { + default V notEmpty(@NonNull String property) { + addRule( + new Rule(property, EmptyValueSource.class) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotNullRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotNullRule.java new file mode 100644 index 0000000..0c7d63d --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotNullRule.java @@ -0,0 +1,16 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.NullValueSource; +import lombok.NonNull; + +public interface NotNullRule> extends ValidationRulesData { + default V notNull(@NonNull String property) { + addRule( + new Rule(property, NullValueSource.class) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NullableRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NullableRule.java new file mode 100644 index 0000000..7a5baba --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NullableRule.java @@ -0,0 +1,15 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import lombok.NonNull; + +public interface NullableRule> extends ValidationRulesData { + default V nullable(@NonNull String property) { + addRule( + Rule.nullableAnnotated(property) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/PastRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PastRule.java new file mode 100644 index 0000000..53854ab --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PastRule.java @@ -0,0 +1,16 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.FutureValueSource; +import lombok.NonNull; + +public interface PastRule> extends ValidationRulesData { + default V past(@NonNull String property) { + addRule( + new Rule(property, FutureValueSource.class) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveOrZeroRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveOrZeroRule.java new file mode 100644 index 0000000..137e940 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveOrZeroRule.java @@ -0,0 +1,16 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.LessThanValueSource; +import lombok.NonNull; + +public interface PositiveOrZeroRule> extends ValidationRulesData { + default V positiveOrZero(@NonNull String property) { + addRule( + new Rule(property, LessThanValueSource.class, 0L) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveRule.java new file mode 100644 index 0000000..a9be1c3 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveRule.java @@ -0,0 +1,20 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.LessThanValueSource; +import it.aboutbits.springboot.testing.validation.source.ZeroValueSource; +import lombok.NonNull; + +public interface PositiveRule> extends ValidationRulesData { + default V positive(@NonNull String property) { + addRule( + new Rule(property, LessThanValueSource.class, 0L) + ); + addRule( + new Rule(property, ZeroValueSource.class) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/UncheckedRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/UncheckedRule.java new file mode 100644 index 0000000..0b929b7 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/UncheckedRule.java @@ -0,0 +1,16 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import it.aboutbits.springboot.testing.validation.source.InertValueSource; +import lombok.NonNull; + +public interface UncheckedRule> extends ValidationRulesData { + default V unchecked(@NonNull String property) { + addRule( + new Rule(property, InertValueSource.class) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/ValidBeanRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/ValidBeanRule.java new file mode 100644 index 0000000..e7555a5 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/ValidBeanRule.java @@ -0,0 +1,15 @@ +package it.aboutbits.springboot.testing.validation.rule; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.Rule; +import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; +import lombok.NonNull; + +public interface ValidBeanRule> extends ValidationRulesData { + default V validBean(@NonNull String property) { + addRule( + Rule.validAnnotated(property) + ); + return (V) this; + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java new file mode 100644 index 0000000..e9b1fe4 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java @@ -0,0 +1,103 @@ +package it.aboutbits.springboot.testing.validation.source; + +import it.aboutbits.springboot.testing.validation.core.ValueSource; +import org.jetbrains.annotations.NotNull; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.function.Function; +import java.util.stream.Stream; + +public class BiggerThanValueSource implements ValueSource { + private static final Map, Function>> TYPE_SOURCES = new HashMap<>(); + private static final Random RANDOM = new Random(); + + static { + TYPE_SOURCES.put(Integer.class, BiggerThanValueSource::getIntegerStream); + TYPE_SOURCES.put(int.class, BiggerThanValueSource::getIntegerStream); + + TYPE_SOURCES.put(Float.class, BiggerThanValueSource::getFloatStream); + TYPE_SOURCES.put(float.class, BiggerThanValueSource::getFloatStream); + + TYPE_SOURCES.put(Long.class, BiggerThanValueSource::getLongStream); + TYPE_SOURCES.put(long.class, BiggerThanValueSource::getLongStream); + + TYPE_SOURCES.put(Double.class, BiggerThanValueSource::getDoubleStream); + TYPE_SOURCES.put(double.class, BiggerThanValueSource::getDoubleStream); + + TYPE_SOURCES.put(BigDecimal.class, BiggerThanValueSource::getBigDecimalStream); + } + + public static void registerType(Class type, Function> source) { + TYPE_SOURCES.put(type, source); + } + + @Override + public Stream values(Class propertyClass, Object... args) { + var sourceFunction = TYPE_SOURCES.get(propertyClass); + if (sourceFunction != null) { + return sourceFunction.apply(args); + } + + throw new IllegalArgumentException("Property class not supported!"); + } + + @NotNull + private static Stream getBigDecimalStream(Object[] args) { + var minValue = Long.valueOf((long) args[0]).doubleValue() + 0.1d; + var maxValue = Double.MAX_VALUE; + + return Stream.concat( + Stream.of(BigDecimal.valueOf(minValue), BigDecimal.valueOf(maxValue)), + RANDOM.doubles(minValue, maxValue).limit(5).boxed().map(BigDecimal::valueOf) + ); + } + + @NotNull + private static Stream getDoubleStream(Object[] args) { + var minValue = Long.valueOf((long) args[0]).doubleValue() + 0.1d; + var maxValue = Double.MAX_VALUE; + + return Stream.concat( + Stream.of(minValue, maxValue), + RANDOM.doubles(minValue, maxValue).limit(5).boxed() + ); + } + + @NotNull + private static Stream getLongStream(Object[] args) { + var minValue = (long) args[0] + 1; + var maxValue = Long.MAX_VALUE; + + return Stream.concat( + Stream.of(minValue, maxValue), + RANDOM.longs(minValue, maxValue).limit(5).boxed() + ); + } + + @NotNull + private static Stream getFloatStream(Object[] args) { + var minValue = Long.valueOf((long) args[0]).floatValue() + 0.1f; + var maxValue = Float.MAX_VALUE; + + return Stream.concat( + Stream.of(minValue, maxValue), + RANDOM.doubles(minValue, maxValue).limit(5).boxed().map( + Double::floatValue + ) + ); + } + + @NotNull + private static Stream getIntegerStream(Object[] args) { + var minValue = Long.valueOf((long) args[0]).intValue() + 1; + var maxValue = Integer.MAX_VALUE; + + return Stream.concat( + Stream.of(minValue, maxValue), + RANDOM.ints(minValue, maxValue).limit(5).boxed() + ); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java new file mode 100644 index 0000000..25aafdd --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java @@ -0,0 +1,33 @@ +package it.aboutbits.springboot.testing.validation.source; + +import it.aboutbits.springboot.testing.validation.core.ValueSource; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + +public class BlankValueSource implements ValueSource { + private static final Map, Function>> TYPE_SOURCES = new HashMap<>(); + + static { + TYPE_SOURCES.put( + String.class, + (Object[] args) -> Stream.of("", " ", " ", "\t", "\r", "\n", "\r\n") + ); + } + + public static void registerType(Class type, Function> source) { + TYPE_SOURCES.put(type, source); + } + + @Override + public Stream values(Class propertyClass, Object... args) { + var sourceFunction = TYPE_SOURCES.get(propertyClass); + if (sourceFunction != null) { + return sourceFunction.apply(args); + } + + throw new IllegalArgumentException("Property class not supported!"); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java new file mode 100644 index 0000000..fecfc2e --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java @@ -0,0 +1,45 @@ +package it.aboutbits.springboot.testing.validation.source; + +import it.aboutbits.springboot.testing.validation.core.ValueSource; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + +public class EmptyValueSource implements ValueSource { + private static final Map, Function>> TYPE_SOURCES = new HashMap<>(); + + static { + TYPE_SOURCES.put( + String.class, + (Object[] args) -> Stream.of("") + ); + TYPE_SOURCES.put( + Set.class, + (Object[] args) -> Stream.of(new HashSet<>()) + ); + TYPE_SOURCES.put( + List.class, + (Object[] args) -> Stream.of(new ArrayList<>()) + ); + } + + public static void registerType(Class type, Function> source) { + TYPE_SOURCES.put(type, source); + } + + @Override + public Stream values(Class propertyClass, Object... args) { + var sourceFunction = TYPE_SOURCES.get(propertyClass); + if (sourceFunction != null) { + return sourceFunction.apply(args); + } + + throw new IllegalArgumentException("Property class not supported!"); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java new file mode 100644 index 0000000..0374e8f --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java @@ -0,0 +1,66 @@ +package it.aboutbits.springboot.testing.validation.source; + +import it.aboutbits.springboot.testing.validation.core.ValueSource; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + +public class FutureValueSource implements ValueSource { + private static final Map, Function>> TYPE_SOURCES = new HashMap<>(); + + static { + TYPE_SOURCES.put(LocalDate.class, FutureValueSource::getLocalDateStream); + TYPE_SOURCES.put(LocalDateTime.class, FutureValueSource::getLocalDatetimeStream); + TYPE_SOURCES.put(OffsetDateTime.class, FutureValueSource::getOffsetDateTimeStream); + } + + public static void registerType(Class type, Function> source) { + TYPE_SOURCES.put(type, source); + } + + @Override + public Stream values(Class propertyClass, Object... args) { + var sourceFunction = TYPE_SOURCES.get(propertyClass); + if (sourceFunction != null) { + return sourceFunction.apply(args); + } + + throw new IllegalArgumentException("Property class not supported!"); + } + + private static Stream getLocalDateStream(Object[] args) { + var currentDate = LocalDate.now().plusDays(1); + var largestDate = LocalDate.MAX; + return Stream.concat( + Stream.of(largestDate), + Stream.iterate(currentDate, date -> date.plusDays(1)) + .limit(4) + ); + } + + private static Stream getLocalDatetimeStream(Object[] args) { + var currentDateTime = LocalDateTime.now().plusDays(1); + var largestDateTime = LocalDateTime.MAX; + return Stream.concat( + Stream.of(largestDateTime, currentDateTime.plusSeconds(1)), + Stream.iterate(currentDateTime, dateTime -> dateTime.plusHours(1)) + .limit(4) + ); + } + + private static Stream getOffsetDateTimeStream(Object[] args) { + var currentOffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC).plusDays(1); + var largestOffsetDateTime = OffsetDateTime.MAX; + return Stream.concat( + Stream.of(largestOffsetDateTime, currentOffsetDateTime.plusSeconds(1)), + Stream.iterate(currentOffsetDateTime, offsetDateTime -> offsetDateTime.plusHours(1)) + .limit(2) + ); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/InertValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/InertValueSource.java new file mode 100644 index 0000000..1a26fae --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/InertValueSource.java @@ -0,0 +1,12 @@ +package it.aboutbits.springboot.testing.validation.source; + +import it.aboutbits.springboot.testing.validation.core.ValueSource; + +import java.util.stream.Stream; + +public class InertValueSource implements ValueSource { + @Override + public Stream values(Class propertyClass, Object... args) { + return Stream.empty(); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java new file mode 100644 index 0000000..afc3ecb --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java @@ -0,0 +1,104 @@ +package it.aboutbits.springboot.testing.validation.source; + +import it.aboutbits.springboot.testing.validation.core.ValueSource; +import org.jetbrains.annotations.NotNull; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.function.Function; +import java.util.stream.Stream; + +// For floating point values: The negative max value is the minimum, MIN_VALUE is the smallest positive +public class LessThanValueSource implements ValueSource { + private static final Map, Function>> TYPE_SOURCES = new HashMap<>(); + private static final Random RANDOM = new Random(); + + static { + TYPE_SOURCES.put(Integer.class, LessThanValueSource::getIntegerStream); + TYPE_SOURCES.put(int.class, LessThanValueSource::getIntegerStream); + + TYPE_SOURCES.put(Float.class, LessThanValueSource::getFloatStream); + TYPE_SOURCES.put(float.class, LessThanValueSource::getFloatStream); + + TYPE_SOURCES.put(Long.class, LessThanValueSource::getLongStream); + TYPE_SOURCES.put(long.class, LessThanValueSource::getLongStream); + + TYPE_SOURCES.put(Double.class, LessThanValueSource::getDoubleStream); + TYPE_SOURCES.put(double.class, LessThanValueSource::getDoubleStream); + + TYPE_SOURCES.put(BigDecimal.class, LessThanValueSource::getBigDecimalStream); + } + + public static void registerType(Class type, Function> source) { + TYPE_SOURCES.put(type, source); + } + + @Override + public Stream values(Class propertyClass, Object... args) { + var sourceFunction = TYPE_SOURCES.get(propertyClass); + if (sourceFunction != null) { + return sourceFunction.apply(args); + } + + throw new IllegalArgumentException("Property class not supported!"); + } + + @NotNull + private static Stream getIntegerStream(Object[] args) { + var minValue = Integer.MIN_VALUE; + var maxValue = Long.valueOf((long) args[0]).intValue() - 1; + + return Stream.concat( + Stream.of(minValue, maxValue), + RANDOM.ints(minValue, maxValue).limit(5).boxed() + ); + } + + @NotNull + private static Stream getFloatStream(Object[] args) { + var minValue = Float.MAX_VALUE * -1; + var maxValue = Long.valueOf((long) args[0]).floatValue() - 0.1f; + + return Stream.concat( + Stream.of(minValue, maxValue), + RANDOM.doubles(5).map(d -> minValue + (maxValue - minValue) * d).boxed().map( + Double::floatValue + ) + ); + } + + @NotNull + private static Stream getDoubleStream(Object[] args) { + var minValue = Double.MAX_VALUE * -1; + var maxValue = Long.valueOf((long) args[0]).doubleValue() - 0.1d; + + return Stream.concat( + Stream.of(minValue, maxValue), + RANDOM.doubles(5).map(d -> minValue + (maxValue - minValue) * d).boxed() + ); + } + + @NotNull + private static Stream getBigDecimalStream(Object[] args) { + var minValue = Double.MAX_VALUE * -1; + var maxValue = Long.valueOf((long) args[0]).doubleValue() - 0.1d; + + return Stream.concat( + Stream.of(BigDecimal.valueOf(minValue), BigDecimal.valueOf(maxValue)), + RANDOM.doubles(5).map(d -> minValue + (maxValue - minValue) * d).boxed().map(BigDecimal::valueOf) + ); + } + + @NotNull + private static Stream getLongStream(Object[] args) { + var minValue = Long.MIN_VALUE; + var maxValue = (long) args[0] - 1; + + return Stream.concat( + Stream.of(minValue, maxValue), + RANDOM.longs(minValue, maxValue).limit(5).boxed() + ); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java new file mode 100644 index 0000000..feedcb8 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java @@ -0,0 +1,26 @@ +package it.aboutbits.springboot.testing.validation.source; + +import it.aboutbits.springboot.testing.validation.core.ValueSource; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + +public class NullValueSource implements ValueSource { + private static final Map, Function>> TYPE_SOURCES = new HashMap<>(); + + public static void registerType(Class type, Function> source) { + TYPE_SOURCES.put(type, source); + } + + @Override + public Stream values(Class propertyClass, Object... args) { + var sourceFunction = TYPE_SOURCES.get(propertyClass); + if (sourceFunction != null) { + return sourceFunction.apply(args); + } + + return Stream.of((Object) null); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java new file mode 100644 index 0000000..2a2ae19 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java @@ -0,0 +1,67 @@ +package it.aboutbits.springboot.testing.validation.source; + +import it.aboutbits.springboot.testing.validation.core.ValueSource; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + +public class PastValueSource implements ValueSource { + private static final Map, Function>> TYPE_SOURCES = new HashMap<>(); + + static { + TYPE_SOURCES.put(LocalDate.class, PastValueSource::getLocalDateStream); + TYPE_SOURCES.put(LocalDateTime.class, PastValueSource::getLocalDatetimeStream); + TYPE_SOURCES.put(OffsetDateTime.class, PastValueSource::getOffsetDateTimeStream); + } + + public static void registerType(Class type, Function> source) { + TYPE_SOURCES.put(type, source); + } + + @Override + public Stream values(Class propertyClass, Object... args) { + var sourceFunction = TYPE_SOURCES.get(propertyClass); + if (sourceFunction != null) { + return sourceFunction.apply(args); + } + + throw new IllegalArgumentException("Property class not supported!"); + } + + + private static Stream getLocalDateStream(Object[] args) { + var currentDate = LocalDate.now().minusDays(1); + var smallestDate = LocalDate.MIN; + return Stream.concat( + Stream.of(smallestDate), + Stream.iterate(currentDate, date -> date.minusDays(1)) + .limit(4) + ); + } + + private static Stream getLocalDatetimeStream(Object[] args) { + var currentDateTime = LocalDateTime.now().minusDays(1); + var smallestDateTime = LocalDateTime.MIN; + return Stream.concat( + Stream.of(smallestDateTime, currentDateTime.minusSeconds(1)), + Stream.iterate(currentDateTime, dateTime -> dateTime.minusHours(1)) + .limit(4) + ); + } + + private static Stream getOffsetDateTimeStream(Object[] args) { + var currentOffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC).minusDays(1); + var smallestOffsetDateTime = OffsetDateTime.MIN; + return Stream.concat( + Stream.of(smallestOffsetDateTime, currentOffsetDateTime.minusSeconds(1)), + Stream.iterate(currentOffsetDateTime, offsetDateTime -> offsetDateTime.minusHours(1)) + .limit(4) + ); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java new file mode 100644 index 0000000..eb8a9a6 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java @@ -0,0 +1,43 @@ +package it.aboutbits.springboot.testing.validation.source; + +import it.aboutbits.springboot.testing.validation.core.ValueSource; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + +public class ZeroValueSource implements ValueSource { + private static final Map, Function>> TYPE_SOURCES = new HashMap<>(); + + static { + TYPE_SOURCES.put(Integer.class, (Object[] args) -> Stream.of(0)); + TYPE_SOURCES.put(int.class, (Object[] args) -> Stream.of(0)); + + TYPE_SOURCES.put(Float.class, (Object[] args) -> Stream.of(0F)); + TYPE_SOURCES.put(float.class, (Object[] args) -> Stream.of(0F)); + + TYPE_SOURCES.put(Long.class, (Object[] args) -> Stream.of(0L)); + TYPE_SOURCES.put(long.class, (Object[] args) -> Stream.of(0L)); + + TYPE_SOURCES.put(Double.class, (Object[] args) -> Stream.of(0D)); + TYPE_SOURCES.put(double.class, (Object[] args) -> Stream.of(0D)); + + TYPE_SOURCES.put(BigDecimal.class, (Object[] args) -> Stream.of(BigDecimal.valueOf(0))); + } + + public static void registerType(Class type, Function> source) { + TYPE_SOURCES.put(type, source); + } + + @Override + public Stream values(Class propertyClass, Object... args) { + var sourceFunction = TYPE_SOURCES.get(propertyClass); + if (sourceFunction != null) { + return sourceFunction.apply(args); + } + + throw new IllegalArgumentException("Property class not supported!"); + } +} diff --git a/src/test/java/it/aboutbits/springboot/testing/validation/TestValidationAssert.java b/src/test/java/it/aboutbits/springboot/testing/validation/TestValidationAssert.java new file mode 100644 index 0000000..db55e0f --- /dev/null +++ b/src/test/java/it/aboutbits/springboot/testing/validation/TestValidationAssert.java @@ -0,0 +1,14 @@ +package it.aboutbits.springboot.testing.validation; + +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.BaseValidationAssert; + +public class TestValidationAssert extends BaseValidationAssert> { + public TestValidationAssert() { + super(new TestRuleBuilder()); + } + + public static final class TestRuleBuilder extends BaseRuleBuilder { + + } +} diff --git a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java new file mode 100644 index 0000000..b4dbdf3 --- /dev/null +++ b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java @@ -0,0 +1,255 @@ +package it.aboutbits.springboot.testing.validation; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Negative; +import jakarta.validation.constraints.NegativeOrZero; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Past; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; + +class ValidationAssertTest { + public record SomeParameter( + @NotNull + String notNullable, + @NotBlank + String notBlank, + @Min(5) + int biggerThanInt, + @Min(5) + long biggerThanLong, + @Min(5) + float biggerThanFloat, + @Min(5) + double biggerThanDouble, + @Min(5) + BigDecimal biggerThanBigDecimal, + @Max(5) + int lessThanInt, + @Max(5) + long lessThanLong, + @Max(5) + float lessThanFloat, + @Max(5) + double lessThanDouble, + @Max(5) + BigDecimal lessThanBigDecimal, + @Min(-3) + @Max(5) + int betweenInt, + @Min(-3) + @Max(5) + long betweenLong, + @Min(-3) + @Max(5) + float betweenFloat, + @Min(-3) + @Max(5) + double betweenDouble, + @Min(-3) + @Max(5) + BigDecimal betweenBigDecimal, + @Positive + int positiveInt, + @Positive + long positiveLong, + @Positive + float positiveFloat, + @Positive + double positiveDouble, + @Positive + BigDecimal positiveBigDecimal, + @Negative + int negativeInt, + @Negative + long negativeLong, + @Negative + float negativeFloat, + @Negative + double negativeDouble, + @Negative + BigDecimal negativeBigDecimal, + @PositiveOrZero + int positiveOrZeroInt, + @PositiveOrZero + long positiveOrZeroLong, + @PositiveOrZero + float positiveOrZeroFloat, + @PositiveOrZero + double positiveOrZeroDouble, + @PositiveOrZero + BigDecimal positiveOrZeroBigDecimal, + @NegativeOrZero + int negativeOrZeroInt, + @NegativeOrZero + long negativeOrZeroLong, + @NegativeOrZero + float negativeOrZeroFloat, + @NegativeOrZero + double negativeOrZeroDouble, + @NegativeOrZero + BigDecimal negativeOrZeroBigDecimal, + @Future + LocalDate futureDate, + @Future + LocalDateTime futureDateTime, + @Future + OffsetDateTime futureOffsetDateTime, + @Past + LocalDate pastDate, + @Past + LocalDateTime pastDateTime, + @Past + OffsetDateTime pastOffsetDateTime, + @Valid + Object validObject, + @Nullable + Object nullable, + Object notValidated + ) { + + } + + @Test + @SuppressWarnings("checkstyle:MethodLength") + void testWithBeanValidation() { + var validParameter = new SomeParameter( + // notNull + "", + + // notBlank + "something", + + // min + 6, + 6, + 6, + 6, + BigDecimal.valueOf(6), + + // max + 4, + 4, + 4, + 4, + BigDecimal.valueOf(4), + + // positive + 4, + 4, + 4, + 4, + BigDecimal.valueOf(4), + + // negative + 1, + 1, + 1, + 1, + BigDecimal.valueOf(1), + + // between + -1, + -1, + -1, + -1, + BigDecimal.valueOf(-1), + + // positiveOrZero + 0, + 0, + 0, + 0, + BigDecimal.valueOf(0), + + // negativeOrZero + 0, + 0, + 0, + 0, + BigDecimal.valueOf(0), + + // future + LocalDate.now().plusDays(1), + LocalDateTime.now().plusDays(1), + OffsetDateTime.now().plusDays(1), + + // past + LocalDate.now().minusDays(1), + LocalDateTime.now().minusDays(1), + OffsetDateTime.now().minusDays(1), + + // valid + null, + + // nullable + null, + + // not validated + null + ); + + try (var validate = new TestValidationAssert()) { + validate.that(validParameter) + .usingBeanValidation() + .notNull("notNullable") + .notBlank("notBlank") + .min("biggerThanInt", 5) + .min("biggerThanLong", 5) + .min("biggerThanFloat", 5) + .min("biggerThanDouble", 5) + .min("biggerThanBigDecimal", 5) + .max("lessThanInt", 5) + .max("lessThanLong", 5) + .max("lessThanFloat", 5) + .max("lessThanDouble", 5) + .max("lessThanBigDecimal", 5) + .positive("positiveInt") + .positive("positiveLong") + .positive("positiveFloat") + .positive("positiveDouble") + .positive("positiveBigDecimal") + .negative("negativeInt") + .negative("negativeLong") + .negative("negativeFloat") + .negative("negativeDouble") + .negative("negativeBigDecimal") + .between("betweenInt", -3, 5) + .between("betweenLong", -3, 5) + .between("betweenFloat", -3, 5) + .between("betweenDouble", -3, 5) + .between("betweenBigDecimal", -3, 5) + .positiveOrZero("positiveOrZeroInt") + .positiveOrZero("positiveOrZeroLong") + .positiveOrZero("positiveOrZeroFloat") + .positiveOrZero("positiveOrZeroDouble") + .positiveOrZero("positiveOrZeroBigDecimal") + .negativeOrZero("negativeOrZeroInt") + .negativeOrZero("negativeOrZeroLong") + .negativeOrZero("negativeOrZeroFloat") + .negativeOrZero("negativeOrZeroDouble") + .negativeOrZero("negativeOrZeroBigDecimal") + .future("futureDate") + .future("futureDateTime") + .future("futureOffsetDateTime") + .past("pastDate") + .past("pastDateTime") + .past("pastOffsetDateTime") + .validBean("validObject") + .nullable("nullable") + .unchecked("notValidated") + .isCompliant(); + } + } +} From 3730f7bf7a1d4db555e5e37b6721bfb4a8ba4489 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Thu, 5 Sep 2024 16:35:46 +0200 Subject: [PATCH 02/11] cleanup implementation --- .gitignore | 1 - .../validation/core/BaseRuleBuilder.java | 4 +- .../validation/core/BaseValidationAssert.java | 26 +- .../validation/core/RuleValidator.java | 5 +- .../testing/validation/core/ValueSource.java | 2 +- .../testing/validation/rule/BetweenRule.java | 1 + .../testing/validation/rule/FutureRule.java | 1 + .../testing/validation/rule/MaxRule.java | 1 + .../testing/validation/rule/MinRule.java | 1 + .../validation/rule/NegativeOrZeroRule.java | 1 + .../testing/validation/rule/NegativeRule.java | 1 + .../testing/validation/rule/NotBlankRule.java | 1 + .../testing/validation/rule/NotEmptyRule.java | 1 + .../testing/validation/rule/NotNullRule.java | 1 + ...checkedRule.java => NotValidatedRule.java} | 5 +- .../testing/validation/rule/NullableRule.java | 1 + .../testing/validation/rule/PastRule.java | 1 + .../validation/rule/PositiveOrZeroRule.java | 1 + .../testing/validation/rule/PositiveRule.java | 1 + .../validation/rule/ValidBeanRule.java | 1 + .../source/BiggerThanValueSource.java | 5 +- .../validation/source/BlankValueSource.java | 5 +- .../validation/source/EmptyValueSource.java | 5 +- .../validation/source/FutureValueSource.java | 5 +- .../validation/source/InertValueSource.java | 2 +- .../source/LessThanValueSource.java | 5 +- .../validation/source/NullValueSource.java | 7 +- .../validation/source/PastValueSource.java | 5 +- .../validation/source/ZeroValueSource.java | 5 +- .../validation/TestValidationAssert.java | 14 - .../validation/ValidationAssertTest.java | 241 ++++++++++++++---- 31 files changed, 245 insertions(+), 111 deletions(-) rename src/main/java/it/aboutbits/springboot/testing/validation/rule/{UncheckedRule.java => NotValidatedRule.java} (72%) delete mode 100644 src/test/java/it/aboutbits/springboot/testing/validation/TestValidationAssert.java diff --git a/.gitignore b/.gitignore index 0dab500..26c6288 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,3 @@ build/ ### Local Development ### application-local.yml -/src/main/java/it/aboutbits/springboot/testing/demo_project/ diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java index 4b67de6..fda5fed 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java @@ -9,11 +9,11 @@ import it.aboutbits.springboot.testing.validation.rule.NotBlankRule; import it.aboutbits.springboot.testing.validation.rule.NotEmptyRule; import it.aboutbits.springboot.testing.validation.rule.NotNullRule; +import it.aboutbits.springboot.testing.validation.rule.NotValidatedRule; import it.aboutbits.springboot.testing.validation.rule.NullableRule; import it.aboutbits.springboot.testing.validation.rule.PastRule; import it.aboutbits.springboot.testing.validation.rule.PositiveOrZeroRule; import it.aboutbits.springboot.testing.validation.rule.PositiveRule; -import it.aboutbits.springboot.testing.validation.rule.UncheckedRule; import it.aboutbits.springboot.testing.validation.rule.ValidBeanRule; import lombok.AccessLevel; import lombok.Getter; @@ -40,7 +40,7 @@ public abstract class BaseRuleBuilder> implements PastRule, PositiveOrZeroRule, PositiveRule, - UncheckedRule, + NotValidatedRule, ValidBeanRule { @Getter(AccessLevel.PACKAGE) private final List rules = new ArrayList<>(); diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java index b9f652e..13e26c6 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java @@ -11,10 +11,8 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; -import static org.assertj.core.api.Assertions.assertThat; - -@RequiredArgsConstructor -public abstract class BaseValidationAssert> implements AutoCloseable { +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class BaseValidationAssert> { @Getter(AccessLevel.PROTECTED) private final R ruleBuilder; @@ -23,34 +21,24 @@ public abstract class BaseValidationAssert> impleme private Object parameterUnderTest; @Setter(AccessLevel.PRIVATE) - private Consumer functionToCallWithParameter = null; - - protected boolean hasBeenCalled = false; + private Consumer functionToCallWithParameter = null; public static void registerNonBeanType(Class type) { NON_BEAN_TYPES.add(type); } - public

CallBuilder that(@NonNull P parameterUnderTest) { + public

CallBuilder of(@NonNull P parameterUnderTest) { this.parameterUnderTest = parameterUnderTest; ruleBuilder.setTriggerValidation(this::assertValidation); - return new CallBuilder<>(this, parameterUnderTest); - } - - @Override - public void close() throws RuntimeException { - assertThat(hasBeenCalled) - .withFailMessage("Validation was never invoked by calling 'isCompliant()'.") - .isTrue(); + return new CallBuilder<>(this); } @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public static final class CallBuilder, P> { private final BaseValidationAssert parent; - private final P parameterUnderTest; public R calling(@NonNull Consumer

functionToCallWithParameter) { - parent.setFunctionToCallWithParameter((Consumer) functionToCallWithParameter); + parent.setFunctionToCallWithParameter(functionToCallWithParameter); return parent.ruleBuilder; } @@ -58,6 +46,7 @@ public R usingBeanValidation() { return parent.ruleBuilder; } + @SuppressWarnings("unchecked") public R calling( @NonNull BiConsumer functionToCallWithParameter, @NonNull ID id @@ -70,7 +59,6 @@ public R calling( } private void assertValidation() { - hasBeenCalled = true; new RuleValidator<>().assertValidation( new RuleValidator.AssertionParameter<>( parameterUnderTest, diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java index 3094fce..08766b5 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java @@ -28,7 +28,7 @@ record AssertionParameter

( @NonNull P parameterUnderTest, @Nullable - Consumer

functionToCallWithParameter, + Consumer functionToCallWithParameter, @NonNull List rules, @NonNull @@ -38,7 +38,8 @@ record AssertionParameter

( void assertValidation(AssertionParameter

assertionParameter) { var parameterUnderTest = assertionParameter.parameterUnderTest(); - var functionToCallWithParameter = assertionParameter.functionToCallWithParameter(); + @SuppressWarnings("unchecked") + var functionToCallWithParameter = (Consumer

) assertionParameter.functionToCallWithParameter(); var rules = assertionParameter.rules(); var nonBeanTypes = assertionParameter.nonBeanTypes(); diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/ValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/ValueSource.java index c8f2264..290191f 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/ValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/ValueSource.java @@ -3,5 +3,5 @@ import java.util.stream.Stream; public interface ValueSource { - Stream values(Class propertyClass, Object... args); + Stream values(Class propertyClass, Object... args); } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/BetweenRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/BetweenRule.java index 3522933..285cfe3 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/BetweenRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/BetweenRule.java @@ -7,6 +7,7 @@ import it.aboutbits.springboot.testing.validation.source.LessThanValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface BetweenRule> extends ValidationRulesData { default V between(@NonNull String property, long min, long max) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/FutureRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/FutureRule.java index 585c1bc..7da2a2d 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/FutureRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/FutureRule.java @@ -6,6 +6,7 @@ import it.aboutbits.springboot.testing.validation.source.PastValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface FutureRule> extends ValidationRulesData { default V future(@NonNull String property) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/MaxRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/MaxRule.java index 1abaec0..4858067 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/MaxRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/MaxRule.java @@ -6,6 +6,7 @@ import it.aboutbits.springboot.testing.validation.source.BiggerThanValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface MaxRule> extends ValidationRulesData { default V max(@NonNull String property, long max) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/MinRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/MinRule.java index e8e4c75..5484f08 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/MinRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/MinRule.java @@ -6,6 +6,7 @@ import it.aboutbits.springboot.testing.validation.source.LessThanValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface MinRule> extends ValidationRulesData { default V min(@NonNull String property, long min) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeOrZeroRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeOrZeroRule.java index 9ff380b..4852867 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeOrZeroRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeOrZeroRule.java @@ -6,6 +6,7 @@ import it.aboutbits.springboot.testing.validation.source.BiggerThanValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface NegativeOrZeroRule> extends ValidationRulesData { default V negativeOrZero(@NonNull String property) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeRule.java index 632cbc5..e420f2e 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NegativeRule.java @@ -7,6 +7,7 @@ import it.aboutbits.springboot.testing.validation.source.ZeroValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface NegativeRule> extends ValidationRulesData { default V negative(@NonNull String property) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotBlankRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotBlankRule.java index 2aed992..052cbb3 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotBlankRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotBlankRule.java @@ -6,6 +6,7 @@ import it.aboutbits.springboot.testing.validation.source.BlankValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface NotBlankRule> extends ValidationRulesData { default V notBlank(@NonNull String property) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotEmptyRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotEmptyRule.java index 8ed3e85..53ba30d 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotEmptyRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotEmptyRule.java @@ -6,6 +6,7 @@ import it.aboutbits.springboot.testing.validation.source.EmptyValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface NotEmptyRule> extends ValidationRulesData { default V notEmpty(@NonNull String property) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotNullRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotNullRule.java index 0c7d63d..5346ae9 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotNullRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotNullRule.java @@ -6,6 +6,7 @@ import it.aboutbits.springboot.testing.validation.source.NullValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface NotNullRule> extends ValidationRulesData { default V notNull(@NonNull String property) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/UncheckedRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotValidatedRule.java similarity index 72% rename from src/main/java/it/aboutbits/springboot/testing/validation/rule/UncheckedRule.java rename to src/main/java/it/aboutbits/springboot/testing/validation/rule/NotValidatedRule.java index 0b929b7..3e00e20 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/UncheckedRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NotValidatedRule.java @@ -6,8 +6,9 @@ import it.aboutbits.springboot.testing.validation.source.InertValueSource; import lombok.NonNull; -public interface UncheckedRule> extends ValidationRulesData { - default V unchecked(@NonNull String property) { +@SuppressWarnings("unchecked") +public interface NotValidatedRule> extends ValidationRulesData { + default V notValidated(@NonNull String property) { addRule( new Rule(property, InertValueSource.class) ); diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NullableRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NullableRule.java index 7a5baba..69a33c4 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/NullableRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/NullableRule.java @@ -5,6 +5,7 @@ import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface NullableRule> extends ValidationRulesData { default V nullable(@NonNull String property) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/PastRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PastRule.java index 53854ab..b61dd7d 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/PastRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PastRule.java @@ -6,6 +6,7 @@ import it.aboutbits.springboot.testing.validation.source.FutureValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface PastRule> extends ValidationRulesData { default V past(@NonNull String property) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveOrZeroRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveOrZeroRule.java index 137e940..ba9a3fe 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveOrZeroRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveOrZeroRule.java @@ -6,6 +6,7 @@ import it.aboutbits.springboot.testing.validation.source.LessThanValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface PositiveOrZeroRule> extends ValidationRulesData { default V positiveOrZero(@NonNull String property) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveRule.java index a9be1c3..bf9b25b 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/PositiveRule.java @@ -7,6 +7,7 @@ import it.aboutbits.springboot.testing.validation.source.ZeroValueSource; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface PositiveRule> extends ValidationRulesData { default V positive(@NonNull String property) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/rule/ValidBeanRule.java b/src/main/java/it/aboutbits/springboot/testing/validation/rule/ValidBeanRule.java index e7555a5..7c3e7ae 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/rule/ValidBeanRule.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/rule/ValidBeanRule.java @@ -5,6 +5,7 @@ import it.aboutbits.springboot.testing.validation.core.ValidationRulesData; import lombok.NonNull; +@SuppressWarnings("unchecked") public interface ValidBeanRule> extends ValidationRulesData { default V validBean(@NonNull String property) { addRule( diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java index e9b1fe4..9d69be8 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java @@ -35,10 +35,11 @@ public static void registerType(Class type, Function> sou } @Override - public Stream values(Class propertyClass, Object... args) { + @SuppressWarnings("unchecked") + public Stream values(Class propertyClass, Object... args) { var sourceFunction = TYPE_SOURCES.get(propertyClass); if (sourceFunction != null) { - return sourceFunction.apply(args); + return (Stream) sourceFunction.apply(args); } throw new IllegalArgumentException("Property class not supported!"); diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java index 25aafdd..035d6d4 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/BlankValueSource.java @@ -22,10 +22,11 @@ public static void registerType(Class type, Function> sou } @Override - public Stream values(Class propertyClass, Object... args) { + @SuppressWarnings("unchecked") + public Stream values(Class propertyClass, Object... args) { var sourceFunction = TYPE_SOURCES.get(propertyClass); if (sourceFunction != null) { - return sourceFunction.apply(args); + return (Stream) sourceFunction.apply(args); } throw new IllegalArgumentException("Property class not supported!"); diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java index fecfc2e..318c13d 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/EmptyValueSource.java @@ -34,10 +34,11 @@ public static void registerType(Class type, Function> sou } @Override - public Stream values(Class propertyClass, Object... args) { + @SuppressWarnings("unchecked") + public Stream values(Class propertyClass, Object... args) { var sourceFunction = TYPE_SOURCES.get(propertyClass); if (sourceFunction != null) { - return sourceFunction.apply(args); + return (Stream) sourceFunction.apply(args); } throw new IllegalArgumentException("Property class not supported!"); diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java index 0374e8f..5d51a03 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java @@ -25,10 +25,11 @@ public static void registerType(Class type, Function> sou } @Override - public Stream values(Class propertyClass, Object... args) { + @SuppressWarnings("unchecked") + public Stream values(Class propertyClass, Object... args) { var sourceFunction = TYPE_SOURCES.get(propertyClass); if (sourceFunction != null) { - return sourceFunction.apply(args); + return (Stream) sourceFunction.apply(args); } throw new IllegalArgumentException("Property class not supported!"); diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/InertValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/InertValueSource.java index 1a26fae..7e895b6 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/InertValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/InertValueSource.java @@ -6,7 +6,7 @@ public class InertValueSource implements ValueSource { @Override - public Stream values(Class propertyClass, Object... args) { + public Stream values(Class propertyClass, Object... args) { return Stream.empty(); } } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java index afc3ecb..73e6d2e 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java @@ -36,10 +36,11 @@ public static void registerType(Class type, Function> sou } @Override - public Stream values(Class propertyClass, Object... args) { + @SuppressWarnings("unchecked") + public Stream values(Class propertyClass, Object... args) { var sourceFunction = TYPE_SOURCES.get(propertyClass); if (sourceFunction != null) { - return sourceFunction.apply(args); + return (Stream) sourceFunction.apply(args); } throw new IllegalArgumentException("Property class not supported!"); diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java index feedcb8..0656bdf 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/NullValueSource.java @@ -15,12 +15,13 @@ public static void registerType(Class type, Function> sou } @Override - public Stream values(Class propertyClass, Object... args) { + @SuppressWarnings("unchecked") + public Stream values(Class propertyClass, Object... args) { var sourceFunction = TYPE_SOURCES.get(propertyClass); if (sourceFunction != null) { - return sourceFunction.apply(args); + return (Stream) sourceFunction.apply(args); } - return Stream.of((Object) null); + return Stream.of((T) null); } } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java index 2a2ae19..96878c2 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java @@ -25,10 +25,11 @@ public static void registerType(Class type, Function> sou } @Override - public Stream values(Class propertyClass, Object... args) { + @SuppressWarnings("unchecked") + public Stream values(Class propertyClass, Object... args) { var sourceFunction = TYPE_SOURCES.get(propertyClass); if (sourceFunction != null) { - return sourceFunction.apply(args); + return (Stream) sourceFunction.apply(args); } throw new IllegalArgumentException("Property class not supported!"); diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java index eb8a9a6..67e3bd2 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java @@ -32,10 +32,11 @@ public static void registerType(Class type, Function> sou } @Override - public Stream values(Class propertyClass, Object... args) { + @SuppressWarnings("unchecked") + public Stream values(Class propertyClass, Object... args) { var sourceFunction = TYPE_SOURCES.get(propertyClass); if (sourceFunction != null) { - return sourceFunction.apply(args); + return (Stream) sourceFunction.apply(args); } throw new IllegalArgumentException("Property class not supported!"); diff --git a/src/test/java/it/aboutbits/springboot/testing/validation/TestValidationAssert.java b/src/test/java/it/aboutbits/springboot/testing/validation/TestValidationAssert.java deleted file mode 100644 index db55e0f..0000000 --- a/src/test/java/it/aboutbits/springboot/testing/validation/TestValidationAssert.java +++ /dev/null @@ -1,14 +0,0 @@ -package it.aboutbits.springboot.testing.validation; - -import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; -import it.aboutbits.springboot.testing.validation.core.BaseValidationAssert; - -public class TestValidationAssert extends BaseValidationAssert> { - public TestValidationAssert() { - super(new TestRuleBuilder()); - } - - public static final class TestRuleBuilder extends BaseRuleBuilder { - - } -} diff --git a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java index b4dbdf3..f8194f0 100644 --- a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java +++ b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java @@ -1,5 +1,7 @@ package it.aboutbits.springboot.testing.validation; +import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; +import it.aboutbits.springboot.testing.validation.core.BaseValidationAssert; import jakarta.validation.Valid; import jakarta.validation.constraints.Future; import jakarta.validation.constraints.Max; @@ -11,6 +13,7 @@ import jakarta.validation.constraints.Past; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.PositiveOrZero; +import lombok.With; import org.junit.jupiter.api.Test; import org.springframework.lang.Nullable; @@ -19,8 +22,12 @@ import java.time.LocalDateTime; import java.time.OffsetDateTime; +import static it.aboutbits.springboot.testing.validation.ValidationAssertTest.TestValidationAssert.assertThatValidation; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + class ValidationAssertTest { - public record SomeParameter( + @With + public record SomeValidParameter( @NotNull String notNullable, @NotBlank @@ -124,7 +131,175 @@ public record SomeParameter( @Test @SuppressWarnings("checkstyle:MethodLength") void testWithBeanValidation() { - var validParameter = new SomeParameter( + var validParameter = getSomeValidParameter(); + + assertThatValidation().of(validParameter) + .usingBeanValidation() + .notNull("notNullable") + .notBlank("notBlank") + .min("biggerThanInt", 5) + .min("biggerThanLong", 5) + .min("biggerThanFloat", 5) + .min("biggerThanDouble", 5) + .min("biggerThanBigDecimal", 5) + .max("lessThanInt", 5) + .max("lessThanLong", 5) + .max("lessThanFloat", 5) + .max("lessThanDouble", 5) + .max("lessThanBigDecimal", 5) + .positive("positiveInt") + .positive("positiveLong") + .positive("positiveFloat") + .positive("positiveDouble") + .positive("positiveBigDecimal") + .negative("negativeInt") + .negative("negativeLong") + .negative("negativeFloat") + .negative("negativeDouble") + .negative("negativeBigDecimal") + .between("betweenInt", -3, 5) + .between("betweenLong", -3, 5) + .between("betweenFloat", -3, 5) + .between("betweenDouble", -3, 5) + .between("betweenBigDecimal", -3, 5) + .positiveOrZero("positiveOrZeroInt") + .positiveOrZero("positiveOrZeroLong") + .positiveOrZero("positiveOrZeroFloat") + .positiveOrZero("positiveOrZeroDouble") + .positiveOrZero("positiveOrZeroBigDecimal") + .negativeOrZero("negativeOrZeroInt") + .negativeOrZero("negativeOrZeroLong") + .negativeOrZero("negativeOrZeroFloat") + .negativeOrZero("negativeOrZeroDouble") + .negativeOrZero("negativeOrZeroBigDecimal") + .future("futureDate") + .future("futureDateTime") + .future("futureOffsetDateTime") + .past("pastDate") + .past("pastDateTime") + .past("pastOffsetDateTime") + .validBean("validObject") + .nullable("nullable") + .notValidated("notValidated") + .isCompliant(); + } + + @Test + void invalidParameter_shouldFail() { + var validParameter = getSomeValidParameter(); + + var invalidParameter = validParameter.withFutureDate(LocalDate.EPOCH); + + assertThatExceptionOfType(AssertionError.class).isThrownBy( + () -> assertThatValidation().of(invalidParameter) + .usingBeanValidation() + .notNull("notNullable") + .notBlank("notBlank") + .min("biggerThanInt", 5) + .min("biggerThanLong", 5) + .min("biggerThanFloat", 5) + .min("biggerThanDouble", 5) + .min("biggerThanBigDecimal", 5) + .max("lessThanInt", 5) + .max("lessThanLong", 5) + .max("lessThanFloat", 5) + .max("lessThanDouble", 5) + .max("lessThanBigDecimal", 5) + .positive("positiveInt") + .positive("positiveLong") + .positive("positiveFloat") + .positive("positiveDouble") + .positive("positiveBigDecimal") + .negative("negativeInt") + .negative("negativeLong") + .negative("negativeFloat") + .negative("negativeDouble") + .negative("negativeBigDecimal") + .between("betweenInt", -3, 5) + .between("betweenLong", -3, 5) + .between("betweenFloat", -3, 5) + .between("betweenDouble", -3, 5) + .between("betweenBigDecimal", -3, 5) + .positiveOrZero("positiveOrZeroInt") + .positiveOrZero("positiveOrZeroLong") + .positiveOrZero("positiveOrZeroFloat") + .positiveOrZero("positiveOrZeroDouble") + .positiveOrZero("positiveOrZeroBigDecimal") + .negativeOrZero("negativeOrZeroInt") + .negativeOrZero("negativeOrZeroLong") + .negativeOrZero("negativeOrZeroFloat") + .negativeOrZero("negativeOrZeroDouble") + .negativeOrZero("negativeOrZeroBigDecimal") + .future("futureDate") + .future("futureDateTime") + .future("futureOffsetDateTime") + .past("pastDate") + .past("pastDateTime") + .past("pastOffsetDateTime") + .validBean("validObject") + .nullable("nullable") + .notValidated("notValidated") + .isCompliant()); + } + + @Test + void propertyMissingRule_shouldFail() { + var validParameter = getSomeValidParameter(); + + assertThatExceptionOfType(AssertionError.class).isThrownBy( + () -> assertThatValidation().of(validParameter) + .usingBeanValidation() + .notNull("notNullable") + // this is now missing: .notBlank("notBlank") + .min("biggerThanInt", 5) + .min("biggerThanLong", 5) + .min("biggerThanFloat", 5) + .min("biggerThanDouble", 5) + .min("biggerThanBigDecimal", 5) + .max("lessThanInt", 5) + .max("lessThanLong", 5) + .max("lessThanFloat", 5) + .max("lessThanDouble", 5) + .max("lessThanBigDecimal", 5) + .positive("positiveInt") + .positive("positiveLong") + .positive("positiveFloat") + .positive("positiveDouble") + .positive("positiveBigDecimal") + .negative("negativeInt") + .negative("negativeLong") + .negative("negativeFloat") + .negative("negativeDouble") + .negative("negativeBigDecimal") + .between("betweenInt", -3, 5) + .between("betweenLong", -3, 5) + .between("betweenFloat", -3, 5) + .between("betweenDouble", -3, 5) + .between("betweenBigDecimal", -3, 5) + .positiveOrZero("positiveOrZeroInt") + .positiveOrZero("positiveOrZeroLong") + .positiveOrZero("positiveOrZeroFloat") + .positiveOrZero("positiveOrZeroDouble") + .positiveOrZero("positiveOrZeroBigDecimal") + .negativeOrZero("negativeOrZeroInt") + .negativeOrZero("negativeOrZeroLong") + .negativeOrZero("negativeOrZeroFloat") + .negativeOrZero("negativeOrZeroDouble") + .negativeOrZero("negativeOrZeroBigDecimal") + .future("futureDate") + .future("futureDateTime") + .future("futureOffsetDateTime") + .past("pastDate") + .past("pastDateTime") + .past("pastOffsetDateTime") + .validBean("validObject") + .nullable("nullable") + .notValidated("notValidated") + .isCompliant()); + } + + private static SomeValidParameter getSomeValidParameter() { + return new SomeValidParameter( // notNull "", @@ -199,57 +374,19 @@ void testWithBeanValidation() { // not validated null ); + } + + public static class TestValidationAssert extends BaseValidationAssert> { + protected TestValidationAssert() { + super(new TestRuleBuilder()); + } + + public static TestValidationAssert assertThatValidation() { + return new TestValidationAssert(); + } + + public static final class TestRuleBuilder extends BaseRuleBuilder { - try (var validate = new TestValidationAssert()) { - validate.that(validParameter) - .usingBeanValidation() - .notNull("notNullable") - .notBlank("notBlank") - .min("biggerThanInt", 5) - .min("biggerThanLong", 5) - .min("biggerThanFloat", 5) - .min("biggerThanDouble", 5) - .min("biggerThanBigDecimal", 5) - .max("lessThanInt", 5) - .max("lessThanLong", 5) - .max("lessThanFloat", 5) - .max("lessThanDouble", 5) - .max("lessThanBigDecimal", 5) - .positive("positiveInt") - .positive("positiveLong") - .positive("positiveFloat") - .positive("positiveDouble") - .positive("positiveBigDecimal") - .negative("negativeInt") - .negative("negativeLong") - .negative("negativeFloat") - .negative("negativeDouble") - .negative("negativeBigDecimal") - .between("betweenInt", -3, 5) - .between("betweenLong", -3, 5) - .between("betweenFloat", -3, 5) - .between("betweenDouble", -3, 5) - .between("betweenBigDecimal", -3, 5) - .positiveOrZero("positiveOrZeroInt") - .positiveOrZero("positiveOrZeroLong") - .positiveOrZero("positiveOrZeroFloat") - .positiveOrZero("positiveOrZeroDouble") - .positiveOrZero("positiveOrZeroBigDecimal") - .negativeOrZero("negativeOrZeroInt") - .negativeOrZero("negativeOrZeroLong") - .negativeOrZero("negativeOrZeroFloat") - .negativeOrZero("negativeOrZeroDouble") - .negativeOrZero("negativeOrZeroBigDecimal") - .future("futureDate") - .future("futureDateTime") - .future("futureOffsetDateTime") - .past("pastDate") - .past("pastDateTime") - .past("pastOffsetDateTime") - .validBean("validObject") - .nullable("nullable") - .unchecked("notValidated") - .isCompliant(); } } } From b822d67ca0a180e7e5eb09adfb4583012443562d Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Thu, 5 Sep 2024 16:53:57 +0200 Subject: [PATCH 03/11] add dependency to toolbox --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index dab0479..f2c342e 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,12 @@ + + it.aboutbits + spring-boot-toolbox + BUILD-SNAPSHOT + + org.springframework.boot spring-boot-starter-validation From cbe67a66fc81251efe3d9e7f7217215716f1a4c1 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Thu, 5 Sep 2024 16:54:18 +0200 Subject: [PATCH 04/11] add ScaledBigDecimal and reorder methods --- .../source/BiggerThanValueSource.java | 57 ++++++++++++------- .../source/LessThanValueSource.java | 29 +++++++--- .../validation/source/ZeroValueSource.java | 8 ++- 3 files changed, 61 insertions(+), 33 deletions(-) diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java index 9d69be8..79d146a 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java @@ -1,6 +1,7 @@ package it.aboutbits.springboot.testing.validation.source; import it.aboutbits.springboot.testing.validation.core.ValueSource; +import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; import org.jetbrains.annotations.NotNull; import java.math.BigDecimal; @@ -18,16 +19,17 @@ public class BiggerThanValueSource implements ValueSource { TYPE_SOURCES.put(Integer.class, BiggerThanValueSource::getIntegerStream); TYPE_SOURCES.put(int.class, BiggerThanValueSource::getIntegerStream); - TYPE_SOURCES.put(Float.class, BiggerThanValueSource::getFloatStream); - TYPE_SOURCES.put(float.class, BiggerThanValueSource::getFloatStream); - TYPE_SOURCES.put(Long.class, BiggerThanValueSource::getLongStream); TYPE_SOURCES.put(long.class, BiggerThanValueSource::getLongStream); + TYPE_SOURCES.put(Float.class, BiggerThanValueSource::getFloatStream); + TYPE_SOURCES.put(float.class, BiggerThanValueSource::getFloatStream); + TYPE_SOURCES.put(Double.class, BiggerThanValueSource::getDoubleStream); TYPE_SOURCES.put(double.class, BiggerThanValueSource::getDoubleStream); TYPE_SOURCES.put(BigDecimal.class, BiggerThanValueSource::getBigDecimalStream); + TYPE_SOURCES.put(ScaledBigDecimal.class, BiggerThanValueSource::getScaledBigDecimalStream); } public static void registerType(Class type, Function> source) { @@ -46,24 +48,13 @@ public Stream values(Class propertyClass, Object... args) { } @NotNull - private static Stream getBigDecimalStream(Object[] args) { - var minValue = Long.valueOf((long) args[0]).doubleValue() + 0.1d; - var maxValue = Double.MAX_VALUE; - - return Stream.concat( - Stream.of(BigDecimal.valueOf(minValue), BigDecimal.valueOf(maxValue)), - RANDOM.doubles(minValue, maxValue).limit(5).boxed().map(BigDecimal::valueOf) - ); - } - - @NotNull - private static Stream getDoubleStream(Object[] args) { - var minValue = Long.valueOf((long) args[0]).doubleValue() + 0.1d; - var maxValue = Double.MAX_VALUE; + private static Stream getIntegerStream(Object[] args) { + var minValue = Long.valueOf((long) args[0]).intValue() + 1; + var maxValue = Integer.MAX_VALUE; return Stream.concat( Stream.of(minValue, maxValue), - RANDOM.doubles(minValue, maxValue).limit(5).boxed() + RANDOM.ints(minValue, maxValue).limit(5).boxed() ); } @@ -92,13 +83,35 @@ private static Stream getFloatStream(Object[] args) { } @NotNull - private static Stream getIntegerStream(Object[] args) { - var minValue = Long.valueOf((long) args[0]).intValue() + 1; - var maxValue = Integer.MAX_VALUE; + private static Stream getDoubleStream(Object[] args) { + var minValue = Long.valueOf((long) args[0]).doubleValue() + 0.1d; + var maxValue = Double.MAX_VALUE; return Stream.concat( Stream.of(minValue, maxValue), - RANDOM.ints(minValue, maxValue).limit(5).boxed() + RANDOM.doubles(minValue, maxValue).limit(5).boxed() + ); + } + + @NotNull + private static Stream getScaledBigDecimalStream(Object[] args) { + var minValue = Long.valueOf((long) args[0]).doubleValue() + 0.1d; + var maxValue = Double.MAX_VALUE; + + return Stream.concat( + Stream.of(ScaledBigDecimal.valueOf(minValue), ScaledBigDecimal.valueOf(maxValue)), + RANDOM.doubles(minValue, maxValue).limit(5).boxed().map(ScaledBigDecimal::valueOf) + ); + } + + @NotNull + private static Stream getBigDecimalStream(Object[] args) { + var minValue = Long.valueOf((long) args[0]).doubleValue() + 0.1d; + var maxValue = Double.MAX_VALUE; + + return Stream.concat( + Stream.of(BigDecimal.valueOf(minValue), BigDecimal.valueOf(maxValue)), + RANDOM.doubles(minValue, maxValue).limit(5).boxed().map(BigDecimal::valueOf) ); } } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java index 73e6d2e..ad55e41 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java @@ -1,6 +1,7 @@ package it.aboutbits.springboot.testing.validation.source; import it.aboutbits.springboot.testing.validation.core.ValueSource; +import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; import org.jetbrains.annotations.NotNull; import java.math.BigDecimal; @@ -19,16 +20,17 @@ public class LessThanValueSource implements ValueSource { TYPE_SOURCES.put(Integer.class, LessThanValueSource::getIntegerStream); TYPE_SOURCES.put(int.class, LessThanValueSource::getIntegerStream); - TYPE_SOURCES.put(Float.class, LessThanValueSource::getFloatStream); - TYPE_SOURCES.put(float.class, LessThanValueSource::getFloatStream); - TYPE_SOURCES.put(Long.class, LessThanValueSource::getLongStream); TYPE_SOURCES.put(long.class, LessThanValueSource::getLongStream); + TYPE_SOURCES.put(Float.class, LessThanValueSource::getFloatStream); + TYPE_SOURCES.put(float.class, LessThanValueSource::getFloatStream); + TYPE_SOURCES.put(Double.class, LessThanValueSource::getDoubleStream); TYPE_SOURCES.put(double.class, LessThanValueSource::getDoubleStream); TYPE_SOURCES.put(BigDecimal.class, LessThanValueSource::getBigDecimalStream); + TYPE_SOURCES.put(ScaledBigDecimal.class, LessThanValueSource::getScaledBigDecimalStream); } public static void registerType(Class type, Function> source) { @@ -57,6 +59,17 @@ private static Stream getIntegerStream(Object[] args) { ); } + @NotNull + private static Stream getLongStream(Object[] args) { + var minValue = Long.MIN_VALUE; + var maxValue = (long) args[0] - 1; + + return Stream.concat( + Stream.of(minValue, maxValue), + RANDOM.longs(minValue, maxValue).limit(5).boxed() + ); + } + @NotNull private static Stream getFloatStream(Object[] args) { var minValue = Float.MAX_VALUE * -1; @@ -93,13 +106,13 @@ private static Stream getBigDecimalStream(Object[] args) { } @NotNull - private static Stream getLongStream(Object[] args) { - var minValue = Long.MIN_VALUE; - var maxValue = (long) args[0] - 1; + private static Stream getScaledBigDecimalStream(Object[] args) { + var minValue = Double.MAX_VALUE * -1; + var maxValue = Long.valueOf((long) args[0]).doubleValue() - 0.1d; return Stream.concat( - Stream.of(minValue, maxValue), - RANDOM.longs(minValue, maxValue).limit(5).boxed() + Stream.of(ScaledBigDecimal.valueOf(minValue), ScaledBigDecimal.valueOf(maxValue)), + RANDOM.doubles(5).map(d -> minValue + (maxValue - minValue) * d).boxed().map(ScaledBigDecimal::valueOf) ); } } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java index 67e3bd2..b0a7c02 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/ZeroValueSource.java @@ -1,6 +1,7 @@ package it.aboutbits.springboot.testing.validation.source; import it.aboutbits.springboot.testing.validation.core.ValueSource; +import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; import java.math.BigDecimal; import java.util.HashMap; @@ -15,16 +16,17 @@ public class ZeroValueSource implements ValueSource { TYPE_SOURCES.put(Integer.class, (Object[] args) -> Stream.of(0)); TYPE_SOURCES.put(int.class, (Object[] args) -> Stream.of(0)); - TYPE_SOURCES.put(Float.class, (Object[] args) -> Stream.of(0F)); - TYPE_SOURCES.put(float.class, (Object[] args) -> Stream.of(0F)); - TYPE_SOURCES.put(Long.class, (Object[] args) -> Stream.of(0L)); TYPE_SOURCES.put(long.class, (Object[] args) -> Stream.of(0L)); + TYPE_SOURCES.put(Float.class, (Object[] args) -> Stream.of(0F)); + TYPE_SOURCES.put(float.class, (Object[] args) -> Stream.of(0F)); + TYPE_SOURCES.put(Double.class, (Object[] args) -> Stream.of(0D)); TYPE_SOURCES.put(double.class, (Object[] args) -> Stream.of(0D)); TYPE_SOURCES.put(BigDecimal.class, (Object[] args) -> Stream.of(BigDecimal.valueOf(0))); + TYPE_SOURCES.put(ScaledBigDecimal.class, (Object[] args) -> Stream.of(ScaledBigDecimal.valueOf(0))); } public static void registerType(Class type, Function> source) { From 133447f298bf2dd72a1bb6f7b98398f6682927b7 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Fri, 6 Sep 2024 08:46:54 +0200 Subject: [PATCH 05/11] add bean validation for ScaledBigDecimal --- .../validation/core/BaseValidationAssert.java | 7 ++- .../validation/ValidationAssertTest.java | 44 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java index 13e26c6..1bf861a 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java @@ -1,5 +1,6 @@ package it.aboutbits.springboot.testing.validation.core; +import it.aboutbits.springboot.toolbox.type.CustomType; import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; @@ -16,7 +17,11 @@ public abstract class BaseValidationAssert> { @Getter(AccessLevel.PROTECTED) private final R ruleBuilder; - protected static final Set> NON_BEAN_TYPES = new HashSet<>(); + protected static final Set> NON_BEAN_TYPES = new HashSet<>( + Set.of( + CustomType.class + ) + ); private Object parameterUnderTest; diff --git a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java index f8194f0..dec8575 100644 --- a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java +++ b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java @@ -2,6 +2,7 @@ import it.aboutbits.springboot.testing.validation.core.BaseRuleBuilder; import it.aboutbits.springboot.testing.validation.core.BaseValidationAssert; +import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; import jakarta.validation.Valid; import jakarta.validation.constraints.Future; import jakarta.validation.constraints.Max; @@ -42,6 +43,8 @@ public record SomeValidParameter( double biggerThanDouble, @Min(5) BigDecimal biggerThanBigDecimal, + @Min(5) + ScaledBigDecimal biggerThanScaledBigDecimal, @Max(5) int lessThanInt, @Max(5) @@ -52,6 +55,8 @@ public record SomeValidParameter( double lessThanDouble, @Max(5) BigDecimal lessThanBigDecimal, + @Max(5) + ScaledBigDecimal lessThanScaledBigDecimal, @Min(-3) @Max(5) int betweenInt, @@ -67,6 +72,9 @@ public record SomeValidParameter( @Min(-3) @Max(5) BigDecimal betweenBigDecimal, + @Min(-3) + @Max(5) + ScaledBigDecimal betweenScaledBigDecimal, @Positive int positiveInt, @Positive @@ -77,6 +85,8 @@ public record SomeValidParameter( double positiveDouble, @Positive BigDecimal positiveBigDecimal, + @Positive + ScaledBigDecimal positiveScaledBigDecimal, @Negative int negativeInt, @Negative @@ -87,6 +97,8 @@ public record SomeValidParameter( double negativeDouble, @Negative BigDecimal negativeBigDecimal, + @Negative + ScaledBigDecimal negativeScaledBigDecimal, @PositiveOrZero int positiveOrZeroInt, @PositiveOrZero @@ -97,6 +109,8 @@ public record SomeValidParameter( double positiveOrZeroDouble, @PositiveOrZero BigDecimal positiveOrZeroBigDecimal, + @PositiveOrZero + ScaledBigDecimal positiveOrZeroScaledBigDecimal, @NegativeOrZero int negativeOrZeroInt, @NegativeOrZero @@ -107,6 +121,8 @@ public record SomeValidParameter( double negativeOrZeroDouble, @NegativeOrZero BigDecimal negativeOrZeroBigDecimal, + @NegativeOrZero + ScaledBigDecimal negativeOrZeroScaledBigDecimal, @Future LocalDate futureDate, @Future @@ -142,36 +158,43 @@ void testWithBeanValidation() { .min("biggerThanFloat", 5) .min("biggerThanDouble", 5) .min("biggerThanBigDecimal", 5) + .min("biggerThanScaledBigDecimal", 5) .max("lessThanInt", 5) .max("lessThanLong", 5) .max("lessThanFloat", 5) .max("lessThanDouble", 5) .max("lessThanBigDecimal", 5) + .max("lessThanScaledBigDecimal", 5) .positive("positiveInt") .positive("positiveLong") .positive("positiveFloat") .positive("positiveDouble") .positive("positiveBigDecimal") + .positive("positiveScaledBigDecimal") .negative("negativeInt") .negative("negativeLong") .negative("negativeFloat") .negative("negativeDouble") .negative("negativeBigDecimal") + .negative("negativeScaledBigDecimal") .between("betweenInt", -3, 5) .between("betweenLong", -3, 5) .between("betweenFloat", -3, 5) .between("betweenDouble", -3, 5) .between("betweenBigDecimal", -3, 5) + .between("betweenScaledBigDecimal", -3, 5) .positiveOrZero("positiveOrZeroInt") .positiveOrZero("positiveOrZeroLong") .positiveOrZero("positiveOrZeroFloat") .positiveOrZero("positiveOrZeroDouble") .positiveOrZero("positiveOrZeroBigDecimal") + .positiveOrZero("positiveOrZeroScaledBigDecimal") .negativeOrZero("negativeOrZeroInt") .negativeOrZero("negativeOrZeroLong") .negativeOrZero("negativeOrZeroFloat") .negativeOrZero("negativeOrZeroDouble") .negativeOrZero("negativeOrZeroBigDecimal") + .negativeOrZero("negativeOrZeroScaledBigDecimal") .future("futureDate") .future("futureDateTime") .future("futureOffsetDateTime") @@ -200,36 +223,43 @@ void invalidParameter_shouldFail() { .min("biggerThanFloat", 5) .min("biggerThanDouble", 5) .min("biggerThanBigDecimal", 5) + .min("biggerThanScaledBigDecimal", 5) .max("lessThanInt", 5) .max("lessThanLong", 5) .max("lessThanFloat", 5) .max("lessThanDouble", 5) .max("lessThanBigDecimal", 5) + .max("lessThanScaledBigDecimal", 5) .positive("positiveInt") .positive("positiveLong") .positive("positiveFloat") .positive("positiveDouble") .positive("positiveBigDecimal") + .positive("positiveScaledBigDecimal") .negative("negativeInt") .negative("negativeLong") .negative("negativeFloat") .negative("negativeDouble") .negative("negativeBigDecimal") + .negative("negativeScaledBigDecimal") .between("betweenInt", -3, 5) .between("betweenLong", -3, 5) .between("betweenFloat", -3, 5) .between("betweenDouble", -3, 5) .between("betweenBigDecimal", -3, 5) + .between("betweenScaledBigDecimal", -3, 5) .positiveOrZero("positiveOrZeroInt") .positiveOrZero("positiveOrZeroLong") .positiveOrZero("positiveOrZeroFloat") .positiveOrZero("positiveOrZeroDouble") .positiveOrZero("positiveOrZeroBigDecimal") + .positiveOrZero("positiveOrZeroScaledBigDecimal") .negativeOrZero("negativeOrZeroInt") .negativeOrZero("negativeOrZeroLong") .negativeOrZero("negativeOrZeroFloat") .negativeOrZero("negativeOrZeroDouble") .negativeOrZero("negativeOrZeroBigDecimal") + .negativeOrZero("negativeOrZeroScaledBigDecimal") .future("futureDate") .future("futureDateTime") .future("futureOffsetDateTime") @@ -256,36 +286,43 @@ void propertyMissingRule_shouldFail() { .min("biggerThanFloat", 5) .min("biggerThanDouble", 5) .min("biggerThanBigDecimal", 5) + .min("biggerThanScaledBigDecimal", 5) .max("lessThanInt", 5) .max("lessThanLong", 5) .max("lessThanFloat", 5) .max("lessThanDouble", 5) .max("lessThanBigDecimal", 5) + .max("lessThanScaledBigDecimal", 5) .positive("positiveInt") .positive("positiveLong") .positive("positiveFloat") .positive("positiveDouble") .positive("positiveBigDecimal") + .positive("positiveScaledBigDecimal") .negative("negativeInt") .negative("negativeLong") .negative("negativeFloat") .negative("negativeDouble") .negative("negativeBigDecimal") + .negative("negativeScaledBigDecimal") .between("betweenInt", -3, 5) .between("betweenLong", -3, 5) .between("betweenFloat", -3, 5) .between("betweenDouble", -3, 5) .between("betweenBigDecimal", -3, 5) + .between("betweenScaledBigDecimal", -3, 5) .positiveOrZero("positiveOrZeroInt") .positiveOrZero("positiveOrZeroLong") .positiveOrZero("positiveOrZeroFloat") .positiveOrZero("positiveOrZeroDouble") .positiveOrZero("positiveOrZeroBigDecimal") + .positiveOrZero("positiveOrZeroScaledBigDecimal") .negativeOrZero("negativeOrZeroInt") .negativeOrZero("negativeOrZeroLong") .negativeOrZero("negativeOrZeroFloat") .negativeOrZero("negativeOrZeroDouble") .negativeOrZero("negativeOrZeroBigDecimal") + .negativeOrZero("negativeOrZeroScaledBigDecimal") .future("futureDate") .future("futureDateTime") .future("futureOffsetDateTime") @@ -312,6 +349,7 @@ private static SomeValidParameter getSomeValidParameter() { 6, 6, BigDecimal.valueOf(6), + ScaledBigDecimal.valueOf(6), // max 4, @@ -319,6 +357,7 @@ private static SomeValidParameter getSomeValidParameter() { 4, 4, BigDecimal.valueOf(4), + ScaledBigDecimal.valueOf(4), // positive 4, @@ -326,6 +365,7 @@ private static SomeValidParameter getSomeValidParameter() { 4, 4, BigDecimal.valueOf(4), + ScaledBigDecimal.valueOf(4), // negative 1, @@ -333,6 +373,7 @@ private static SomeValidParameter getSomeValidParameter() { 1, 1, BigDecimal.valueOf(1), + ScaledBigDecimal.valueOf(1), // between -1, @@ -340,6 +381,7 @@ private static SomeValidParameter getSomeValidParameter() { -1, -1, BigDecimal.valueOf(-1), + ScaledBigDecimal.valueOf(-1), // positiveOrZero 0, @@ -347,6 +389,7 @@ private static SomeValidParameter getSomeValidParameter() { 0, 0, BigDecimal.valueOf(0), + ScaledBigDecimal.valueOf(0), // negativeOrZero 0, @@ -354,6 +397,7 @@ private static SomeValidParameter getSomeValidParameter() { 0, 0, BigDecimal.valueOf(0), + ScaledBigDecimal.valueOf(0), // future LocalDate.now().plusDays(1), From 188f27a52df144320be7e1cb6704f9e4e0ab7d54 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Mon, 9 Sep 2024 12:48:07 +0200 Subject: [PATCH 06/11] remove jetbrains annotations and lock toolbox lib to release candidate version --- pom.xml | 9 +-------- .../validation/core/RuleValidationException.java | 11 +++++++++++ .../testing/validation/core/RuleValidator.java | 14 +++++++------- .../validation/source/BiggerThanValueSource.java | 14 +++++++------- .../validation/source/LessThanValueSource.java | 14 +++++++------- 5 files changed, 33 insertions(+), 29 deletions(-) create mode 100644 src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidationException.java diff --git a/pom.xml b/pom.xml index f2c342e..dd9bd70 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ it.aboutbits spring-boot-toolbox - BUILD-SNAPSHOT + 1.0.0-RC1 @@ -38,13 +38,6 @@ true - - - org.jetbrains - annotations - 24.1.0 - - org.springframework.boot diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidationException.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidationException.java new file mode 100644 index 0000000..3260b67 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidationException.java @@ -0,0 +1,11 @@ +package it.aboutbits.springboot.testing.validation.core; + +class RuleValidationException extends RuntimeException { + RuleValidationException(String message) { + super(message); + } + + RuleValidationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java index 08766b5..0e2d6ce 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java @@ -6,8 +6,7 @@ import jakarta.validation.ValidatorFactory; import lombok.NonNull; import lombok.SneakyThrows; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.springframework.lang.Nullable; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -145,7 +144,7 @@ private static

void checkIfNestedValidationIsEnabledForNestedRecords( } } - @NotNull + @NonNull @SneakyThrows(ReflectiveOperationException.class) private static

Stream getValues(Rule rule, P parameterUnderTest) { var source = (ValueSource) rule.getValueSource().getDeclaredConstructors()[0].newInstance(); @@ -204,12 +203,12 @@ private static T createCopyWithAlteredProperty(T original, String property, // Create a copy with the altered property value return (T) constructor.newInstance(newPropertyValues); } catch (NoSuchMethodException e) { - throw new RuntimeException( + throw new RuleValidationException( "Error creating copy with altered property. Maybe there is no all-args-constructor?", e ); } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { - throw new RuntimeException("Error creating copy with altered property: " + property, e); + throw new RuleValidationException("Error creating copy with altered property: " + property, e); } } @@ -243,14 +242,15 @@ private static boolean hasNullableAnnotation(String propertyName, Object object) || field.getAnnotation(jakarta.annotation.Nullable.class) != null; } - private static @NotNull Field getFieldOrFail(String propertyName, Object object) { + @NonNull + private static Field getFieldOrFail(String propertyName, Object object) { var clazz = object.getClass(); Field field = null; try { field = clazz.getDeclaredField(propertyName); } catch (NoSuchFieldException e) { - throw new RuntimeException("Property does not exist: " + propertyName, e); + throw new RuleValidationException("Property does not exist: " + propertyName, e); } return field; } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java index 79d146a..e6fab3f 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java @@ -2,7 +2,7 @@ import it.aboutbits.springboot.testing.validation.core.ValueSource; import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; -import org.jetbrains.annotations.NotNull; +import lombok.NonNull; import java.math.BigDecimal; import java.util.HashMap; @@ -47,7 +47,7 @@ public Stream values(Class propertyClass, Object... args) { throw new IllegalArgumentException("Property class not supported!"); } - @NotNull + @NonNull private static Stream getIntegerStream(Object[] args) { var minValue = Long.valueOf((long) args[0]).intValue() + 1; var maxValue = Integer.MAX_VALUE; @@ -58,7 +58,7 @@ private static Stream getIntegerStream(Object[] args) { ); } - @NotNull + @NonNull private static Stream getLongStream(Object[] args) { var minValue = (long) args[0] + 1; var maxValue = Long.MAX_VALUE; @@ -69,7 +69,7 @@ private static Stream getLongStream(Object[] args) { ); } - @NotNull + @NonNull private static Stream getFloatStream(Object[] args) { var minValue = Long.valueOf((long) args[0]).floatValue() + 0.1f; var maxValue = Float.MAX_VALUE; @@ -82,7 +82,7 @@ private static Stream getFloatStream(Object[] args) { ); } - @NotNull + @NonNull private static Stream getDoubleStream(Object[] args) { var minValue = Long.valueOf((long) args[0]).doubleValue() + 0.1d; var maxValue = Double.MAX_VALUE; @@ -93,7 +93,7 @@ private static Stream getDoubleStream(Object[] args) { ); } - @NotNull + @NonNull private static Stream getScaledBigDecimalStream(Object[] args) { var minValue = Long.valueOf((long) args[0]).doubleValue() + 0.1d; var maxValue = Double.MAX_VALUE; @@ -104,7 +104,7 @@ private static Stream getScaledBigDecimalStream(Object[] args) ); } - @NotNull + @NonNull private static Stream getBigDecimalStream(Object[] args) { var minValue = Long.valueOf((long) args[0]).doubleValue() + 0.1d; var maxValue = Double.MAX_VALUE; diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java index ad55e41..911c2d2 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java @@ -2,7 +2,7 @@ import it.aboutbits.springboot.testing.validation.core.ValueSource; import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; -import org.jetbrains.annotations.NotNull; +import lombok.NonNull; import java.math.BigDecimal; import java.util.HashMap; @@ -48,7 +48,7 @@ public Stream values(Class propertyClass, Object... args) { throw new IllegalArgumentException("Property class not supported!"); } - @NotNull + @NonNull private static Stream getIntegerStream(Object[] args) { var minValue = Integer.MIN_VALUE; var maxValue = Long.valueOf((long) args[0]).intValue() - 1; @@ -59,7 +59,7 @@ private static Stream getIntegerStream(Object[] args) { ); } - @NotNull + @NonNull private static Stream getLongStream(Object[] args) { var minValue = Long.MIN_VALUE; var maxValue = (long) args[0] - 1; @@ -70,7 +70,7 @@ private static Stream getLongStream(Object[] args) { ); } - @NotNull + @NonNull private static Stream getFloatStream(Object[] args) { var minValue = Float.MAX_VALUE * -1; var maxValue = Long.valueOf((long) args[0]).floatValue() - 0.1f; @@ -83,7 +83,7 @@ private static Stream getFloatStream(Object[] args) { ); } - @NotNull + @NonNull private static Stream getDoubleStream(Object[] args) { var minValue = Double.MAX_VALUE * -1; var maxValue = Long.valueOf((long) args[0]).doubleValue() - 0.1d; @@ -94,7 +94,7 @@ private static Stream getDoubleStream(Object[] args) { ); } - @NotNull + @NonNull private static Stream getBigDecimalStream(Object[] args) { var minValue = Double.MAX_VALUE * -1; var maxValue = Long.valueOf((long) args[0]).doubleValue() - 0.1d; @@ -105,7 +105,7 @@ private static Stream getBigDecimalStream(Object[] args) { ); } - @NotNull + @NonNull private static Stream getScaledBigDecimalStream(Object[] args) { var minValue = Double.MAX_VALUE * -1; var maxValue = Long.valueOf((long) args[0]).doubleValue() - 0.1d; From 1c7a1bb4e6c4f9190b648601489f3daeb4962493 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Tue, 10 Sep 2024 11:24:02 +0200 Subject: [PATCH 07/11] add documentation and extract some methods --- readme.md | 243 ++++++++++++++++++ .../validation/core/BaseValidationAssert.java | 6 + .../validation/core/RuleValidator.java | 68 ++++- .../validation/ValidationAssertTest.java | 4 +- 4 files changed, 315 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index c570d6b..cd9e16d 100644 --- a/readme.md +++ b/readme.md @@ -7,6 +7,7 @@ Testing library for Spring Boot projects. Add this library to the classpath by adding the following maven dependency. Versions can be found [here](../../packages) ```xml + it.aboutbits spring-boot-testing @@ -14,6 +15,248 @@ Add this library to the classpath by adding the following maven dependency. Vers ``` +## Usage + +### Validation + +The validation tester allows us to quickly test simple validation constraints. Most commonly we use bean validation for this. + +#### Configuration + +To use the validation tester in your project you need to extend both the [BaseValidationAssert.java](src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java) and the [BaseRuleBuilder.java](src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java). + +```java +public class ValidationAssert extends BaseValidationAssert> { + protected ValidationAssert() { + super(new RuleBuilder()); + } + + public static ValidationAssert assertThatValidation() { + return new ValidationAssert(); + } + + public static final class RuleBuilder extends BaseRuleBuilder { + + } +} +``` + +By default, the validation tester will assume that all properties of type `Record` are substructures. Therefore, using the `@Valid` annotation is required to make sure that validation for those records is triggered. +You can add a class to a whitelist to disable this behavior: + +```java + +public class ValidationConfig { + public static void configure() { + ValidationAssert.registerNonBeanType(NotValidated.class); + } +} + +public class ValidationAssert extends BaseValidationAssert> { + static { + ValidationConfig.configure(); + } + + // ... +} +``` + +#### Usage + +Each property is required to have at least one rule defined. You can add multiple rules for the same property as needed to combine more complex rulesets. +The tester will fail if not all properties have rules. In case you have properties without any restrictions, use the `notValidated` rule. + +The validation tester works by taking in a **valid** parameter. It will then mutate the parameter internally and test each property with an invalid value. Then a check is done if a validation violation is raised as expected. + +In any case, the call to `isCompliant` is required at the end and then triggers the actual assertion. + +You can use plain bean validation to verify a Record: + +```java +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Past; +import org.springframework.lang.Nullable; + +public record SomeParameter( + @NotBlank + String name, + @Min(18) + int age, + @NotNull + @Past + LocalDate birthDay, + @Nullable + String something, + String notValidatedAtAll +) { +} + + +@Test +void testValidation() { + var validParameter = new SomeParameter("Sepp", 32); + + assertThatValidation().of(validParameter) + .usingBeanValidation() + .notBlank("name") + .min("age", 18) + .notNull("birthDay") + .past("birthDay") + .nullable("something") + .notValidated("notValidatedAtAll") + .isCompliant(); +} +``` + +Alternatively you can use a method call to a service function to verify the validation. This is the preferred way as it makes sure that the bean validation is both triggered and also valid. + +```java +import org.springframework.beans.factory.annotation.Autowired; + +public record SomeParameter( + @NotBlank + String name, + @Min(18) + int age +) { +} + +@Autowired +private MyService myService; + + +@Test +void testValidation() { + var validParameter = new SomeParameter("Sepp", 32); + + assertThatValidation().of(validParameter) + .calling(myService::create) + .notBlank("name") + .min("age", 18) + .isCompliant(); +} + +@Test +void testValidationWithIdParameter() { + var validParameter = new SomeParameter("Sepp", 32); + + assertThatValidation().of(validParameter) + .calling(myService::update, new User.ID(3L)) + .notBlank("name") + .min("age", 18) + .isCompliant(); +} +``` + +#### Adding custom validation rules + +You can add new rules by creating a new interface: + +```java +public interface MyShinyNewRule> extends ValidationRulesData { + default V shiny(@NonNull String property) { + this.addRule(new Rule(property, InertValueSource.class, new Object[0])); + return (BaseRuleBuilder) this; + } +} +``` + +To use the newly created rule, we can simply have our `RuleBuilder` implement the interface: + +```java +public class ValidationAssert extends BaseValidationAssert> { + // ... + + public static final class RuleBuilder extends BaseRuleBuilder implements MyShinyNewRule { + + } +} +``` + +The `Rule` requires the property name, a value-source and an array of optional parameters. For example `min(property, minValue)` takes in the additional parameter for the value. +Note that the value-source must return **invalid** values. This is required because the tool is actively trying to violate the rules to check if an error is raised. + +#### Adding custom value sources + +You can add custom values sources by implementing the `ValueSource` interface. +While the interface can not enforce the static function `registerType`, it is best practice to implement it in a way that keeps this extensible. +This way we can use the same logical value-source for multiple property types. + +Here is an example: + +```java +public class EmptyValueSource implements ValueSource { + private static final Map, Function>> TYPE_SOURCES = new HashMap<>(); + + static { + TYPE_SOURCES.put( + String.class, + (Object[] args) -> Stream.of("") + ); + TYPE_SOURCES.put( + Set.class, + (Object[] args) -> Stream.of(new HashSet<>()) + ); + TYPE_SOURCES.put( + List.class, + (Object[] args) -> Stream.of(new ArrayList<>()) + ); + } + + public static void registerType(Class type, Function> source) { + TYPE_SOURCES.put(type, source); + } + + @Override + @SuppressWarnings("unchecked") + public Stream values(Class propertyClass, Object... args) { + var sourceFunction = TYPE_SOURCES.get(propertyClass); + if (sourceFunction != null) { + return (Stream) sourceFunction.apply(args); + } + + throw new IllegalArgumentException("Property class not supported!"); + } +} +``` + +#### Adding support for custom types + +_Note: CustomType wrappers from the `toolbox` are currently not natively supported._ + +Adding custom types will require some extension to the existing value-sources. Those need to become aware of the new type in order to produce values of said type. + +This can be done by extending the configuration: + +```java +public record SurnameType( + String value +) { +} + +public class ValidationConfig { + public static void configure() { + EmptyValueSource.registerType( + SurnameType.class, + (args) -> { + return Stream.of(new SurnameType("")); + } + ); + + // ... + } +} + +public class ValidationAssert extends BaseValidationAssert> { + static { + ValidationConfig.configure(); + } + + // ... +} +``` + ## Local development: To use this library as a local development dependency, you can simply refer to the version `BUILD-SNAPSHOT`. diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java index 1bf861a..6edee7b 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java @@ -17,6 +17,7 @@ public abstract class BaseValidationAssert> { @Getter(AccessLevel.PROTECTED) private final R ruleBuilder; + // This keeps track of classes that are not required to have a @Valid annotation. protected static final Set> NON_BEAN_TYPES = new HashSet<>( Set.of( CustomType.class @@ -28,6 +29,11 @@ public abstract class BaseValidationAssert> { @Setter(AccessLevel.PRIVATE) private Consumer functionToCallWithParameter = null; + /** + * Configure a class that is not required to have a @Valid annotation. Sub-structures are assumed to always require @Valid. + * + * @param type The class to whitelist. + */ public static void registerNonBeanType(Class type) { NON_BEAN_TYPES.add(type); } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java index 0e2d6ce..c517c87 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java @@ -3,6 +3,7 @@ import jakarta.validation.ConstraintViolationException; import jakarta.validation.Valid; import jakarta.validation.Validation; +import jakarta.validation.Validator; import jakarta.validation.ValidatorFactory; import lombok.NonNull; import lombok.SneakyThrows; @@ -20,6 +21,22 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +/** + * The main idea of the RuleValidator is to take in a valid parameter and then mutate it. + * Each mutation changes exactly one property`s value. + * Then we check if an exception is raised. + * We repeat this for each defined rule. + *

+ * Additionally, we also check of @Valid or @Nullable is present where required according to the rules. + * Also, we enforce that all properties must have at least one rule (with a rule existing that says "no-rule"). + *

+ * + * @parameterUnderTest A valid parameter we can use as the basis for our mutations. Validation for the unmodified parameter MUST succeed. + * @functionToCallWithParameter Optional. Instead of directly using bean validation, we can also validate a real function call. This makes sure the parameter is actually annotated with @Valid as well and that the class is using @Validated. + * @rules The list of rules to validate. + * @nonBeanTypes This is a whitelist that holds classes that don't implicitly require @Valid. We assume that @Valid is required + * for all substructures. + */ final class RuleValidator

{ private static final ValidatorFactory VALIDATOR_FACTORY = Validation.buildDefaultValidatorFactory(); @@ -48,13 +65,41 @@ void assertValidation(AssertionParameter

assertionParameter) { var validator = VALIDATOR_FACTORY.getValidator(); + var propertiesWithRules = getPropertyNamesThatHaveRules(rules); + + assertThatValidationIsCompliantForEachProperty( + rules, + parameterUnderTest, + functionToCallWithParameter, + validator + ); + + assertThatValidAnnotationsArePresent(rules, propertiesWithRules, parameterUnderTest); + + assertThatNullableAnnotationsArePresent(rules, propertiesWithRules, parameterUnderTest); + + assertThatAllPropertiesHaveRules(parameterUnderTest, propertiesWithRules); + + checkIfNestedValidationIsEnabledForNestedRecords(parameterUnderTest, nonBeanTypes); + } + + private static HashSet getPropertyNamesThatHaveRules(List rules) { // Create a set to keep track of properties that have validation rules var propertiesWithRules = new HashSet(); - - // Iterate through the rules and validate each property for (var rule : rules) { propertiesWithRules.add(rule.getProperty()); + } + return propertiesWithRules; + } + private static

void assertThatValidationIsCompliantForEachProperty( + List rules, + P parameterUnderTest, + Consumer

functionToCallWithParameter, + Validator validator + ) { + // Iterate through the rules and validate each property + for (var rule : rules) { var values = getValues(rule, parameterUnderTest); values.forEach(alteredValue -> { // Create a copy of the original object @@ -81,7 +126,13 @@ void assertValidation(AssertionParameter

assertionParameter) { } }); } + } + private static

void assertThatValidAnnotationsArePresent( + List rules, + HashSet propertiesWithRules, + P parameterUnderTest + ) { var propertiesWithValid = rules.stream() .filter(Rule::isRequireValid) .map(Rule::getProperty) @@ -94,7 +145,13 @@ void assertValidation(AssertionParameter

assertionParameter) { .withFailMessage("Missing @Valid annotation for property: " + property) .isTrue(); } + } + private static

void assertThatNullableAnnotationsArePresent( + List rules, + HashSet propertiesWithRules, + P parameterUnderTest + ) { var propertiesWithNullable = rules.stream() .filter(Rule::isRequireNullable) .map(Rule::getProperty) @@ -107,7 +164,12 @@ void assertValidation(AssertionParameter

assertionParameter) { .withFailMessage("Missing @Nullable annotation for property: " + property + ". Note: This does not work with `org.jetbrains.annotations.Nullable` because of their retention policy. Use `org.springframework.lang.Nullable` or `jakarta.annotation.Nullable` instead.") .isTrue(); } + } + private static

void assertThatAllPropertiesHaveRules( + P parameterUnderTest, + HashSet propertiesWithRules + ) { // Check if all properties have rules (you can also handle this differently based on your needs) var allProperties = getAllPropertiesOf(parameterUnderTest); @@ -120,8 +182,6 @@ void assertValidation(AssertionParameter

assertionParameter) { missingProperties ) + "]") .isEmpty(); - - checkIfNestedValidationIsEnabledForNestedRecords(parameterUnderTest, nonBeanTypes); } private static

void checkIfNestedValidationIsEnabledForNestedRecords( diff --git a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java index dec8575..e59d945 100644 --- a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java +++ b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java @@ -420,8 +420,8 @@ private static SomeValidParameter getSomeValidParameter() { ); } - public static class TestValidationAssert extends BaseValidationAssert> { - protected TestValidationAssert() { + public static final class TestValidationAssert extends BaseValidationAssert> { + private TestValidationAssert() { super(new TestRuleBuilder()); } From 5cb4ea80b333feb087ba7ebc82d5cf60d9bd64b1 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Tue, 10 Sep 2024 12:27:02 +0200 Subject: [PATCH 08/11] fix workflow --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3ccc8f6..de73bac 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -24,7 +24,7 @@ jobs: run: sed -i 's|BUILD-SNAPSHOT|${{ github.event.inputs.version }}|g' pom.xml - name: Publish package - run: mvn --batch-mode deploy + run: mvn -s $GITHUB_WORKSPACE/.github/workflows/maven-settings.xml --batch-mode deploy env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 19862c8fb8e2cb8ac8d5f6b4c9afdf9d401c8407 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Tue, 10 Sep 2024 12:32:30 +0200 Subject: [PATCH 09/11] fix workflow --- .github/workflows/publish.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index de73bac..9cc4c11 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -26,7 +26,8 @@ jobs: - name: Publish package run: mvn -s $GITHUB_WORKSPACE/.github/workflows/maven-settings.xml --batch-mode deploy env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_USER_NAME: ${{ github.actor }} + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} tag: timeout-minutes: 5 From 20900642932b5e179690e60f2f264b26630c1795 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Tue, 10 Sep 2024 12:37:54 +0200 Subject: [PATCH 10/11] fix workflow --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9cc4c11..ec9a169 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: aboutbits/github-actions-java/setup-and-install@v3 + - uses: aboutbits/github-actions-java/setup@v3 - name: Set Version run: sed -i 's|BUILD-SNAPSHOT|${{ github.event.inputs.version }}|g' pom.xml From 92273f718a4ec65cbc1fd0387288806fb709d6e8 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Tue, 10 Sep 2024 14:56:28 +0200 Subject: [PATCH 11/11] fix review issues --- .../validation/core/RuleValidator.java | 4 +-- .../source/BiggerThanValueSource.java | 12 ++++----- .../validation/source/FutureValueSource.java | 27 +++++++------------ .../source/LessThanValueSource.java | 12 ++++----- .../validation/source/PastValueSource.java | 27 +++++++------------ 5 files changed, 32 insertions(+), 50 deletions(-) diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java index c517c87..48bd79c 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java @@ -27,11 +27,11 @@ * Then we check if an exception is raised. * We repeat this for each defined rule. *

- * Additionally, we also check of @Valid or @Nullable is present where required according to the rules. + * Additionally, we also check if @Valid or @Nullable is present where required according to the rules. * Also, we enforce that all properties must have at least one rule (with a rule existing that says "no-rule"). *

* - * @parameterUnderTest A valid parameter we can use as the basis for our mutations. Validation for the unmodified parameter MUST succeed. + * @parameterUnderTest A valid parameter that we can use as the basis for our mutations. Validation for the unmodified parameter MUST succeed. * @functionToCallWithParameter Optional. Instead of directly using bean validation, we can also validate a real function call. This makes sure the parameter is actually annotated with @Valid as well and that the class is using @Validated. * @rules The list of rules to validate. * @nonBeanTypes This is a whitelist that holds classes that don't implicitly require @Valid. We assume that @Valid is required diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java index e6fab3f..4343980 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/BiggerThanValueSource.java @@ -54,7 +54,7 @@ private static Stream getIntegerStream(Object[] args) { return Stream.concat( Stream.of(minValue, maxValue), - RANDOM.ints(minValue, maxValue).limit(5).boxed() + RANDOM.ints(minValue, maxValue).limit(1).boxed() ); } @@ -65,7 +65,7 @@ private static Stream getLongStream(Object[] args) { return Stream.concat( Stream.of(minValue, maxValue), - RANDOM.longs(minValue, maxValue).limit(5).boxed() + RANDOM.longs(minValue, maxValue).limit(1).boxed() ); } @@ -76,7 +76,7 @@ private static Stream getFloatStream(Object[] args) { return Stream.concat( Stream.of(minValue, maxValue), - RANDOM.doubles(minValue, maxValue).limit(5).boxed().map( + RANDOM.doubles(minValue, maxValue).limit(1).boxed().map( Double::floatValue ) ); @@ -89,7 +89,7 @@ private static Stream getDoubleStream(Object[] args) { return Stream.concat( Stream.of(minValue, maxValue), - RANDOM.doubles(minValue, maxValue).limit(5).boxed() + RANDOM.doubles(minValue, maxValue).limit(1).boxed() ); } @@ -100,7 +100,7 @@ private static Stream getScaledBigDecimalStream(Object[] args) return Stream.concat( Stream.of(ScaledBigDecimal.valueOf(minValue), ScaledBigDecimal.valueOf(maxValue)), - RANDOM.doubles(minValue, maxValue).limit(5).boxed().map(ScaledBigDecimal::valueOf) + RANDOM.doubles(minValue, maxValue).limit(1).boxed().map(ScaledBigDecimal::valueOf) ); } @@ -111,7 +111,7 @@ private static Stream getBigDecimalStream(Object[] args) { return Stream.concat( Stream.of(BigDecimal.valueOf(minValue), BigDecimal.valueOf(maxValue)), - RANDOM.doubles(minValue, maxValue).limit(5).boxed().map(BigDecimal::valueOf) + RANDOM.doubles(minValue, maxValue).limit(1).boxed().map(BigDecimal::valueOf) ); } } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java index 5d51a03..2441a06 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/FutureValueSource.java @@ -36,32 +36,23 @@ public Stream values(Class propertyClass, Object... args) { } private static Stream getLocalDateStream(Object[] args) { - var currentDate = LocalDate.now().plusDays(1); + var currentDate = LocalDate.now(); var largestDate = LocalDate.MAX; - return Stream.concat( - Stream.of(largestDate), - Stream.iterate(currentDate, date -> date.plusDays(1)) - .limit(4) - ); + + return Stream.of(currentDate.plusDays(1), largestDate); } private static Stream getLocalDatetimeStream(Object[] args) { - var currentDateTime = LocalDateTime.now().plusDays(1); + var currentDateTime = LocalDateTime.now(); var largestDateTime = LocalDateTime.MAX; - return Stream.concat( - Stream.of(largestDateTime, currentDateTime.plusSeconds(1)), - Stream.iterate(currentDateTime, dateTime -> dateTime.plusHours(1)) - .limit(4) - ); + + return Stream.of(currentDateTime.plusSeconds(1), largestDateTime); } private static Stream getOffsetDateTimeStream(Object[] args) { - var currentOffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC).plusDays(1); + var currentOffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC); var largestOffsetDateTime = OffsetDateTime.MAX; - return Stream.concat( - Stream.of(largestOffsetDateTime, currentOffsetDateTime.plusSeconds(1)), - Stream.iterate(currentOffsetDateTime, offsetDateTime -> offsetDateTime.plusHours(1)) - .limit(2) - ); + + return Stream.of(currentOffsetDateTime.plusSeconds(1), largestOffsetDateTime); } } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java index 911c2d2..df02ab9 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/LessThanValueSource.java @@ -55,7 +55,7 @@ private static Stream getIntegerStream(Object[] args) { return Stream.concat( Stream.of(minValue, maxValue), - RANDOM.ints(minValue, maxValue).limit(5).boxed() + RANDOM.ints(minValue, maxValue).limit(1).boxed() ); } @@ -66,7 +66,7 @@ private static Stream getLongStream(Object[] args) { return Stream.concat( Stream.of(minValue, maxValue), - RANDOM.longs(minValue, maxValue).limit(5).boxed() + RANDOM.longs(minValue, maxValue).limit(1).boxed() ); } @@ -77,7 +77,7 @@ private static Stream getFloatStream(Object[] args) { return Stream.concat( Stream.of(minValue, maxValue), - RANDOM.doubles(5).map(d -> minValue + (maxValue - minValue) * d).boxed().map( + RANDOM.doubles(1).map(d -> minValue + (maxValue - minValue) * d).boxed().map( Double::floatValue ) ); @@ -90,7 +90,7 @@ private static Stream getDoubleStream(Object[] args) { return Stream.concat( Stream.of(minValue, maxValue), - RANDOM.doubles(5).map(d -> minValue + (maxValue - minValue) * d).boxed() + RANDOM.doubles(1).map(d -> minValue + (maxValue - minValue) * d).boxed() ); } @@ -101,7 +101,7 @@ private static Stream getBigDecimalStream(Object[] args) { return Stream.concat( Stream.of(BigDecimal.valueOf(minValue), BigDecimal.valueOf(maxValue)), - RANDOM.doubles(5).map(d -> minValue + (maxValue - minValue) * d).boxed().map(BigDecimal::valueOf) + RANDOM.doubles(1).map(d -> minValue + (maxValue - minValue) * d).boxed().map(BigDecimal::valueOf) ); } @@ -112,7 +112,7 @@ private static Stream getScaledBigDecimalStream(Object[] args) return Stream.concat( Stream.of(ScaledBigDecimal.valueOf(minValue), ScaledBigDecimal.valueOf(maxValue)), - RANDOM.doubles(5).map(d -> minValue + (maxValue - minValue) * d).boxed().map(ScaledBigDecimal::valueOf) + RANDOM.doubles(1).map(d -> minValue + (maxValue - minValue) * d).boxed().map(ScaledBigDecimal::valueOf) ); } } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java b/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java index 96878c2..f8b2271 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/source/PastValueSource.java @@ -37,32 +37,23 @@ public Stream values(Class propertyClass, Object... args) { private static Stream getLocalDateStream(Object[] args) { - var currentDate = LocalDate.now().minusDays(1); + var currentDate = LocalDate.now(); var smallestDate = LocalDate.MIN; - return Stream.concat( - Stream.of(smallestDate), - Stream.iterate(currentDate, date -> date.minusDays(1)) - .limit(4) - ); + + return Stream.of(smallestDate, currentDate.minusDays(1)); } private static Stream getLocalDatetimeStream(Object[] args) { - var currentDateTime = LocalDateTime.now().minusDays(1); + var currentDateTime = LocalDateTime.now(); var smallestDateTime = LocalDateTime.MIN; - return Stream.concat( - Stream.of(smallestDateTime, currentDateTime.minusSeconds(1)), - Stream.iterate(currentDateTime, dateTime -> dateTime.minusHours(1)) - .limit(4) - ); + + return Stream.of(smallestDateTime, currentDateTime.minusSeconds(1)); } private static Stream getOffsetDateTimeStream(Object[] args) { - var currentOffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC).minusDays(1); + var currentOffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC); var smallestOffsetDateTime = OffsetDateTime.MIN; - return Stream.concat( - Stream.of(smallestOffsetDateTime, currentOffsetDateTime.minusSeconds(1)), - Stream.iterate(currentOffsetDateTime, offsetDateTime -> offsetDateTime.minusHours(1)) - .limit(4) - ); + + return Stream.of(smallestOffsetDateTime, currentOffsetDateTime.minusSeconds(1)); } }