Skip to content

Commit 4d1c080

Browse files
committed
test: add coverage for race condition and null ID edge cases (#248)
- Add test for DataIntegrityViolationException during concurrent creates - Add test for PUT with null body ID (validates ID from path) - Refactor URI construction to use ServletUriComponentsBuilder
1 parent aeece8e commit 4d1c080

File tree

3 files changed

+63
-4
lines changed

3 files changed

+63
-4
lines changed

src/main/java/ar/com/nanotaboada/java/samples/spring/boot/controllers/PlayersController.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import org.springframework.web.bind.annotation.PutMapping;
1515
import org.springframework.web.bind.annotation.RequestBody;
1616
import org.springframework.web.bind.annotation.RestController;
17-
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
17+
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
1818

1919
import ar.com.nanotaboada.java.samples.spring.boot.models.PlayerDTO;
2020
import ar.com.nanotaboada.java.samples.spring.boot.services.PlayersService;
@@ -97,9 +97,10 @@ public ResponseEntity<Void> post(@RequestBody @Valid PlayerDTO playerDTO) {
9797
if (createdPlayer == null) {
9898
return ResponseEntity.status(HttpStatus.CONFLICT).build();
9999
}
100-
URI location = MvcUriComponentsBuilder
101-
.fromMethodCall(MvcUriComponentsBuilder.on(PlayersController.class).getById(createdPlayer.getId()))
102-
.build()
100+
URI location = ServletUriComponentsBuilder
101+
.fromCurrentRequest()
102+
.path("/{id}")
103+
.buildAndExpand(createdPlayer.getId())
103104
.toUri();
104105
return ResponseEntity.status(HttpStatus.CREATED)
105106
.header(LOCATION, location.toString())

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,36 @@ void put_idMismatch_returnsBadRequest()
482482
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
483483
}
484484

485+
/**
486+
* Given the body ID is null (ID only in path)
487+
* When PUT /players/{id} is called
488+
* Then the ID is set from the path and the update proceeds normally
489+
*/
490+
@Test
491+
void put_nullBodyId_setsIdFromPath()
492+
throws Exception {
493+
// Arrange
494+
PlayerDTO playerDTO = PlayerDTOFakes.createOneValid();
495+
playerDTO.setId(null); // Body has null ID
496+
Long pathId = 1L;
497+
String body = objectMapper.writeValueAsString(playerDTO);
498+
Mockito
499+
.when(playersServiceMock.update(any(PlayerDTO.class)))
500+
.thenReturn(true);
501+
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
502+
.put(PATH + "/{id}", pathId)
503+
.content(body)
504+
.contentType(MediaType.APPLICATION_JSON);
505+
// Act
506+
MockHttpServletResponse response = application
507+
.perform(request)
508+
.andReturn()
509+
.getResponse();
510+
// Assert
511+
verify(playersServiceMock, times(1)).update(any(PlayerDTO.class));
512+
assertThat(response.getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value());
513+
}
514+
485515
/*
486516
* -------------------------------------------------------------------------
487517
* HTTP DELETE

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import ar.com.nanotaboada.java.samples.spring.boot.services.PlayersService;
2626
import ar.com.nanotaboada.java.samples.spring.boot.test.PlayerDTOFakes;
2727
import ar.com.nanotaboada.java.samples.spring.boot.test.PlayerFakes;
28+
import org.springframework.dao.DataIntegrityViolationException;
2829

2930
@DisplayName("CRUD Operations on Service")
3031
@ExtendWith(MockitoExtension.class)
@@ -98,6 +99,33 @@ void create_squadNumberExists_returnsNull() {
9899
assertThat(result).isNull();
99100
}
100101

102+
/**
103+
* Given a race condition occurs where another request creates the same squad number
104+
* When create() is called and save() throws DataIntegrityViolationException
105+
* Then null is returned (conflict detected via exception)
106+
*/
107+
@Test
108+
void create_raceConditionOnSave_returnsNull() {
109+
// Arrange
110+
PlayerDTO playerDTO = PlayerDTOFakes.createOneValid();
111+
Player player = PlayerFakes.createOneValid();
112+
Mockito
113+
.when(playersRepositoryMock.findBySquadNumber(playerDTO.getSquadNumber()))
114+
.thenReturn(Optional.empty()); // No conflict initially
115+
Mockito
116+
.when(modelMapperMock.map(playerDTO, Player.class))
117+
.thenReturn(player);
118+
Mockito
119+
.when(playersRepositoryMock.save(any(Player.class)))
120+
.thenThrow(new DataIntegrityViolationException("Unique constraint violation"));
121+
// Act
122+
PlayerDTO result = playersService.create(playerDTO);
123+
// Assert
124+
verify(playersRepositoryMock, times(1)).findBySquadNumber(playerDTO.getSquadNumber());
125+
verify(playersRepositoryMock, times(1)).save(any(Player.class));
126+
assertThat(result).isNull();
127+
}
128+
101129
/*
102130
* -----------------------------------------------------------------------------------------------------------------------
103131
* Retrieve

0 commit comments

Comments
 (0)