Skip to content

Commit 534eea4

Browse files
nanotaboadaclaude
andcommitted
fix(api): mark PlayerDTO.id read-only, guard null squadNumber (#268)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d5da8c1 commit 534eea4

5 files changed

Lines changed: 23 additions & 8 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
import java.time.LocalDate;
44

5+
import com.fasterxml.jackson.annotation.JsonProperty;
56
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
67
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
78
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
89
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
910

1011
import java.util.UUID;
1112

13+
import io.swagger.v3.oas.annotations.media.Schema;
1214
import jakarta.validation.constraints.NotBlank;
1315
import jakarta.validation.constraints.NotNull;
1416
import jakarta.validation.constraints.Past;
@@ -48,6 +50,8 @@
4850
*/
4951
@Data
5052
public class PlayerDTO {
53+
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
54+
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
5155
private UUID id;
5256
@NotBlank
5357
private String firstName;

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ public PlayerDTO create(PlayerDTO playerDTO) {
111111
*
112112
* @return a list of all players (empty list if none found)
113113
*/
114+
@Transactional(readOnly = true)
114115
@Cacheable(value = "players")
115116
public List<PlayerDTO> retrieveAll() {
116117
return playersRepository.findAll()
@@ -128,6 +129,7 @@ public List<PlayerDTO> retrieveAll() {
128129
* @param id the UUID primary key (must not be null)
129130
* @return the player DTO if found, null otherwise
130131
*/
132+
@Transactional(readOnly = true)
131133
@Cacheable(value = "players", key = "#id", unless = "#result == null")
132134
public PlayerDTO retrieveById(UUID id) {
133135
return playersRepository.findById(id)
@@ -144,6 +146,7 @@ public PlayerDTO retrieveById(UUID id) {
144146
* @param squadNumber the squad number to retrieve (jersey number, typically 1-99)
145147
* @return the player DTO if found, null otherwise
146148
*/
149+
@Transactional(readOnly = true)
147150
@Cacheable(value = "players", key = "'squad-' + #squadNumber", unless = "#result == null")
148151
public PlayerDTO retrieveBySquadNumber(Integer squadNumber) {
149152
return playersRepository.findBySquadNumber(squadNumber)
@@ -163,6 +166,7 @@ public PlayerDTO retrieveBySquadNumber(Integer squadNumber) {
163166
* @param league the league name to search for (must not be null or blank)
164167
* @return a list of matching players (empty list if none found)
165168
*/
169+
@Transactional(readOnly = true)
166170
public List<PlayerDTO> searchByLeague(String league) {
167171
return playersRepository.findByLeagueContainingIgnoreCase(league)
168172
.stream()
@@ -192,6 +196,11 @@ public List<PlayerDTO> searchByLeague(String league) {
192196
public boolean update(Integer squadNumber, PlayerDTO playerDTO) {
193197
log.debug("Updating player with squad number: {}", squadNumber);
194198

199+
if (squadNumber == null) {
200+
log.warn("Cannot update player - squad number is null");
201+
return false;
202+
}
203+
195204
return playersRepository.findBySquadNumber(squadNumber)
196205
.map(existing -> {
197206
Player player = mapFrom(playerDTO);

src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/controllers/PlayersControllerTests.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,8 @@ void givenPlayersExist_whenGetAll_thenReturnsOkWithAllPlayers()
186186
then(response.getContentType()).contains("application/json");
187187
verify(playersServiceMock, times(1)).retrieveAll();
188188
then(response.getStatus()).isEqualTo(HttpStatus.OK.value());
189-
then(actual).usingRecursiveComparison().isEqualTo(expected);
189+
expected.forEach(player -> then(content).contains(player.getId().toString()));
190+
then(actual).usingRecursiveComparison().ignoringFields("id").isEqualTo(expected);
190191
}
191192

192193
/**
@@ -216,7 +217,8 @@ void givenPlayerExists_whenGetById_thenReturnsOk()
216217
then(response.getContentType()).contains("application/json");
217218
verify(playersServiceMock, times(1)).retrieveById(id);
218219
then(response.getStatus()).isEqualTo(HttpStatus.OK.value());
219-
then(actual).usingRecursiveComparison().isEqualTo(expected);
220+
then(content).contains(expected.getId().toString());
221+
then(actual).usingRecursiveComparison().ignoringFields("id").isEqualTo(expected);
220222
}
221223

222224
/**
@@ -274,7 +276,8 @@ void givenPlayerExists_whenGetBySquadNumber_thenReturnsOk()
274276
then(response.getContentType()).contains("application/json");
275277
verify(playersServiceMock, times(1)).retrieveBySquadNumber(squadNumber);
276278
then(response.getStatus()).isEqualTo(HttpStatus.OK.value());
277-
then(actual).usingRecursiveComparison().isEqualTo(expected);
279+
then(content).contains(expected.getId().toString());
280+
then(actual).usingRecursiveComparison().ignoringFields("id").isEqualTo(expected);
278281
then(actual.getSquadNumber()).isEqualTo(squadNumber);
279282
}
280283

@@ -333,7 +336,8 @@ void givenPlayersExist_whenSearchByLeague_thenReturnsOk()
333336
then(response.getContentType()).contains("application/json");
334337
verify(playersServiceMock, times(1)).searchByLeague(any());
335338
then(response.getStatus()).isEqualTo(HttpStatus.OK.value());
336-
then(actual).usingRecursiveComparison().isEqualTo(expected);
339+
expected.forEach(player -> then(content).contains(player.getId().toString()));
340+
then(actual).usingRecursiveComparison().ignoringFields("id").isEqualTo(expected);
337341
}
338342

339343
/**

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,10 @@ void givenPlayerDoesNotExist_whenUpdate_thenReturnsFalse() {
380380
void givenNullSquadNumber_whenUpdate_thenReturnsFalse() {
381381
// Given
382382
PlayerDTO dto = PlayerDTOFakes.createOneValid();
383-
Mockito
384-
.when(playersRepositoryMock.findBySquadNumber(null))
385-
.thenReturn(Optional.empty());
386383
// When
387384
boolean actual = playersService.update(null, dto);
388385
// Then
386+
verify(playersRepositoryMock, never()).findBySquadNumber(any());
389387
verify(playersRepositoryMock, never()).save(any(Player.class));
390388
verify(modelMapperMock, never()).map(any(), any());
391389
then(actual).isFalse();

src/test/resources/ddl.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
DROP TABLE IF EXISTS players;
66

77
CREATE TABLE players (
8-
id VARCHAR(36) PRIMARY KEY,
8+
id VARCHAR(36) NOT NULL PRIMARY KEY,
99
squadNumber INTEGER NOT NULL UNIQUE,
1010
firstName TEXT NOT NULL,
1111
middleName TEXT,

0 commit comments

Comments
 (0)