diff --git a/pom.xml b/pom.xml
index dd9bd70..f6b039f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
it.aboutbitsspring-boot-toolbox
- 1.0.0-RC1
+ 1.0.0-RC2
diff --git a/src/main/java/it/aboutbits/springboot/testing/persistance/PersistenceAssert.java b/src/main/java/it/aboutbits/springboot/testing/persistance/PersistenceAssert.java
new file mode 100644
index 0000000..c08a6c6
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/testing/persistance/PersistenceAssert.java
@@ -0,0 +1,146 @@
+package it.aboutbits.springboot.testing.persistance;
+
+import it.aboutbits.springboot.testing.spring.BeanAccessor;
+import it.aboutbits.springboot.toolbox.persistence.ChangeAware;
+import it.aboutbits.springboot.toolbox.type.identity.EntityId;
+import it.aboutbits.springboot.toolbox.type.identity.Identified;
+import jakarta.persistence.EntityManager;
+import lombok.AccessLevel;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public final class PersistenceAssert {
+ private PersistenceAssert() {
+ }
+
+ private static EntityManager getEntityManager() {
+ return BeanAccessor.getBean(EntityManager.class);
+ }
+
+ @SuppressWarnings("unused")
+ public static & ChangeAware> WriteOperationAsserter assertThatEntity(@NonNull E before) {
+ return new WriteOperationAsserter<>(getEntityManager(), before);
+ }
+
+ @SuppressWarnings("unused")
+ public static > WriteOperationIdAsserter assertThatEntity(@NonNull ID id) {
+ return new WriteOperationIdAsserter<>(getEntityManager(), id);
+ }
+
+ @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+ public static final class WriteOperationAsserter & ChangeAware> {
+ private final EntityManager entityManager;
+ private final E entity;
+
+ @SuppressWarnings("unused")
+ public void hasBeenCreatedInDatabase() {
+ entityManager.clear();
+ var savedInstance = getSavedInstance();
+
+ assertThat(
+ savedInstance.getId()
+ ).isNotNull();
+
+ assertThat(
+ savedInstance.getCreatedAt()
+ ).isNotNull();
+ }
+
+ @SuppressWarnings("unused")
+ public void hasBeenUpdatedInDatabase() {
+ entityManager.clear();
+ var savedInstance = getSavedInstance();
+
+ assertThat(
+ savedInstance.getUpdatedAt()
+ ).isAfter(
+ entity.getUpdatedAt()
+ );
+ }
+
+ @SuppressWarnings("unused")
+ public void hasNotChangedInDatabase() {
+ entityManager.clear();
+ var savedInstance = getSavedInstance();
+
+ assertThat(savedInstance).isNotNull();
+
+ assertThat(
+ savedInstance.getUpdatedAt()
+ ).isEqualTo(
+ entity.getUpdatedAt()
+ );
+ }
+
+ @SuppressWarnings("unused")
+ public void isAbsentInDatabase() {
+ entityManager.clear();
+ var savedInstance = getSavedInstance();
+
+ assertThat(savedInstance).isNull();
+ }
+
+ @SuppressWarnings("unused")
+ public void isPresentInDatabase() {
+ entityManager.clear();
+ var savedInstance = getSavedInstance();
+
+ assertThat(savedInstance).isNotNull();
+ }
+
+ @SuppressWarnings("unchecked")
+ private E getSavedInstance() {
+ return (E) entityManager.find(
+ entity.getClass(),
+ entity.getId()
+ );
+ }
+ }
+
+ public static final class WriteOperationIdAsserter> {
+ private final EntityManager entityManager;
+ private final ID id;
+ private final Class extends Identified extends ID>> clazz;
+
+ @SuppressWarnings("unchecked")
+ private WriteOperationIdAsserter(
+ EntityManager entityManager,
+ ID id
+ ) {
+ this.entityManager = entityManager;
+ this.id = id;
+
+ var enclosingClass = id.getClass().getEnclosingClass();
+ if (Identified.class.isAssignableFrom(enclosingClass)) {
+ this.clazz = (Class extends Identified extends ID>>) enclosingClass;
+ } else {
+ throw new IllegalArgumentException("EntityId is not a subclass of Identified!");
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public void isAbsentInDatabase() {
+ entityManager.clear();
+ var savedInstance = getSavedInstance();
+
+ assertThat(savedInstance).isNull();
+ }
+
+ @SuppressWarnings("unused")
+ public void isPresentInDatabase() {
+ entityManager.clear();
+ var savedInstance = getSavedInstance();
+
+ assertThat(savedInstance).isNotNull();
+ }
+
+ private Object getSavedInstance() {
+ return entityManager.find(
+ clazz,
+ id
+ );
+ }
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/testing/spring/BeanAccessor.java b/src/main/java/it/aboutbits/springboot/testing/spring/BeanAccessor.java
new file mode 100644
index 0000000..267e8d9
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/testing/spring/BeanAccessor.java
@@ -0,0 +1,23 @@
+package it.aboutbits.springboot.testing.spring;
+
+import lombok.NonNull;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+@Component
+public class BeanAccessor implements ApplicationContextAware {
+ private static ApplicationContext applicationContext;
+
+ public static T getBean(@NonNull Class clazz) {
+ if (applicationContext == null) {
+ throw new IllegalStateException("ApplicationContext is not set. Did you @EnableBeanAccessor?");
+ }
+ return applicationContext.getBean(clazz);
+ }
+
+ @Override
+ public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
+ BeanAccessor.applicationContext = applicationContext;
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/testing/spring/EnableBeanAccessor.java b/src/main/java/it/aboutbits/springboot/testing/spring/EnableBeanAccessor.java
new file mode 100644
index 0000000..3cf0664
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/testing/spring/EnableBeanAccessor.java
@@ -0,0 +1,14 @@
+package it.aboutbits.springboot.testing.spring;
+
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Import(BeanAccessor.class)
+public @interface EnableBeanAccessor {
+}
diff --git a/src/main/java/it/aboutbits/springboot/testing/testdata/base/ModifyableTestDataCreator.java b/src/main/java/it/aboutbits/springboot/testing/testdata/base/ModifyableTestDataCreator.java
new file mode 100644
index 0000000..bd12e1c
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/testing/testdata/base/ModifyableTestDataCreator.java
@@ -0,0 +1,95 @@
+package it.aboutbits.springboot.testing.testdata.base;
+
+import it.aboutbits.springboot.testing.spring.BeanAccessor;
+import jakarta.persistence.EntityManager;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.ObjIntConsumer;
+import java.util.function.UnaryOperator;
+
+@SuppressWarnings("unchecked")
+@Slf4j
+public abstract class ModifyableTestDataCreator, ITEM, PARAMETER> extends TestDataCreator {
+ private boolean mutatorSet = false;
+ private boolean mutatorCalled = false;
+
+ protected BiFunction parameterMutator = (parameter, index) -> {
+ mutatorCalled = true;
+ return parameter;
+ };
+ protected ObjIntConsumer resultMutator = (item, index) -> {
+ };
+
+ protected ModifyableTestDataCreator(int count) {
+ super(count);
+ }
+
+ public CREATOR modifyParameter(BiFunction parameterMutator) {
+ this.parameterMutator = (parameter, index) -> {
+ mutatorCalled = true;
+ return parameterMutator.apply(parameter, index);
+ };
+ mutatorSet = true;
+ return (CREATOR) this;
+ }
+
+ public CREATOR modifyParameter(UnaryOperator parameterMutator) {
+ this.parameterMutator = (parameter, index) -> {
+ mutatorCalled = true;
+ return parameterMutator.apply(parameter);
+ };
+ mutatorSet = true;
+ return (CREATOR) this;
+ }
+
+ public CREATOR modifyResult(ObjIntConsumer resultMutator) {
+ this.resultMutator = resultMutator;
+ return (CREATOR) this;
+ }
+
+ public CREATOR modifyResult(Consumer resultMutator) {
+ this.resultMutator = (item, index) -> resultMutator.accept(item);
+ return (CREATOR) this;
+ }
+
+ @Override
+ protected List create() {
+ var result = new ArrayList();
+
+ for (var index = 0; index < numberOfItems; index++) {
+ var item = create(index);
+
+ resultMutator.accept(item, index);
+
+ result.add(
+ saveMutation(item)
+ );
+ }
+
+ if (mutatorSet && !mutatorCalled) {
+ log.error("Parameter-mutation is defined but was never called.");
+ throw new IllegalStateException("Parameter-mutation is defined but was never called.");
+ }
+
+ return result;
+ }
+
+ protected ITEM saveMutation(ITEM item) {
+ var entityManager = BeanAccessor.getBean(EntityManager.class);
+ var transactionTemplate = BeanAccessor.getBean(TransactionTemplate.class);
+
+ return transactionTemplate.execute(status -> {
+ try {
+ return entityManager.merge(item);
+ } catch (Exception e) {
+ status.setRollbackOnly();
+ throw e;
+ }
+ });
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/testing/testdata/base/TestDataCreator.java b/src/main/java/it/aboutbits/springboot/testing/testdata/base/TestDataCreator.java
new file mode 100644
index 0000000..3a23885
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/testing/testdata/base/TestDataCreator.java
@@ -0,0 +1,57 @@
+package it.aboutbits.springboot.testing.testdata.base;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+public abstract class TestDataCreator {
+ protected final int numberOfItems;
+
+ protected TestDataCreator(int numberIfItems) {
+ this.numberOfItems = numberIfItems;
+ }
+
+ public void commit() {
+ create();
+ }
+
+ public ITEM returnFirst() {
+ return create().getFirst();
+ }
+
+ public List returnAll() {
+ return create();
+ }
+
+ public List returnSorted(Comparator comparator) {
+ return returnAll().stream().sorted(comparator).toList();
+ }
+
+ public > List returnSorted(Function comparator) {
+ return returnAll().stream()
+ .sorted(Comparator.comparing(comparator))
+ .toList();
+ }
+
+ public Set returnSet() {
+ return new HashSet<>(create());
+ }
+
+ protected List create() {
+ var result = new ArrayList();
+
+ for (var index = 0; index < numberOfItems; index++) {
+ result.add(
+ create(index)
+ );
+ }
+
+ return result;
+ }
+
+ protected abstract ITEM create(int index);
+
+}
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 6edee7b..af80537 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
@@ -34,6 +34,7 @@ public abstract class BaseValidationAssert> {
*
* @param type The class to whitelist.
*/
+ @SuppressWarnings("unused")
public static void registerNonBeanType(Class> type) {
NON_BEAN_TYPES.add(type);
}
@@ -48,16 +49,18 @@ public
CallBuilder of(@NonNull P parameterUnderTest) {
public static final class CallBuilder, P> {
private final BaseValidationAssert parent;
+ @SuppressWarnings("unused")
public R calling(@NonNull Consumer
functionToCallWithParameter) {
parent.setFunctionToCallWithParameter(functionToCallWithParameter);
return parent.ruleBuilder;
}
+ @SuppressWarnings("unused")
public R usingBeanValidation() {
return parent.ruleBuilder;
}
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({"unused", "unchecked"})
public R calling(
@NonNull BiConsumer functionToCallWithParameter,
@NonNull ID id
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 4343980..2072ed2 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
@@ -32,6 +32,7 @@ public class BiggerThanValueSource implements ValueSource {
TYPE_SOURCES.put(ScaledBigDecimal.class, BiggerThanValueSource::getScaledBigDecimalStream);
}
+ @SuppressWarnings("unchecked")
public static void registerType(Class> type, Function