Skip to content

Commit f3dc154

Browse files
authored
Merge pull request #25 from aboutbits/updates
updates
2 parents 52243ad + cac722f commit f3dc154

5 files changed

Lines changed: 253 additions & 50 deletions

File tree

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ jobs:
4040
tag-name: 'v${{ github.event.inputs.version }}'
4141
- uses: aboutbits/github-actions-base/github-create-release@v2
4242
with:
43-
tag-name: 'v${{ github.event.inputs.version }}'
43+
tag-name: '${{ github.event.inputs.version }}'

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<dependency>
2525
<groupId>it.aboutbits</groupId>
2626
<artifactId>spring-boot-toolbox</artifactId>
27-
<version>1.1.0</version>
27+
<version>1.3.0</version>
2828
</dependency>
2929
</dependencies>
3030
</dependencyManagement>

src/main/java/it/aboutbits/springboot/testing/persistence/PersistenceAssert.java

Lines changed: 158 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
import lombok.NonNull;
1010
import lombok.RequiredArgsConstructor;
1111

12+
import java.util.Collection;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.Objects;
16+
import java.util.stream.Collectors;
17+
18+
import static it.aboutbits.springboot.toolbox.util.CollectUtil.collectToSet;
1219
import static org.assertj.core.api.Assertions.assertThat;
1320

1421
public final class PersistenceAssert {
@@ -23,115 +30,224 @@ private static EntityManager getEntityManager() {
2330
public static <ID extends EntityId<?>, E extends Identified<ID> & ChangeAware, M extends Identified<ID> & ChangeAware> WriteOperationAsserter<ID, E, M> assertThatEntity(
2431
@NonNull E before,
2532
@NonNull Class<M> modelClass
33+
) {
34+
return new WriteOperationAsserter<>(getEntityManager(), List.of(before), modelClass);
35+
}
36+
37+
@SuppressWarnings("unused")
38+
public static <ID extends EntityId<?>, E extends Identified<ID> & ChangeAware, M extends Identified<ID> & ChangeAware> WriteOperationAsserter<ID, E, M> assertThatEntity(
39+
@NonNull Collection<E> before,
40+
@NonNull Class<M> modelClass
2641
) {
2742
return new WriteOperationAsserter<>(getEntityManager(), before, modelClass);
2843
}
2944

45+
/**
46+
* @deprecated Use {@link #assertThatEntityId(EntityId, Class)} instead.
47+
*/
48+
@Deprecated
3049
@SuppressWarnings("unused")
3150
public static <ID extends EntityId<?>, M extends Identified<ID>> WriteOperationIdAsserter<ID, M> assertThatEntity(
3251
@NonNull ID id,
3352
@NonNull Class<M> modelClass
53+
) {
54+
return new WriteOperationIdAsserter<>(getEntityManager(), List.of(id), modelClass);
55+
}
56+
57+
@SuppressWarnings("unused")
58+
public static <ID extends EntityId<?>, M extends Identified<ID>> WriteOperationIdAsserter<ID, M> assertThatEntityId(
59+
@NonNull ID id,
60+
@NonNull Class<M> modelClass
61+
) {
62+
return new WriteOperationIdAsserter<>(getEntityManager(), List.of(id), modelClass);
63+
}
64+
65+
@SuppressWarnings("unused")
66+
public static <ID extends EntityId<?>, M extends Identified<ID>> WriteOperationIdAsserter<ID, M> assertThatEntityId(
67+
@NonNull Collection<ID> id,
68+
@NonNull Class<M> modelClass
3469
) {
3570
return new WriteOperationIdAsserter<>(getEntityManager(), id, modelClass);
3671
}
3772

73+
/**
74+
* Batch query entities by their IDs using JPQL
75+
*/
76+
private static <ID extends EntityId<?>, M extends Identified<ID>> List<M> batchFindByIds(
77+
EntityManager entityManager,
78+
Collection<ID> ids,
79+
Class<M> modelClass
80+
) {
81+
if (ids.isEmpty()) {
82+
return List.of();
83+
}
84+
85+
return ids.stream()
86+
.map(
87+
id -> entityManager.find(modelClass, id)
88+
)
89+
.filter(Objects::nonNull)
90+
.toList();
91+
}
92+
93+
/**
94+
* Check if entities with given IDs exist in the database using a count query
95+
*/
96+
private static <ID extends EntityId<?>, M extends Identified<ID>> Map<ID, Boolean> batchCheckExistence(
97+
EntityManager entityManager,
98+
Collection<ID> ids,
99+
Class<M> modelClass
100+
) {
101+
if (ids.isEmpty()) {
102+
return Map.of();
103+
}
104+
105+
// Get existing entities
106+
var existingEntities = batchFindByIds(entityManager, ids, modelClass);
107+
var existingIds = collectToSet(existingEntities, Identified::getId);
108+
109+
// Map each ID to its existence status
110+
return ids.stream()
111+
.collect(Collectors.toMap(
112+
id -> id,
113+
existingIds::contains
114+
));
115+
}
116+
38117
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
39118
public static final class WriteOperationAsserter<ID extends EntityId<?>, E extends Identified<ID> & ChangeAware, M extends Identified<ID> & ChangeAware> {
40119
private final EntityManager entityManager;
41-
private final E entity;
120+
private final Collection<E> entity;
42121
private final Class<M> modelClass;
43122

44123
@SuppressWarnings("unused")
45124
public void hasBeenCreatedInDatabase() {
46125
entityManager.clear();
47-
var savedInstance = getSavedInstance();
48126

49-
assertThat(
50-
savedInstance.getId()
51-
).isNotNull();
127+
var ids = collectToSet(entity, Identified::getId);
128+
var savedInstances = batchFindByIds(entityManager, ids, modelClass);
129+
130+
for (var savedInstance : savedInstances) {
131+
assertThat(
132+
savedInstance.getId()
133+
).isNotNull();
134+
135+
assertThat(
136+
savedInstance.getCreatedAt()
137+
).isNotNull();
138+
}
52139

53-
assertThat(
54-
savedInstance.getCreatedAt()
55-
).isNotNull();
140+
// Verify all entities were found
141+
assertThat(savedInstances).hasSize(entity.size());
56142
}
57143

58144
@SuppressWarnings("unused")
59145
public void hasBeenUpdatedInDatabase() {
60146
entityManager.clear();
61-
var savedInstance = getSavedInstance();
62147

63-
assertThat(
64-
savedInstance.getUpdatedAt()
65-
).isAfter(
66-
entity.getUpdatedAt()
67-
);
148+
var ids = collectToSet(entity, Identified::getId);
149+
var savedInstances = batchFindByIds(entityManager, ids, modelClass);
150+
151+
// Create a map for easy lookup of original entities by ID
152+
var originalByIdMap = entity.stream()
153+
.collect(Collectors.toMap(Identified::getId, e -> e));
154+
155+
for (var savedInstance : savedInstances) {
156+
var originalEntity = originalByIdMap.get(savedInstance.getId());
157+
assertThat(originalEntity).isNotNull();
158+
159+
assertThat(
160+
savedInstance.getUpdatedAt()
161+
).isAfter(
162+
originalEntity.getUpdatedAt()
163+
);
164+
}
68165
}
69166

70167
@SuppressWarnings("unused")
71168
public void hasNotChangedInDatabase() {
72169
entityManager.clear();
73-
var savedInstance = getSavedInstance();
74170

75-
assertThat(savedInstance).isNotNull();
171+
var ids = collectToSet(entity, Identified::getId);
172+
var savedInstances = batchFindByIds(entityManager, ids, modelClass);
173+
174+
// Create a map for easy lookup of original entities by ID
175+
var originalByIdMap = entity.stream()
176+
.collect(Collectors.toMap(Identified::getId, e -> e));
177+
178+
for (var savedInstance : savedInstances) {
179+
var originalEntity = originalByIdMap.get(savedInstance.getId());
76180

77-
assertThat(
78-
savedInstance.getUpdatedAt()
79-
).isEqualTo(
80-
entity.getUpdatedAt()
81-
);
181+
assertThat(savedInstance).isNotNull();
182+
assertThat(originalEntity).isNotNull();
183+
184+
assertThat(
185+
savedInstance.getUpdatedAt()
186+
).isEqualTo(
187+
originalEntity.getUpdatedAt()
188+
);
189+
}
82190
}
83191

84192
@SuppressWarnings("unused")
85193
public void isAbsentInDatabase() {
86194
entityManager.clear();
87-
var savedInstance = getSavedInstance();
88195

89-
assertThat(savedInstance).isNull();
196+
var ids = collectToSet(entity, Identified::getId);
197+
var existenceMap = batchCheckExistence(entityManager, ids, modelClass);
198+
199+
for (var id : ids) {
200+
assertThat(existenceMap.get(id))
201+
.as("Entity with ID %s should be absent from database", id)
202+
.isFalse();
203+
}
90204
}
91205

92206
@SuppressWarnings("unused")
93207
public void isPresentInDatabase() {
94208
entityManager.clear();
95-
var savedInstance = getSavedInstance();
96209

97-
assertThat(savedInstance).isNotNull();
98-
}
210+
var ids = collectToSet(entity, Identified::getId);
211+
var existenceMap = batchCheckExistence(entityManager, ids, modelClass);
99212

100-
private M getSavedInstance() {
101-
return entityManager.find(
102-
modelClass,
103-
entity.getId()
104-
);
213+
for (var id : ids) {
214+
assertThat(existenceMap.get(id))
215+
.as("Entity with ID %s should be present in database", id)
216+
.isTrue();
217+
}
105218
}
106219
}
107220

108221
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
109222
public static final class WriteOperationIdAsserter<ID extends EntityId<?>, M extends Identified<ID>> {
110223
private final EntityManager entityManager;
111-
private final ID id;
224+
private final Collection<ID> id;
112225
private final Class<M> modelClass;
113226

114227
@SuppressWarnings("unused")
115228
public void isAbsentInDatabase() {
116229
entityManager.clear();
117-
var savedInstance = getSavedInstance();
118230

119-
assertThat(savedInstance).isNull();
231+
var existenceMap = batchCheckExistence(entityManager, id, modelClass);
232+
233+
for (var entityId : id) {
234+
assertThat(existenceMap.get(entityId))
235+
.as("Entity with ID %s should be absent from database", entityId)
236+
.isFalse();
237+
}
120238
}
121239

122240
@SuppressWarnings("unused")
123241
public void isPresentInDatabase() {
124242
entityManager.clear();
125-
var savedInstance = getSavedInstance();
126243

127-
assertThat(savedInstance).isNotNull();
128-
}
244+
var existenceMap = batchCheckExistence(entityManager, id, modelClass);
129245

130-
private M getSavedInstance() {
131-
return entityManager.find(
132-
modelClass,
133-
id
134-
);
246+
for (var entityId : id) {
247+
assertThat(existenceMap.get(entityId))
248+
.as("Entity with ID %s should be present in database", entityId)
249+
.isTrue();
250+
}
135251
}
136252
}
137253
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package it.aboutbits.springboot.testing.testdata.base;
2+
3+
import lombok.NonNull;
4+
5+
import java.util.Comparator;
6+
import java.util.HashSet;
7+
import java.util.List;
8+
import java.util.Set;
9+
import java.util.function.Function;
10+
import java.util.function.Predicate;
11+
12+
public abstract class AllTestDataReader<ITEM> {
13+
public ITEM returnFirst() {
14+
return (ITEM) this.returnAll().getFirst();
15+
}
16+
17+
public List<ITEM> returnAll() {
18+
return this.fetch();
19+
}
20+
21+
@SafeVarargs
22+
public final List<ITEM> returnSorted(@NonNull Comparator<ITEM>... comparators) {
23+
if (comparators.length == 0) {
24+
throw new IllegalArgumentException("At least one comparator must be provided");
25+
}
26+
27+
var combinedComparator = comparators[0];
28+
for (var i = 1; i < comparators.length; i++) {
29+
combinedComparator = combinedComparator.thenComparing(comparators[i]);
30+
}
31+
32+
return returnAll().stream().sorted(combinedComparator).toList();
33+
}
34+
35+
@SafeVarargs
36+
@SuppressWarnings("unchecked")
37+
public final <U extends Comparable<? super U>> List<ITEM> returnSorted(@NonNull Function<ITEM, ? extends Comparable<?>>... comparators) {
38+
if (comparators.length == 0) {
39+
throw new IllegalArgumentException("At least one comparator must be provided");
40+
}
41+
42+
var combinedComparator = Comparator.comparing((Function<ITEM, U>) comparators[0]);
43+
for (var i = 1; i < comparators.length; i++) {
44+
combinedComparator = combinedComparator.thenComparing((Function<ITEM, U>) comparators[i]);
45+
}
46+
47+
return returnAll().stream().sorted(combinedComparator).toList();
48+
}
49+
50+
public <U extends Comparable<? super U>> AllAndFiltered<ITEM> returnFiltered(@NonNull Predicate<ITEM> predicate) {
51+
var all = this.returnAll();
52+
return new AllAndFiltered<ITEM>(
53+
all,
54+
all.stream().filter(predicate).toList(),
55+
all.stream().filter(item -> !predicate.test(item)).toList()
56+
);
57+
}
58+
59+
public Set<ITEM> returnSet() {
60+
return new HashSet<>(this.returnAll());
61+
}
62+
63+
protected abstract List<ITEM> fetch();
64+
65+
public record AllAndFiltered<T>(@NonNull List<T> all, @NonNull List<T> filtered, @NonNull List<T> other) {
66+
67+
}
68+
}

0 commit comments

Comments
 (0)