From ea4fc96a93147cbfb85a4eff5d4da2739b4b3701 Mon Sep 17 00:00:00 2001 From: Russell Dodd Date: Thu, 5 Sep 2024 14:17:07 +0100 Subject: [PATCH] PO-756: Add draft account DELETE endpoint --- ...DraftAccountControllerIntegrationTest.java | 18 ++++++++ .../controllers/DraftAccountController.java | 24 ++++++++++ .../service/DraftAccountServiceInterface.java | 15 ------ .../service/opal/DraftAccountService.java | 20 ++++++-- .../DraftAccountControllerTest.java | 13 ++++++ .../service/opal/DraftAccountServiceTest.java | 46 ++++++++++++++++++- 6 files changed, 115 insertions(+), 21 deletions(-) delete mode 100644 src/main/java/uk/gov/hmcts/opal/service/DraftAccountServiceInterface.java diff --git a/src/integrationTest/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerIntegrationTest.java b/src/integrationTest/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerIntegrationTest.java index 566c8d730..8d2cd487c 100644 --- a/src/integrationTest/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerIntegrationTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerIntegrationTest.java @@ -35,6 +35,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; 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.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -187,4 +188,21 @@ void shouldReturn503WhenDownstreamServiceIsUnavailable() throws Exception { "message": "Opal Fines Database is currently unavailable" }""")); } + + @Test + void testDeleteDraftAccountById() throws Exception { + DraftAccountEntity draftAccountEntity = createDraftAccountEntity(); + + when(draftAccountService.getDraftAccount(1L)).thenReturn(draftAccountEntity); + + MvcResult result = mockMvc.perform(delete("/api/draft-accounts/1") + .header("authorization", "Bearer some_value")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value("Draft Account '1' deleted")) + .andReturn(); + + String body = result.getResponse().getContentAsString(); + logger.info(":testGetDraftAccountById: Response body:\n" + ToJsonString.toPrettyJson(body)); + } } diff --git a/src/main/java/uk/gov/hmcts/opal/controllers/DraftAccountController.java b/src/main/java/uk/gov/hmcts/opal/controllers/DraftAccountController.java index a9cf8b347..c0c110a13 100644 --- a/src/main/java/uk/gov/hmcts/opal/controllers/DraftAccountController.java +++ b/src/main/java/uk/gov/hmcts/opal/controllers/DraftAccountController.java @@ -1,16 +1,20 @@ package uk.gov.hmcts.opal.controllers; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import uk.gov.hmcts.opal.authorisation.model.UserState; import uk.gov.hmcts.opal.dto.AddDraftAccountRequestDto; @@ -37,6 +41,8 @@ public class DraftAccountController { public static final String ADD_DRAFT_ACCOUNT_REQUEST_JSON = "addDraftAccountRequest.json"; + public static final String ACCOUNT_DELETED_MESSAGE_FORMAT = """ + { "message": "Draft Account '%s' deleted"}"""; private final DraftAccountService draftAccountService; @@ -94,6 +100,24 @@ public ResponseEntity postDraftAccount(@RequestBody Add return buildCreatedResponse(toGetResponseDto(response)); } + @Hidden + @DeleteMapping(value = "/{draftAccountId}", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Deletes the Draft Account for the given draftAccountId.") + @ConditionalOnProperty(prefix = "opal.testing-support-endpoints", name = "enabled", havingValue = "true") + public ResponseEntity deleteDraftAccountById( + @PathVariable Long draftAccountId, + @RequestHeader(value = "Authorization", required = false) String authHeaderValue, + @RequestParam Optional ignoreMissing) { + + log.info(":DELETE:deleteDraftAccountById: draftAccountId: {}", draftAccountId); + + userStateService.checkForAuthorisedUser(authHeaderValue); + + draftAccountService.deleteDraftAccount(draftAccountId, ignoreMissing); + + return buildResponse(String.format(ACCOUNT_DELETED_MESSAGE_FORMAT, draftAccountId)); + } + DraftAccountResponseDto toGetResponseDto(DraftAccountEntity entity) { return DraftAccountResponseDto.builder() .draftAccountId(entity.getDraftAccountId()) diff --git a/src/main/java/uk/gov/hmcts/opal/service/DraftAccountServiceInterface.java b/src/main/java/uk/gov/hmcts/opal/service/DraftAccountServiceInterface.java deleted file mode 100644 index 7be5902c1..000000000 --- a/src/main/java/uk/gov/hmcts/opal/service/DraftAccountServiceInterface.java +++ /dev/null @@ -1,15 +0,0 @@ -package uk.gov.hmcts.opal.service; - -import uk.gov.hmcts.opal.dto.search.DraftAccountSearchDto; -import uk.gov.hmcts.opal.entity.DraftAccountEntity; - -import java.util.List; - -public interface DraftAccountServiceInterface { - - DraftAccountEntity getDraftAccount(long draftAccountId); - - List searchDraftAccounts(DraftAccountSearchDto criteria); - - -} diff --git a/src/main/java/uk/gov/hmcts/opal/service/opal/DraftAccountService.java b/src/main/java/uk/gov/hmcts/opal/service/opal/DraftAccountService.java index 17b114b6f..d58e20246 100644 --- a/src/main/java/uk/gov/hmcts/opal/service/opal/DraftAccountService.java +++ b/src/main/java/uk/gov/hmcts/opal/service/opal/DraftAccountService.java @@ -20,17 +20,17 @@ import uk.gov.hmcts.opal.repository.BusinessUnitRepository; import uk.gov.hmcts.opal.repository.DraftAccountRepository; import uk.gov.hmcts.opal.repository.jpa.DraftAccountSpecs; -import uk.gov.hmcts.opal.service.DraftAccountServiceInterface; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; +import java.util.Optional; @Service @Slf4j(topic = "DraftAccountService") @RequiredArgsConstructor @Qualifier("draftAccountService") -public class DraftAccountService implements DraftAccountServiceInterface { +public class DraftAccountService { private final DraftAccountRepository draftAccountRepository; @@ -38,12 +38,24 @@ public class DraftAccountService implements DraftAccountServiceInterface { private final DraftAccountSpecs specs = new DraftAccountSpecs(); - @Override public DraftAccountEntity getDraftAccount(long draftAccountId) { return draftAccountRepository.getReferenceById(draftAccountId); } - @Override + public void deleteDraftAccount(long draftAccountId, Optional ignoreMissing) { + DraftAccountEntity entity = getDraftAccount(draftAccountId); + // If the DB doesn't hold the target entity to be deleted, then no exception is thrown when a deletion is + // attempted. So we need to retrieve the entity first and try to access any property. + // This will throw an exception if the entity doesn't exist. + boolean checkExists = !(ignoreMissing.orElse(false)); + if (checkExists && entity.getDraftAccountId() == null) { + // Will not get here, as JPA should throw an exception. But for testing, throw an Exception. + throw new RuntimeException("Draft Account entity '" + draftAccountId + "' does not exist in the DB."); + } else { + draftAccountRepository.delete(entity); + } + } + public List searchDraftAccounts(DraftAccountSearchDto criteria) { Page page = draftAccountRepository .findBy(specs.findBySearchCriteria(criteria), diff --git a/src/test/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerTest.java b/src/test/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerTest.java index ace066443..946552e82 100644 --- a/src/test/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerTest.java +++ b/src/test/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerTest.java @@ -121,6 +121,19 @@ void testSaveDraftAccounts_Success() { verify(draftAccountService, times(1)).submitDraftAccount(any(), any()); } + @Test + void testDeleteDraftAccount_Success() { + // Act + ResponseEntity response = draftAccountController + .deleteDraftAccountById(7L, BEARER_TOKEN, Optional.empty()); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(""" + { "message": "Draft Account '7' deleted"}""", response.getBody()); + verify(draftAccountService, times(1)).deleteDraftAccount(any(Long.class), any()); + } + DraftAccountResponseDto toGetDto(DraftAccountEntity entity) { return DraftAccountResponseDto.builder() .draftAccountId(entity.getDraftAccountId()) diff --git a/src/test/java/uk/gov/hmcts/opal/service/opal/DraftAccountServiceTest.java b/src/test/java/uk/gov/hmcts/opal/service/opal/DraftAccountServiceTest.java index 3d7a57ddd..0c725e42e 100644 --- a/src/test/java/uk/gov/hmcts/opal/service/opal/DraftAccountServiceTest.java +++ b/src/test/java/uk/gov/hmcts/opal/service/opal/DraftAccountServiceTest.java @@ -1,5 +1,6 @@ package uk.gov.hmcts.opal.service.opal; +import jakarta.persistence.EntityNotFoundException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -24,7 +25,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -42,7 +45,6 @@ class DraftAccountServiceTest { @Test void testGetDraftAccount() { // Arrange - DraftAccountEntity draftAccountEntity = DraftAccountEntity.builder().build(); when(draftAccountRepository.getReferenceById(any())).thenReturn(draftAccountEntity); @@ -51,7 +53,6 @@ void testGetDraftAccount() { // Assert assertNotNull(result); - } @SuppressWarnings("unchecked") @@ -98,6 +99,47 @@ void testSubmitDraftAccounts() { assertEquals(draftAccountEntity, result); } + @Test + void testDeleteDraftAccount_success() { + // Arrange + DraftAccountEntity draftAccountEntity = DraftAccountEntity.builder().draftAccountId(1L).build(); + when(draftAccountRepository.getReferenceById(any())).thenReturn(draftAccountEntity); + + // Act + draftAccountService.deleteDraftAccount(1, Optional.empty()); + } + + @Test + void testDeleteDraftAccount_fail1() { + // Arrange + DraftAccountEntity draftAccountEntity = mock(DraftAccountEntity.class); + when(draftAccountEntity.getDraftAccountId()).thenThrow(new EntityNotFoundException("No Entity in DB")); + when(draftAccountRepository.getReferenceById(any())).thenReturn(draftAccountEntity); + + // Act + EntityNotFoundException enfe = assertThrows( + EntityNotFoundException.class, () -> draftAccountService.deleteDraftAccount(1, Optional.empty()) + ); + + // Assert + assertEquals("No Entity in DB", enfe.getMessage()); + } + + @Test + void testDeleteDraftAccount_fail2() { + // Arrange + DraftAccountEntity draftAccountEntity = DraftAccountEntity.builder().build(); + when(draftAccountRepository.getReferenceById(any())).thenReturn(draftAccountEntity); + + // Act + RuntimeException re = assertThrows( + RuntimeException.class, () -> draftAccountService.deleteDraftAccount(8, Optional.empty()) + ); + + // Assert + assertEquals("Draft Account entity '8' does not exist in the DB.", re.getMessage()); + } + private String createAccountString() { return """ {