Skip to content

Commit e08af5f

Browse files
committed
fix(tests): correct data inconsistencies and improve test configuration (#248)
Data Corrections: - Fix Di María position abbreviation (LW → RW to match "Right Winger") - Correct team name typos (Villareal → Villarreal, Nottingham Forrest → Nottingham Forest) - Apply corrections consistently across PlayerDTOFakes, PlayerFakes, dml.sql, and production database Configuration Improvements: - Add explicit Hibernate dialect (spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect) to test application.properties for consistency with production Documentation: - Update PlayersService class-level JavaDoc to accurately document @CacheEvict(allEntries=true) cache strategy - Add explanation of why allEntries=true is needed (retrieveAll() caches full list under single key) Addresses code review feedback ensuring test data accuracy and environment consistency.
1 parent ef72cf6 commit e08af5f

File tree

9 files changed

+57
-24
lines changed

9 files changed

+57
-24
lines changed

AGENTS.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ open target/site/jacoco/index.html
4848
./mvnw test -Dtest=PlayersControllerTests
4949

5050
# Run specific test method
51-
./mvnw test -Dtest=PlayersControllerTests#givenGetAll_whenServiceRetrieveAllReturnsPlayers_thenResponseIsOkAndResultIsPlayers
51+
./mvnw test -Dtest=PlayersControllerTests#getAll_playersExist_returnsOkWithAllPlayers
5252

5353
# Run tests without rebuilding
5454
./mvnw surefire:test
@@ -99,7 +99,7 @@ java -jar target/java.samples.spring.boot-*.jar
9999

100100
### Database Management
101101

102-
This project uses **H2 in-memory database for tests** and **SQLite for runtime**.
102+
This project uses **SQLite in-memory database for tests** and **SQLite for runtime**.
103103

104104
**Runtime (SQLite)**:
105105

@@ -115,9 +115,9 @@ rm storage/players-sqlite3.db
115115
# Database location: storage/players-sqlite3.db
116116
```
117117

118-
**Tests (H2)**:
118+
**Tests (SQLite)**:
119119

120-
- In-memory database per test run
120+
- In-memory database per test run (jdbc:sqlite::memory:)
121121
- Automatically cleared after each test
122122
- Configuration in `src/test/resources/application.properties`
123123

@@ -281,7 +281,7 @@ rm storage/players-sqlite3.db
281281
./mvnw test -X
282282

283283
# Run single test for debugging
284-
./mvnw test -Dtest=PlayersControllerTests#givenGetAll_whenServiceRetrieveAllReturnsPlayers_thenResponseIsOkAndResultIsPlayers -X
284+
./mvnw test -Dtest=PlayersControllerTests#getAll_playersExist_returnsOkWithAllPlayers -X
285285
```
286286

287287
### Maven wrapper issues

src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/PlayerDTO.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
99

1010
import jakarta.validation.constraints.NotBlank;
11+
import jakarta.validation.constraints.NotNull;
1112
import jakarta.validation.constraints.Past;
1213
import jakarta.validation.constraints.Positive;
1314
import lombok.Data;
@@ -25,8 +26,8 @@
2526
* <ul>
2627
* <li><b>firstName:</b> Required (not blank)</li>
2728
* <li><b>lastName:</b> Required (not blank)</li>
28-
* <li><b>dateOfBirth:</b> Must be a past date</li>
29-
* <li><b>squadNumber:</b> Must be a positive integer</li>
29+
* <li><b>dateOfBirth:</b> Required (not null) and must be a past date</li>
30+
* <li><b>squadNumber:</b> Required (not null) and must be a positive integer</li>
3031
* <li><b>position:</b> Required (not blank)</li>
3132
* <li><b>team:</b> Required (not blank)</li>
3233
* </ul>
@@ -38,6 +39,7 @@
3839
*
3940
* @see Player
4041
* @see jakarta.validation.constraints.NotBlank
42+
* @see jakarta.validation.constraints.NotNull
4143
* @see jakarta.validation.constraints.Past
4244
* @see jakarta.validation.constraints.Positive
4345
* @since 4.0.2025
@@ -50,10 +52,12 @@ public class PlayerDTO {
5052
private String middleName;
5153
@NotBlank
5254
private String lastName;
55+
@NotNull
5356
@Past
5457
@JsonDeserialize(using = LocalDateDeserializer.class)
5558
@JsonSerialize(using = LocalDateSerializer.class)
5659
private LocalDate dateOfBirth;
60+
@NotNull
5761
@Positive
5862
private Integer squadNumber;
5963
@NotBlank

src/main/java/ar/com/nanotaboada/java/samples/spring/boot/services/PlayersService.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
import org.modelmapper.ModelMapper;
77
import org.springframework.cache.annotation.CacheEvict;
8-
import org.springframework.cache.annotation.CachePut;
98
import org.springframework.cache.annotation.Cacheable;
109
import org.springframework.stereotype.Service;
10+
import org.springframework.transaction.annotation.Transactional;
1111

1212
import ar.com.nanotaboada.java.samples.spring.boot.models.Player;
1313
import ar.com.nanotaboada.java.samples.spring.boot.models.PlayerDTO;
@@ -31,10 +31,16 @@
3131
* <h3>Cache Strategy:</h3>
3232
* <ul>
3333
* <li><b>@Cacheable:</b> Retrieval operations (read-through cache)</li>
34-
* <li><b>@CachePut:</b> Create operations (update cache)</li>
35-
* <li><b>@CacheEvict:</b> Update/Delete operations (invalidate cache)</li>
34+
* <li><b>@CacheEvict(allEntries=true):</b> Mutating operations (create/update/delete) - invalidates entire cache to maintain
35+
* consistency</li>
3636
* </ul>
3737
*
38+
* <p>
39+
* <b>Why invalidate all entries?</b> The {@code retrieveAll()} method caches the full player list under a single key.
40+
* Individual cache evictions would leave this list stale. Using {@code allEntries=true} ensures both individual
41+
* player caches and the list cache stay synchronized after any data modification.
42+
* </p>
43+
*
3844
* @see PlayersRepository
3945
* @see PlayerDTO
4046
* @see Player
@@ -67,9 +73,10 @@ public class PlayersService {
6773
*
6874
* @param playerDTO the player data to create (must not be null)
6975
* @return the created player with auto-generated ID, or null if squad number already exists
70-
* @see org.springframework.cache.annotation.CachePut
76+
* @see org.springframework.cache.annotation.CacheEvict
7177
*/
72-
@CachePut(value = "players", key = "#result.id", unless = "#result == null")
78+
@Transactional
79+
@CacheEvict(value = "players", allEntries = true)
7380
public PlayerDTO create(PlayerDTO playerDTO) {
7481
// Check if squad number already exists
7582
if (playersRepository.findBySquadNumber(playerDTO.getSquadNumber()).isPresent()) {
@@ -175,9 +182,10 @@ public PlayerDTO searchBySquadNumber(Integer squadNumber) {
175182
*
176183
* @param playerDTO the player data to update (must include a valid ID)
177184
* @return true if the player was updated successfully, false if not found
178-
* @see org.springframework.cache.annotation.CachePut
185+
* @see org.springframework.cache.annotation.CacheEvict
179186
*/
180-
@CachePut(value = "players", key = "#playerDTO.id")
187+
@Transactional
188+
@CacheEvict(value = "players", allEntries = true)
181189
public boolean update(PlayerDTO playerDTO) {
182190
if (playerDTO.getId() != null && playersRepository.existsById(playerDTO.getId())) {
183191
Player player = mapFrom(playerDTO);
@@ -205,7 +213,8 @@ public boolean update(PlayerDTO playerDTO) {
205213
* @return true if the player was deleted successfully, false if not found
206214
* @see org.springframework.cache.annotation.CacheEvict
207215
*/
208-
@CacheEvict(value = "players", key = "#id")
216+
@Transactional
217+
@CacheEvict(value = "players", allEntries = true)
209218
public boolean delete(Long id) {
210219
if (playersRepository.existsById(id)) {
211220
playersRepository.deleteById(id);

src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/PlayerDTOFakes.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public static java.util.List<PlayerDTO> createAll() {
140140
createPlayerDTOWithId(5L, "Nicolás", "Alejandro", "Tagliafico", LocalDate.of(1992, 8, 31), 3,
141141
"Left-Back", "LB", "Olympique Lyon", "Ligue 1", true),
142142
createPlayerDTOWithId(6L, "Ángel", "Fabián", "Di María", LocalDate.of(1988, 2, 14), 11, "Right Winger",
143-
"LW", "SL Benfica", "Liga Portugal", true),
143+
"RW", "SL Benfica", "Liga Portugal", true),
144144
createPlayerDTOWithId(7L, "Rodrigo", "Javier", "de Paul", LocalDate.of(1994, 5, 24), 7,
145145
"Central Midfield", "CM", "Atlético Madrid", "La Liga", true),
146146
createPlayerDTOWithId(8L, "Enzo", "Jeremías", "Fernández", LocalDate.of(2001, 1, 17), 24,
@@ -157,9 +157,9 @@ public static java.util.List<PlayerDTO> createAll() {
157157
createPlayerDTOWithId(13L, "Gerónimo", null, "Rulli", LocalDate.of(1992, 5, 20), 12, "Goalkeeper", "GK",
158158
"Ajax Amsterdam", "Eredivisie", false),
159159
createPlayerDTOWithId(14L, "Juan", "Marcos", "Foyth", LocalDate.of(1998, 1, 12), 2, "Right-Back", "RB",
160-
"Villareal", "La Liga", false),
160+
"Villarreal", "La Liga", false),
161161
createPlayerDTOWithId(15L, "Gonzalo", "Ariel", "Montiel", LocalDate.of(1997, 1, 1), 4, "Right-Back",
162-
"RB", "Nottingham Forrest", "Premier League", false),
162+
"RB", "Nottingham Forest", "Premier League", false),
163163
createPlayerDTOWithId(16L, "Germán", "Alejo", "Pezzella", LocalDate.of(1991, 6, 27), 6, "Centre-Back",
164164
"CB", "Real Betis Balompié", "La Liga", false),
165165
createPlayerDTOWithId(17L, "Marcos", "Javier", "Acuña", LocalDate.of(1991, 10, 28), 8, "Left-Back",

src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/PlayerFakes.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public static java.util.List<Player> createAll() {
143143
createPlayerWithId(5L, "Nicolás", "Alejandro", "Tagliafico", LocalDate.of(1992, 8, 31), 3, "Left-Back",
144144
"LB", "Olympique Lyon", "Ligue 1", true),
145145
createPlayerWithId(6L, "Ángel", "Fabián", "Di María", LocalDate.of(1988, 2, 14), 11, "Right Winger",
146-
"LW", "SL Benfica", "Liga Portugal", true),
146+
"RW", "SL Benfica", "Liga Portugal", true),
147147
createPlayerWithId(7L, "Rodrigo", "Javier", "de Paul", LocalDate.of(1994, 5, 24), 7, "Central Midfield",
148148
"CM", "Atlético Madrid", "La Liga", true),
149149
createPlayerWithId(8L, "Enzo", "Jeremías", "Fernández", LocalDate.of(2001, 1, 17), 24,
@@ -160,9 +160,9 @@ public static java.util.List<Player> createAll() {
160160
createPlayerWithId(13L, "Gerónimo", null, "Rulli", LocalDate.of(1992, 5, 20), 12, "Goalkeeper", "GK",
161161
"Ajax Amsterdam", "Eredivisie", false),
162162
createPlayerWithId(14L, "Juan", "Marcos", "Foyth", LocalDate.of(1998, 1, 12), 2, "Right-Back", "RB",
163-
"Villareal", "La Liga", false),
163+
"Villarreal", "La Liga", false),
164164
createPlayerWithId(15L, "Gonzalo", "Ariel", "Montiel", LocalDate.of(1997, 1, 1), 4, "Right-Back", "RB",
165-
"Nottingham Forrest", "Premier League", false),
165+
"Nottingham Forest", "Premier League", false),
166166
createPlayerWithId(16L, "Germán", "Alejo", "Pezzella", LocalDate.of(1991, 6, 27), 6, "Centre-Back",
167167
"CB", "Real Betis Balompié", "La Liga", false),
168168
createPlayerWithId(17L, "Marcos", "Javier", "Acuña", LocalDate.of(1991, 10, 28), 8, "Left-Back", "LB",

src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,25 @@ void update_playerNotFound_returnsFalse() {
325325
assertThat(result).isFalse();
326326
}
327327

328+
/**
329+
* Given a PlayerDTO has null ID
330+
* When update() is called
331+
* Then false is returned without checking repository or saving
332+
*/
333+
@Test
334+
void update_nullId_returnsFalse() {
335+
// Arrange
336+
PlayerDTO playerDTO = PlayerDTOFakes.createOneValid();
337+
playerDTO.setId(null);
338+
// Act
339+
boolean result = playersService.update(playerDTO);
340+
// Assert
341+
verify(playersRepositoryMock, never()).existsById(any());
342+
verify(playersRepositoryMock, never()).save(any(Player.class));
343+
verify(modelMapperMock, never()).map(any(), any());
344+
assertThat(result).isFalse();
345+
}
346+
328347
/*
329348
* -----------------------------------------------------------------------------------------------------------------------
330349
* Delete

src/test/resources/application.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# SQLite In-Memory Database Configuration (Test Only)
77
spring.datasource.url=jdbc:sqlite::memory:
88
spring.datasource.driver-class-name=org.sqlite.JDBC
9+
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
910
spring.jpa.hibernate.ddl-auto=none
1011
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
1112
spring.sql.init.mode=always

src/test/resources/dml.sql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ INSERT INTO players (id, firstName, middleName, lastName, dateOfBirth, squadNumb
1111
(3, 'Cristian', 'Gabriel', 'Romero', '1998-04-27T00:00:00.000Z', 13, 'Centre-Back', 'CB', 'Tottenham Hotspur', 'Premier League', 1),
1212
(4, 'Nicolás', 'Hernán Gonzalo', 'Otamendi', '1988-02-12T00:00:00.000Z', 19, 'Centre-Back', 'CB', 'SL Benfica', 'Liga Portugal', 1),
1313
(5, 'Nicolás', 'Alejandro', 'Tagliafico', '1992-08-31T00:00:00.000Z', 3, 'Left-Back', 'LB', 'Olympique Lyon', 'Ligue 1', 1),
14-
(6, 'Ángel', 'Fabián', 'Di María', '1988-02-14T00:00:00.000Z', 11, 'Right Winger', 'LW', 'SL Benfica', 'Liga Portugal', 1),
14+
(6, 'Ángel', 'Fabián', 'Di María', '1988-02-14T00:00:00.000Z', 11, 'Right Winger', 'RW', 'SL Benfica', 'Liga Portugal', 1),
1515
(7, 'Rodrigo', 'Javier', 'de Paul', '1994-05-24T00:00:00.000Z', 7, 'Central Midfield', 'CM', 'Atlético Madrid', 'La Liga', 1),
1616
(8, 'Enzo', 'Jeremías', 'Fernández', '2001-01-17T00:00:00.000Z', 24, 'Central Midfield', 'CM', 'Chelsea FC', 'Premier League', 1),
1717
(9, 'Alexis', NULL, 'Mac Allister', '1998-12-24T00:00:00.000Z', 20, 'Central Midfield', 'CM', 'Liverpool FC', 'Premier League', 1),
@@ -22,8 +22,8 @@ INSERT INTO players (id, firstName, middleName, lastName, dateOfBirth, squadNumb
2222
INSERT INTO players (id, firstName, middleName, lastName, dateOfBirth, squadNumber, position, abbrPosition, team, league, starting11) VALUES
2323
(12, 'Franco', 'Daniel', 'Armani', '1986-10-16T00:00:00.000Z', 1, 'Goalkeeper', 'GK', 'River Plate', 'Copa de la Liga', 0),
2424
(13, 'Gerónimo', NULL, 'Rulli', '1992-05-20T00:00:00.000Z', 12, 'Goalkeeper', 'GK', 'Ajax Amsterdam', 'Eredivisie', 0),
25-
(14, 'Juan', 'Marcos', 'Foyth', '1998-01-12T00:00:00.000Z', 2, 'Right-Back', 'RB', 'Villareal', 'La Liga', 0),
26-
(15, 'Gonzalo', 'Ariel', 'Montiel', '1997-01-01T00:00:00.000Z', 4, 'Right-Back', 'RB', 'Nottingham Forrest', 'Premier League', 0),
25+
(14, 'Juan', 'Marcos', 'Foyth', '1998-01-12T00:00:00.000Z', 2, 'Right-Back', 'RB', 'Villarreal', 'La Liga', 0),
26+
(15, 'Gonzalo', 'Ariel', 'Montiel', '1997-01-01T00:00:00.000Z', 4, 'Right-Back', 'RB', 'Nottingham Forest', 'Premier League', 0),
2727
(16, 'Germán', 'Alejo', 'Pezzella', '1991-06-27T00:00:00.000Z', 6, 'Centre-Back', 'CB', 'Real Betis Balompié', 'La Liga', 0),
2828
(17, 'Marcos', 'Javier', 'Acuña', '1991-10-28T00:00:00.000Z', 8, 'Left-Back', 'LB', 'Sevilla FC', 'La Liga', 0),
2929
(18, 'Lisandro', NULL, 'Martínez', '1998-01-18T00:00:00.000Z', 25, 'Centre-Back', 'CB', 'Manchester United', 'Premier League', 0),

storage/players-sqlite3.db

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)