From 788fa0ea4e16bc935dccd33408e658da979c0bf3 Mon Sep 17 00:00:00 2001 From: ZAYTOUN abdellatif <zaytoun.abdellatif@gmail.com> Date: Thu, 13 Mar 2025 19:03:25 +0100 Subject: [PATCH] feat(): update partial for request ( procedure id). --- .../controller/RequestControllerV2.java | 36 ++++++- .../beta/selexpert/exception/ErrorEnum.java | 4 + .../beta/selexpert/mapper/RequestMapper.java | 16 +++ .../request/RequestUpdateRequest.java | 21 ++++ .../selexpert/service/RequestService.java | 33 +++++- .../controller/RequestControllerV2Test.java | 95 ++++++++++++++++- .../selexpert/mapper/RequestMapperTest.java | 72 +++++++++++++ .../request/RequestUpdateRequestTest.java | 84 +++++++++++++++ .../selexpert/service/RequestServiceTest.java | 100 +++++++++++++++++- 9 files changed, 451 insertions(+), 10 deletions(-) create mode 100644 src/main/java/fr/gouv/beta/selexpert/mapper/RequestMapper.java create mode 100644 src/main/java/fr/gouv/beta/selexpert/request/RequestUpdateRequest.java create mode 100644 src/test/java/fr/gouv/beta/selexpert/mapper/RequestMapperTest.java create mode 100644 src/test/java/fr/gouv/beta/selexpert/request/RequestUpdateRequestTest.java diff --git a/src/main/java/fr/gouv/beta/selexpert/controller/RequestControllerV2.java b/src/main/java/fr/gouv/beta/selexpert/controller/RequestControllerV2.java index d1becdf..76916fc 100644 --- a/src/main/java/fr/gouv/beta/selexpert/controller/RequestControllerV2.java +++ b/src/main/java/fr/gouv/beta/selexpert/controller/RequestControllerV2.java @@ -3,6 +3,7 @@ package fr.gouv.beta.selexpert.controller; import fr.gouv.beta.selexpert.exception.HttpConstants; import fr.gouv.beta.selexpert.exception.model.ErrorResponse; import fr.gouv.beta.selexpert.request.RequestExpertUpdateRequest; +import fr.gouv.beta.selexpert.request.RequestUpdateRequest; import fr.gouv.beta.selexpert.service.RequestService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -25,7 +26,7 @@ public class RequestControllerV2 { private final RequestService requestService; @PatchMapping("{requestId}/experts/{expertId}") - @Operation(summary = "Update a request expert", description = "Update a request expert") + @Operation(summary = "Update a request expert", description = "Partially update a request expert") @ApiResponses( value = { @ApiResponse( @@ -49,7 +50,7 @@ public class RequestControllerV2 { content = @Content(schema = @Schema(implementation = ErrorResponse.class))) }) @PreAuthorize( - "hasRole('ADMIN') or (hasAnyRole('MAGISTRATE', 'CLERK', 'ASSISTANT', 'ATTACHE') and @securityService.justiceUserMatchesRequest(#requestId))") + "hasAnyRole('MAGISTRATE', 'CLERK', 'ASSISTANT', 'ATTACHE') and @securityService.justiceUserMatchesRequest(#requestId)") public ResponseEntity<Void> updateRequestExpert( @PathVariable Integer requestId, @PathVariable Integer expertId, @@ -58,4 +59,35 @@ public class RequestControllerV2 { return ResponseEntity.noContent().build(); } + @PatchMapping("{requestId}") + @Operation(summary = "Update a request", description = "Partially update a request") + @ApiResponses( + value = { + @ApiResponse( + responseCode = HttpConstants.HTTP_NO_CONTENT, + description = HttpConstants.HTTP_NO_CONTENT_MESSAGE), + @ApiResponse( + responseCode = HttpConstants.HTTP_BAD_REQUEST, + description = HttpConstants.HTTP_BAD_REQUEST_MESSAGE, + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = HttpConstants.HTTP_FORBIDDEN, + description = HttpConstants.HTTP_FORBIDDEN_MESSAGE, + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = HttpConstants.HTTP_NOT_FOUND, + description = HttpConstants.HTTP_NOT_FOUND_MESSAGE, + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = HttpConstants.HTTP_INTERNAL_SERVER_ERROR, + description = HttpConstants.HTTP_INTERNAL_SERVER_ERROR_MESSAGE, + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + }) + @PreAuthorize( + "hasAnyRole('MAGISTRATE', 'CLERK', 'ASSISTANT', 'ATTACHE') and @securityService.justiceUserMatchesRequest(#requestId)") + public ResponseEntity<Void> updateRequest( + @PathVariable Integer requestId, @RequestBody @Valid RequestUpdateRequest request) { + requestService.updateRequest(requestId, request); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/fr/gouv/beta/selexpert/exception/ErrorEnum.java b/src/main/java/fr/gouv/beta/selexpert/exception/ErrorEnum.java index 5f71724..539412f 100644 --- a/src/main/java/fr/gouv/beta/selexpert/exception/ErrorEnum.java +++ b/src/main/java/fr/gouv/beta/selexpert/exception/ErrorEnum.java @@ -23,6 +23,7 @@ public enum ErrorEnum implements IApiErrorDefinition { REQUEST_EXPERT_NOT_FOUND_BY_USER_ID( "request.expert.not.found", "No Request_Expert found for couple (requestId, userId)= (%s, %s)"), + REQUEST_NOT_FOUND("request.not.found", "No Request found for id %s"), REQUEST_EXPERT_DELETION_NOT_ALLOWED( "request.expert.change.status.not.allowed", "change to DELETED status is not allowed. Its only possible for Request_Experts in QUEUED state"), @@ -32,6 +33,9 @@ public enum ErrorEnum implements IApiErrorDefinition { REQUEST_EXPERT_UPDATE_NOT_ALLOWED( "request.expert.update.not.allowed", "Request_expert update is not allowed. the request is archived or not in status OPEN/REFUSED"), + REQUEST_UPDATE_NOT_ALLOWED( + "request.update.not.allowed", + "Request update is not allowed. the request is archived or not in status OPEN"), EXPERT_NOT_SELECTABLE( "expert.selection.not.allowed", "Expert selection is not allowed for expert ID %s due to invalid status, missing email, or unavailability"), diff --git a/src/main/java/fr/gouv/beta/selexpert/mapper/RequestMapper.java b/src/main/java/fr/gouv/beta/selexpert/mapper/RequestMapper.java new file mode 100644 index 0000000..3360abb --- /dev/null +++ b/src/main/java/fr/gouv/beta/selexpert/mapper/RequestMapper.java @@ -0,0 +1,16 @@ +package fr.gouv.beta.selexpert.mapper; + +import fr.gouv.beta.selexpert.model.Request; +import fr.gouv.beta.selexpert.request.RequestUpdateRequest; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; + +@Mapper( + componentModel = "spring", + uses = {JsonNullableMapper.class}, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface RequestMapper { + + void update(RequestUpdateRequest request, @MappingTarget Request entity); +} diff --git a/src/main/java/fr/gouv/beta/selexpert/request/RequestUpdateRequest.java b/src/main/java/fr/gouv/beta/selexpert/request/RequestUpdateRequest.java new file mode 100644 index 0000000..93add4f --- /dev/null +++ b/src/main/java/fr/gouv/beta/selexpert/request/RequestUpdateRequest.java @@ -0,0 +1,21 @@ +package fr.gouv.beta.selexpert.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.openapitools.jackson.nullable.JsonNullable; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class RequestUpdateRequest { + + @Schema(description = "Request procedure id", implementation = String.class, example = "12/25/54") + @NotBlank + private JsonNullable<String> procedureId; +} diff --git a/src/main/java/fr/gouv/beta/selexpert/service/RequestService.java b/src/main/java/fr/gouv/beta/selexpert/service/RequestService.java index 353c105..e9221c5 100644 --- a/src/main/java/fr/gouv/beta/selexpert/service/RequestService.java +++ b/src/main/java/fr/gouv/beta/selexpert/service/RequestService.java @@ -10,9 +10,12 @@ import fr.gouv.beta.selexpert.exception.EntityNotFoundException; import fr.gouv.beta.selexpert.exception.ErrorEnum; import fr.gouv.beta.selexpert.mapper.JsonNullableMapper; import fr.gouv.beta.selexpert.mapper.RequestExpertMapper; +import fr.gouv.beta.selexpert.mapper.RequestMapper; import fr.gouv.beta.selexpert.model.*; import fr.gouv.beta.selexpert.repository.RequestExpertRepository; +import fr.gouv.beta.selexpert.repository.RequestRepository; import fr.gouv.beta.selexpert.request.RequestExpertUpdateRequest; +import fr.gouv.beta.selexpert.request.RequestUpdateRequest; import fr.gouv.beta.selexpert.util.DateUtils; import fr.gouv.beta.selexpert.util.RequestUtils; import jakarta.transaction.Transactional; @@ -31,8 +34,11 @@ import org.springframework.stereotype.Service; public class RequestService { private final RequestExpertRepository requestExpertRepository; + private final RequestRepository requestRepository; + private final ApplicationEventPublisher eventPublisher; private final RequestExpertMapper requestExpertMapper; + private final RequestMapper requestMapper; private final JsonNullableMapper jsonNullableMapper; private final RequestSchedulerProperties properties; @@ -53,13 +59,13 @@ public class RequestService { requestExpert.getSentOn(), RequestUtils.getWeekdaysToReply(weekdaysToReply, requestExpert)); - if ((requestExpert.getExtraDaysNoRespUpdatedOn() == null && deadlineExtenderThresholdHours <= DateUtils.calculateWorkingHours(LocalDateTime.now(), deadline)) - || requestExpert.getExtraDaysNoRespUpdatedOn() != null && deadlineExtenderThresholdHours - <= DateUtils.calculateWorkingHours( - requestExpert.getExtraDaysNoRespUpdatedOn(), LocalDateTime.now())) { + || requestExpert.getExtraDaysNoRespUpdatedOn() != null + && deadlineExtenderThresholdHours + <= DateUtils.calculateWorkingHours( + requestExpert.getExtraDaysNoRespUpdatedOn(), LocalDateTime.now())) { requestExpert.setExtraDaysNoResponse( Optional.ofNullable(requestExpert.getExtraDaysNoResponse()).orElse(0) + 1); @@ -202,4 +208,23 @@ public class RequestService { new EntityNotFoundException( ErrorEnum.REQUEST_EXPERT_NOT_FOUND_BY_USER_ID, requestId, userId)); } + + public void updateRequest(Integer requestId, RequestUpdateRequest updateRequest) { + + Request request = fetchRequestById(requestId); + + if (request.isArchived() || !RequestStatus.OPEN.equals(request.getStatus())) { + throw new BadRequestException(ErrorEnum.REQUEST_UPDATE_NOT_ALLOWED); + } + + requestMapper.update(updateRequest, request); + + requestRepository.save(request); + } + + Request fetchRequestById(Integer requestId) { + return requestRepository + .findById(requestId) + .orElseThrow(() -> new EntityNotFoundException(ErrorEnum.REQUEST_NOT_FOUND, requestId)); + } } diff --git a/src/test/java/fr/gouv/beta/selexpert/controller/RequestControllerV2Test.java b/src/test/java/fr/gouv/beta/selexpert/controller/RequestControllerV2Test.java index 13e8447..9d22522 100644 --- a/src/test/java/fr/gouv/beta/selexpert/controller/RequestControllerV2Test.java +++ b/src/test/java/fr/gouv/beta/selexpert/controller/RequestControllerV2Test.java @@ -7,7 +7,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -19,6 +18,7 @@ import fr.gouv.beta.selexpert.exception.BadRequestException; import fr.gouv.beta.selexpert.exception.EntityNotFoundException; import fr.gouv.beta.selexpert.exception.ErrorEnum; import fr.gouv.beta.selexpert.request.RequestExpertUpdateRequest; +import fr.gouv.beta.selexpert.request.RequestUpdateRequest; import fr.gouv.beta.selexpert.service.RequestService; import fr.gouv.beta.selexpert.service.SecurityService; import fr.gouv.beta.selexpert.util.TestUtils; @@ -148,4 +148,97 @@ class RequestControllerV2Test { verifyNoInteractions(requestService); } + @Test + @WithMockUser(username = "2", roles = "MAGISTRATE") + void updateRequest_success() throws Exception { + // GIVEN + when(securityService.justiceUserMatchesRequest(eq(1))).thenReturn(true); + + // WHEN + ResultActions result = mockMvc.perform(TestUtils.patch("/api/v2/requests/1", Map.of())); + + // THEN + result.andExpect(status().isNoContent()); + verify(requestService, times(1)).updateRequest(eq(1), any(RequestUpdateRequest.class)); + } + + @Test + @WithMockUser(username = "2", roles = "MAGISTRATE") + void updateRequest_shouldReturnNotFound_whenEntityNotFoundException() throws Exception { + // GIVEN + when(securityService.justiceUserMatchesRequest(eq(1))).thenReturn(true); + + doThrow(new EntityNotFoundException(ErrorEnum.REQUEST_NOT_FOUND, 1)) + .when(requestService) + .updateRequest(eq(1), any(RequestUpdateRequest.class)); + + // WHEN + ResultActions result = mockMvc.perform(TestUtils.patch("/api/v2/requests/1", Map.of())); + + // THEN + result.andExpect(status().isNotFound()); + verify(requestService, times(1)).updateRequest(eq(1), any(RequestUpdateRequest.class)); + } + + @Test + @WithMockUser(username = "2", roles = "MAGISTRATE") + void updateRequest_shouldReturnBadRequest_whenRequestInvalid() throws Exception { + // WHEN + ResultActions result = + mockMvc.perform( + patch("/api/v2/requests/1") + .contentType(MediaType.APPLICATION_JSON) + .content("{ \"procedureId\": \" \"}")); + + // THEN + result + .andExpect(status().isBadRequest()) + .andExpect(content().contentType("application/vnd.error+json")) + .andExpect(jsonPath("$.message").isNotEmpty()); + + verifyNoInteractions(requestService); + } + + @Test + @WithMockUser(roles = {"MAGISTRATE"}) + void updateRequest_shouldReturnBadRequest_whenBadRequestExceptionIsThrown() throws Exception { + // GIVEN + when(securityService.justiceUserMatchesRequest(eq(1))).thenReturn(true); + + Mockito.doThrow(new BadRequestException(ErrorEnum.REQUEST_UPDATE_NOT_ALLOWED)) + .when(requestService) + .updateRequest(eq(1), any(RequestUpdateRequest.class)); + + // WHEN + ResultActions result = mockMvc.perform(TestUtils.patch("/api/v2/requests/1", Map.of())); + + // THEN + result.andExpect(status().isBadRequest()); + verify(requestService, times(1)).updateRequest(eq(1), any(RequestUpdateRequest.class)); + } + + @Test + @WithMockUser(roles = "EXPERT") + void updateRequest_shouldReturnForbidden_whenUserDoesNotHaveRequiredRole() throws Exception { + + // WHEN + ResultActions result = mockMvc.perform(TestUtils.patch("/api/v2/requests/1", Map.of())); + + // THEN + result.andExpect(status().isForbidden()); + verifyNoInteractions(requestService); + } + + @Test + @WithMockUser(roles = "MAGISTRATE") + void updateRequest_shouldReturnForbidden_whenMagistrateNotOwnRequest() throws Exception { + + // WHEN + ResultActions result = mockMvc.perform(TestUtils.patch("/api/v2/requests/1", Map.of())); + + // THEN + result.andExpect(status().isForbidden()); + verify(securityService).justiceUserMatchesRequest(1); + verifyNoInteractions(requestService); + } } diff --git a/src/test/java/fr/gouv/beta/selexpert/mapper/RequestMapperTest.java b/src/test/java/fr/gouv/beta/selexpert/mapper/RequestMapperTest.java new file mode 100644 index 0000000..4faef04 --- /dev/null +++ b/src/test/java/fr/gouv/beta/selexpert/mapper/RequestMapperTest.java @@ -0,0 +1,72 @@ +package fr.gouv.beta.selexpert.mapper; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import fr.gouv.beta.selexpert.model.Request; +import fr.gouv.beta.selexpert.request.RequestUpdateRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; +import org.openapitools.jackson.nullable.JsonNullable; +import org.springframework.test.util.ReflectionTestUtils; + +class RequestMapperTest { + + private final RequestMapper mapper = Mappers.getMapper(RequestMapper.class); + + @BeforeEach + void before() { + ReflectionTestUtils.setField( + mapper, "jsonNullableMapper", Mappers.getMapper(JsonNullableMapper.class)); + } + + @Test + void testUpdateWithNonNullableValues() { + // GIVEN + RequestUpdateRequest updateRequest = new RequestUpdateRequest(); + updateRequest.setProcedureId(JsonNullable.of("145/15/25")); + + Request request = new Request(); + request.setProcedureId(""); + + // WHEN + mapper.update(updateRequest, request); + + // THEN + assertEquals("145/15/25", request.getProcedureId(), "ProcedureId should be updated"); + } + + @Test + void testUpdateWithUndefinedValues() { + // GIVEN + RequestUpdateRequest updateRequest = new RequestUpdateRequest(); + updateRequest.setProcedureId(JsonNullable.undefined()); + + Request request = new Request(); + request.setProcedureId("17/25/69"); + + // WHEN + mapper.update(updateRequest, request); + + // THEN + assertEquals("17/25/69", request.getProcedureId(), "ProcedureId should remain unchanged"); + } + + @Test + void testUpdateWithNullValues() { + // GIVEN + RequestUpdateRequest updateRequest = new RequestUpdateRequest(); + updateRequest.setProcedureId(JsonNullable.of(null)); + + Request request = new Request(); + request.setProcedureId("17/25/69"); + + // WHEN + mapper.update(updateRequest, request); + + // THEN + assertNull(request.getProcedureId(), "ProcedureId should be updated to null"); + } +} diff --git a/src/test/java/fr/gouv/beta/selexpert/request/RequestUpdateRequestTest.java b/src/test/java/fr/gouv/beta/selexpert/request/RequestUpdateRequestTest.java new file mode 100644 index 0000000..f58b257 --- /dev/null +++ b/src/test/java/fr/gouv/beta/selexpert/request/RequestUpdateRequestTest.java @@ -0,0 +1,84 @@ +package fr.gouv.beta.selexpert.request; + +import static org.junit.jupiter.api.Assertions.*; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import java.util.Set; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openapitools.jackson.nullable.JsonNullable; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +class RequestUpdateRequestTest { + + private static Validator validator; + + @BeforeAll + static void setup() { + validator = Validation.buildDefaultValidatorFactory().getValidator(); + } + + @Test + void whenRequestIsValid_thenNoViolations() { + // GIVEN + RequestUpdateRequest request = new RequestUpdateRequest(); + request.setProcedureId(JsonNullable.of("12/58/58")); + + // WHEN + Set<ConstraintViolation<RequestUpdateRequest>> violations = validator.validate(request); + + // THEN + assertTrue(violations.isEmpty(), "There should be no validation errors for null inputs."); + } + + @Test + void whenRequestFieldsAreUndefined_thenNoViolations() { + // GIVEN + RequestUpdateRequest request = new RequestUpdateRequest(); + request.setProcedureId(JsonNullable.undefined()); + + // WHEN + Set<ConstraintViolation<RequestUpdateRequest>> violations = validator.validate(request); + + // THEN + assertTrue(violations.isEmpty(), "There should be no validation errors for undefined inputs."); + } + + @Test + void whenProcedureId_isNull_thenValidationFails() { + // GIVEN + RequestUpdateRequest request = new RequestUpdateRequest(); + request.setProcedureId(JsonNullable.of(null)); + + // WHEN + Set<ConstraintViolation<RequestUpdateRequest>> violations = validator.validate(request); + + // THEN + assertFalse(violations.isEmpty(), "There should be validation errors."); + assertEquals(1, violations.size()); + ConstraintViolation<RequestUpdateRequest> violation = violations.iterator().next(); + assertEquals("must not be blank", violation.getMessage()); + assertEquals("procedureId", violation.getPropertyPath().toString()); + } + + @Test + void whenProcedureId_isBlank_thenValidationFails() { + // GIVEN + RequestUpdateRequest request = new RequestUpdateRequest(); + request.setProcedureId(JsonNullable.of(" ")); + + // WHEN + Set<ConstraintViolation<RequestUpdateRequest>> violations = validator.validate(request); + + // THEN + assertFalse(violations.isEmpty(), "There should be validation errors."); + assertEquals(1, violations.size()); + ConstraintViolation<RequestUpdateRequest> violation = violations.iterator().next(); + assertEquals("must not be blank", violation.getMessage()); + assertEquals("procedureId", violation.getPropertyPath().toString()); + } +} diff --git a/src/test/java/fr/gouv/beta/selexpert/service/RequestServiceTest.java b/src/test/java/fr/gouv/beta/selexpert/service/RequestServiceTest.java index 6d11331..cb4a868 100644 --- a/src/test/java/fr/gouv/beta/selexpert/service/RequestServiceTest.java +++ b/src/test/java/fr/gouv/beta/selexpert/service/RequestServiceTest.java @@ -1,6 +1,7 @@ package fr.gouv.beta.selexpert.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -14,25 +15,26 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import fr.gouv.beta.selexpert.config.properties.RequestSchedulerProperties; import fr.gouv.beta.selexpert.event.DeadlineExtendedEvent; import fr.gouv.beta.selexpert.exception.BadRequestException; import fr.gouv.beta.selexpert.exception.EntityNotFoundException; import fr.gouv.beta.selexpert.exception.ErrorEnum; import fr.gouv.beta.selexpert.mapper.JsonNullableMapper; import fr.gouv.beta.selexpert.mapper.RequestExpertMapper; +import fr.gouv.beta.selexpert.mapper.RequestMapper; import fr.gouv.beta.selexpert.model.Request; import fr.gouv.beta.selexpert.model.RequestExpert; import fr.gouv.beta.selexpert.model.RequestExpertStatus; import fr.gouv.beta.selexpert.model.RequestStatus; import fr.gouv.beta.selexpert.model.Role; import fr.gouv.beta.selexpert.repository.RequestExpertRepository; +import fr.gouv.beta.selexpert.repository.RequestRepository; import fr.gouv.beta.selexpert.request.RequestExpertUpdateRequest; +import fr.gouv.beta.selexpert.request.RequestUpdateRequest; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; import java.util.Optional; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -48,10 +50,12 @@ import org.springframework.test.util.ReflectionTestUtils; class RequestServiceTest { @Mock private RequestExpertRepository requestExpertRepository; + @Mock private RequestRepository requestRepository; @Mock private ApplicationEventPublisher eventPublisher; @Mock private RequestExpertMapper requestExpertMapper; + @Mock private RequestMapper requestMapper; @Mock private JsonNullableMapper jsonNullableMapper; - @Mock private RequestSchedulerProperties properties; + @InjectMocks private RequestService requestService; @BeforeEach @@ -297,6 +301,37 @@ class RequestServiceTest { userExpertId)); } + @Test + void fetchRequestById_whenRequestExists_shouldReturnRequest() { + // GIVEN + Integer requestId = 1; + Request mockRequest = new Request(); + mockRequest.setId(requestId); + + when(requestRepository.findById(requestId)).thenReturn(Optional.of(mockRequest)); + + // WHEN + Request result = requestService.fetchRequestById(requestId); + + // THEN + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(requestId); + } + + @Test + void fetchRequestById_whenRequestDoesNotExist_shouldThrowEntityNotFoundException() { + // GIVEN + Integer requestId = 1; + + when(requestRepository.findById(requestId)).thenReturn(Optional.empty()); + + // WHEN & THEN + assertThatThrownBy(() -> requestService.fetchRequestById(requestId)) + .isInstanceOf(EntityNotFoundException.class) + .hasMessageContaining( + String.format(ErrorEnum.REQUEST_NOT_FOUND.getMessageTemplate(), requestId)); + } + @Test void shouldReturnTrueWhenRequestHasOnlyOneActiveExpert() { // GIVEN @@ -345,4 +380,63 @@ class RequestServiceTest { assertFalse(result); } + @Test + void updateRequest_whenRequestIsValid_shouldUpdateRequest() { + // GIVEN + Integer requestId = 1; + RequestUpdateRequest updateRequest = new RequestUpdateRequest(); + Request request = new Request(); + request.setId(requestId); + request.setArchived(false); + request.setStatus(RequestStatus.OPEN); + + when(requestRepository.findById(requestId)).thenReturn(Optional.of(request)); + + // WHEN + requestService.updateRequest(requestId, updateRequest); + + // THEN + verify(requestMapper).update(updateRequest, request); + verify(requestRepository).save(request); + } + + @Test + void updateRequest_whenRequestIsArchived_shouldThrowBadRequestException() { + // GIVEN + Integer requestId = 1; + RequestUpdateRequest updateRequest = new RequestUpdateRequest(); + Request request = new Request(); + request.setId(requestId); + request.setArchived(true); // Request is archived + request.setStatus(RequestStatus.OPEN); + + when(requestRepository.findById(requestId)).thenReturn(Optional.of(request)); + + // WHEN & THEN + assertThatThrownBy(() -> requestService.updateRequest(requestId, updateRequest)) + .isInstanceOf(BadRequestException.class) + .hasMessageContaining(ErrorEnum.REQUEST_UPDATE_NOT_ALLOWED.getMessageTemplate()); + verifyNoInteractions(requestMapper); + verify(requestRepository, never()).save(any()); + } + + @Test + void updateRequest_whenRequestStatusIsNotOpen_shouldThrowBadRequestException() { + // GIVEN + Integer requestId = 1; + RequestUpdateRequest updateRequest = new RequestUpdateRequest(); + Request request = new Request(); + request.setId(requestId); + request.setArchived(false); + request.setStatus(RequestStatus.ACCEPTED); // Invalid status for update + + when(requestRepository.findById(requestId)).thenReturn(Optional.of(request)); + + // WHEN & THEN + assertThatThrownBy(() -> requestService.updateRequest(requestId, updateRequest)) + .isInstanceOf(BadRequestException.class) + .hasMessageContaining(ErrorEnum.REQUEST_UPDATE_NOT_ALLOWED.getMessageTemplate()); + verifyNoInteractions(requestMapper); + verify(requestRepository, never()).save(any()); + } } -- GitLab