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 eb066b3d1..60f379550 100644 --- a/src/integrationTest/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerIntegrationTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerIntegrationTest.java @@ -1,11 +1,13 @@ package uk.gov.hmcts.opal.controllers; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.QueryTimeoutException; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; import org.mockito.stubbing.OngoingStubbing; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; @@ -102,6 +104,7 @@ void testGetDraftAccountById_success() throws Exception { DraftAccountEntity draftAccountEntity = createDraftAccountEntity(); when(draftAccountService.getDraftAccount(1L)).thenReturn(draftAccountEntity); + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); MvcResult result = mockMvc.perform(get(URL_BASE + "/1") .header("authorization", "Bearer some_value")) @@ -121,12 +124,57 @@ void testGetDraftAccountById_success() throws Exception { assertTrue(jsonSchemaValidationService.isValid(body, GET_DRAFT_ACCOUNT_RESPONSE)); } + @Test + void testGetDraftAccountById_trap403Response_wrongPermission() throws Exception { + DraftAccountEntity entity = createDraftAccountEntity(); + when(draftAccountService.getDraftAccount(2L)).thenReturn(entity); + + UserState userState = permissionUser((short)007, Permissions.COLLECTION_ORDER); + when(userStateService.checkForAuthorisedUser(any())).thenReturn(userState); + + mockMvc.perform( + get(URL_BASE + "/2") + .header("authorization", "Bearer some_value")) + .andExpect(status().isForbidden()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.error").value("Forbidden")) + .andExpect(jsonPath("$.message").value( + "For user null, [CREATE_MANAGE_DRAFT_ACCOUNTS, CHECK_VALIDATE_DRAFT_ACCOUNTS] " + + "permission(s) are not enabled for the user.")); + } + + @Test + void testGetDraftAccountById_trap403Response_wrongBusinessUnit() throws Exception { + DraftAccountEntity entity = createDraftAccountEntity(); + when(draftAccountService.getDraftAccount(2L)).thenReturn(entity); + + UserState userState = permissionUser((short)005, Permissions.DRAFT_ACCOUNT_PERMISSIONS); + when(userStateService.checkForAuthorisedUser(any())).thenReturn(userState); + + mockMvc.perform( + get(URL_BASE + "/2") + .header("authorization", "Bearer some_value")) + .andExpect(status().isForbidden()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.error").value("Forbidden")) + .andExpect(jsonPath("$.message").value( + "For user null, [CREATE_MANAGE_DRAFT_ACCOUNTS, CHECK_VALIDATE_DRAFT_ACCOUNTS] " + + "permission(s) are not enabled in business unit: 7")); + } + @Test void testGetDraftAccountById_trap404Response() throws Exception { - when(draftAccountService.getDraftAccount(2L)).thenReturn(null); + DraftAccountEntity entity = Mockito.mock(DraftAccountEntity.class); + when(entity.getBusinessUnit()).thenThrow(new EntityNotFoundException()); - mockMvc.perform(get(URL_BASE + "/2").header("authorization", "Bearer some_value")) - .andExpect(status().isNotFound()); + when(draftAccountService.getDraftAccount(2L)).thenReturn(entity); + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + mockMvc.perform( + get(URL_BASE + "/2") + .header("authorization", "Bearer some_value")) + .andExpect(status().isNotFound()) + ; } @Test @@ -535,7 +583,7 @@ void testPostDraftAccount_trap403Response_noPermission() throws Exception { .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.error").value("Forbidden")) .andExpect(jsonPath("$.message").value( - "For user null, [CREATE_MANAGE_DRAFT_ACCOUNTS] permission(s) are not allowed for the user.")) + "For user null, [CREATE_MANAGE_DRAFT_ACCOUNTS] permission(s) are not enabled for the user.")) .andReturn(); String body = result.getResponse().getContentAsString(); @@ -559,7 +607,7 @@ void testPostDraftAccount_trap403Response_wrongPermission() throws Exception { .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.error").value("Forbidden")) .andExpect(jsonPath("$.message").value( - "For user null, [CREATE_MANAGE_DRAFT_ACCOUNTS] permission(s) are not allowed for the user.")) + "For user null, [CREATE_MANAGE_DRAFT_ACCOUNTS] permission(s) are not enabled for the user.")) .andReturn(); String body = result.getResponse().getContentAsString(); @@ -801,7 +849,9 @@ void methodsShouldReturn404WhenResourceNotFound(HttpMethod method, String fullPa when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); // For GET return null - when(draftAccountService.getDraftAccount(nonExistentId)).thenReturn(null); + DraftAccountEntity entity = Mockito.mock(DraftAccountEntity.class); + when(entity.getBusinessUnit()).thenThrow(new EntityNotFoundException()); + when(draftAccountService.getDraftAccount(nonExistentId)).thenReturn(entity); // For PUT, throw EntityNotFoundException when(draftAccountService.replaceDraftAccount(eq(nonExistentId), any(ReplaceDraftAccountRequestDto.class))) diff --git a/src/integrationTest/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerIntegrationTest.java.orig b/src/integrationTest/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerIntegrationTest.java.orig new file mode 100644 index 000000000..fbd3974b3 --- /dev/null +++ b/src/integrationTest/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerIntegrationTest.java.orig @@ -0,0 +1,912 @@ +package uk.gov.hmcts.opal.controllers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.EntityNotFoundException; +import jakarta.persistence.QueryTimeoutException; +import org.junit.jupiter.api.Test; +<<<<<<< HEAD +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +======= +import org.mockito.Mockito; +>>>>>>> a65ca2e0 (PO-828: Check authorisation for GET Draft Account endpoint) +import org.mockito.stubbing.OngoingStubbing; +import org.postgresql.util.PSQLException; +import org.postgresql.util.PSQLState; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import uk.gov.hmcts.opal.authentication.service.AccessTokenService; +import uk.gov.hmcts.opal.authorisation.model.Permissions; +import uk.gov.hmcts.opal.authorisation.model.UserState; +import uk.gov.hmcts.opal.config.WebConfig; +import uk.gov.hmcts.opal.controllers.advice.GlobalExceptionHandler; +import uk.gov.hmcts.opal.dto.AddDraftAccountRequestDto; +import uk.gov.hmcts.opal.dto.ReplaceDraftAccountRequestDto; +import uk.gov.hmcts.opal.dto.ToJsonString; +import uk.gov.hmcts.opal.dto.UpdateDraftAccountRequestDto; +import uk.gov.hmcts.opal.dto.search.DraftAccountSearchDto; +import uk.gov.hmcts.opal.entity.BusinessUnitEntity; +import uk.gov.hmcts.opal.entity.DraftAccountEntity; +import uk.gov.hmcts.opal.entity.DraftAccountStatus; +import uk.gov.hmcts.opal.service.opal.DraftAccountService; +import uk.gov.hmcts.opal.service.opal.JsonSchemaValidationService; +import uk.gov.hmcts.opal.service.opal.UserStateService; + +import java.net.ConnectException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import static java.util.Collections.singletonList; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.PATCH; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.HttpMethod.PUT; +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.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static uk.gov.hmcts.opal.controllers.util.UserStateUtil.allPermissionsUser; +import static uk.gov.hmcts.opal.controllers.util.UserStateUtil.noPermissionsUser; +import static uk.gov.hmcts.opal.controllers.util.UserStateUtil.permissionUser; + + +@WebMvcTest +@ContextConfiguration(classes = {DraftAccountController.class, GlobalExceptionHandler.class, WebConfig.class}) +@ActiveProfiles({"integration"}) +class DraftAccountControllerIntegrationTest { + private static final Logger logger = Logger.getLogger(DraftAccountControllerIntegrationTest.class.getSimpleName()); + private static final String URL_BASE = "/draft-accounts"; + private static final String GET_DRAFT_ACCOUNT_RESPONSE = "getDraftAccountResponse.json"; + private static final String GET_DRAFT_ACCOUNTS_RESPONSE = "getDraftAccountsResponse.json"; + + @Autowired + MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + @Qualifier("draftAccountService") + DraftAccountService draftAccountService; + + @MockBean + UserStateService userStateService; + + @MockBean + AccessTokenService tokenService; + + @SpyBean + private JsonSchemaValidationService jsonSchemaValidationService; + + @Test + void testGetDraftAccountById_success() throws Exception { + DraftAccountEntity draftAccountEntity = createDraftAccountEntity(); + + when(draftAccountService.getDraftAccount(1L)).thenReturn(draftAccountEntity); + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + MvcResult result = mockMvc.perform(get(URL_BASE + "/1") + .header("authorization", "Bearer some_value")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.draft_account_id").value(1)) + .andExpect(jsonPath("$.business_unit_id").value(7)) + .andExpect(jsonPath("$.account_type").value("DRAFT")) + .andExpect(jsonPath("$.submitted_by").value("Tony")) + .andExpect(jsonPath("$.account_status").value("Submitted")) + .andReturn(); + + String body = result.getResponse().getContentAsString(); + + logger.info(":testGetDraftAccountById: Response body:\n" + ToJsonString.toPrettyJson(body)); + + assertTrue(jsonSchemaValidationService.isValid(body, GET_DRAFT_ACCOUNT_RESPONSE)); + } + + @Test + void testGetDraftAccountById_trap403Response_wrongPermission() throws Exception { + DraftAccountEntity entity = createDraftAccountEntity(); + when(draftAccountService.getDraftAccount(2L)).thenReturn(entity); + + UserState userState = permissionUser((short)007, Permissions.COLLECTION_ORDER); + when(userStateService.checkForAuthorisedUser(any())).thenReturn(userState); + + mockMvc.perform( + get(URL_BASE + "/2") + .header("authorization", "Bearer some_value")) + .andExpect(status().isForbidden()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.error").value("Forbidden")) + .andExpect(jsonPath("$.message").value( + "For user null, [CREATE_MANAGE_DRAFT_ACCOUNTS, CHECK_VALIDATE_DRAFT_ACCOUNTS] " + + "permission(s) are not enabled for the user.")); + } + + @Test + void testGetDraftAccountById_trap403Response_wrongBusinessUnit() throws Exception { + DraftAccountEntity entity = createDraftAccountEntity(); + when(draftAccountService.getDraftAccount(2L)).thenReturn(entity); + + UserState userState = permissionUser((short)005, Permissions.DRAFT_ACCOUNT_PERMISSIONS); + when(userStateService.checkForAuthorisedUser(any())).thenReturn(userState); + + mockMvc.perform( + get(URL_BASE + "/2") + .header("authorization", "Bearer some_value")) + .andExpect(status().isForbidden()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.error").value("Forbidden")) + .andExpect(jsonPath("$.message").value( + "For user null, [CREATE_MANAGE_DRAFT_ACCOUNTS, CHECK_VALIDATE_DRAFT_ACCOUNTS] " + + "permission(s) are not enabled in business unit: 7")); + } + + @Test + void testGetDraftAccountById_trap404Response() throws Exception { + DraftAccountEntity entity = Mockito.mock(DraftAccountEntity.class); + when(entity.getBusinessUnit()).thenThrow(new EntityNotFoundException()); + + when(draftAccountService.getDraftAccount(2L)).thenReturn(entity); + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + mockMvc.perform( + get(URL_BASE + "/2") + .header("authorization", "Bearer some_value")) + .andExpect(status().isNotFound()) + ; + } + + @Test + void testGetDraftAccountById_trap406Response() throws Exception { + when(draftAccountService.getDraftAccount(1L)).thenReturn(createDraftAccountEntity()); + shouldReturn406WhenResponseContentTypeNotSupported(get(URL_BASE + "/1")); + } + + @Test + void testGetDraftAccountById_trap408Response() throws Exception { + shouldReturn408WhenTimeout(get(URL_BASE + "/1"), + when(draftAccountService.getDraftAccount(1L))); + } + + @Test + void testGetDraftAccountById_trap503Response() throws Exception { + shouldReturn503WhenDownstreamServiceIsUnavailable(get(URL_BASE + "/1"), + when(draftAccountService.getDraftAccount(1L))); + } + + @Test + void testGetDraftAccountsSummaries_noParams() throws Exception { + DraftAccountEntity draftAccountEntity = createDraftAccountEntity(); + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + when(draftAccountService.getDraftAccounts(any(),any(), any())) + .thenReturn(singletonList(draftAccountEntity)); + + String body = checkStandardSummaryExpectations(mockMvc.perform(get(URL_BASE) + .header("authorization", "Bearer some_value") + // .param("business_unit", "1") + .contentType(MediaType.APPLICATION_JSON))); + + logger.info(":testGetDraftAccountsSummaries_noParams: body:\n" + ToJsonString.toPrettyJson(body)); + + assertTrue(jsonSchemaValidationService.isValid(body, GET_DRAFT_ACCOUNTS_RESPONSE)); + } + + @Test + void testGetDraftAccountsSummaries_permission() throws Exception { + DraftAccountEntity draftAccountEntity = createDraftAccountEntity(); + final Short businessId = (short)1; + + UserState user = permissionUser(businessId, Permissions.CREATE_MANAGE_DRAFT_ACCOUNTS); + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(user); + when(draftAccountService.getDraftAccounts(any(), any(), any())) + .thenReturn(singletonList(draftAccountEntity)); + + String body = checkStandardSummaryExpectations(mockMvc.perform(get(URL_BASE) + .header("authorization", "Bearer some_value") + .param("business_unit", businessId.toString()) + .contentType(MediaType.APPLICATION_JSON))); + + logger.info(":testGetDraftAccountsSummaries_permission: body:\n" + ToJsonString.toPrettyJson(body)); + + assertTrue(jsonSchemaValidationService.isValid(body, GET_DRAFT_ACCOUNTS_RESPONSE)); + } + + @Test + void testGetDraftAccountsSummaries_trap403Response_noPermission() throws Exception { + DraftAccountEntity draftAccountEntity = createDraftAccountEntity(); + final Short businessId = (short)1; + + UserState user = permissionUser(businessId, Permissions.COLLECTION_ORDER); + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(user); + when(draftAccountService.getDraftAccounts(any(), any(), any())) + .thenReturn(singletonList(draftAccountEntity)); + + mockMvc.perform(get(URL_BASE) + .header("authorization", "Bearer some_value") + .param("business_unit", businessId.toString()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + } + + @Test + void testGetDraftAccountsSummaries_trap406Response() throws Exception { + shouldReturn406WhenResponseContentTypeNotSupported(get(URL_BASE)); + } + + @Test + void testGetDraftAccountsSummaries_trap408Response() throws Exception { + shouldReturn408WhenTimeout(get(URL_BASE), when(draftAccountService.getDraftAccounts(any(), any(), any()))); + } + + @Test + void testGetDraftAccountsSummaries_trap503Response() throws Exception { + shouldReturn503WhenDownstreamServiceIsUnavailable(get(URL_BASE), + when(draftAccountService.getDraftAccounts(any(), any(), any()))); + } + + private String checkStandardSummaryExpectations(ResultActions actions) throws Exception { + return actions.andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.count").value(1)) + .andExpect(jsonPath("$.summaries[0].draft_account_id").value(1)) + .andExpect(jsonPath("$.summaries[0].business_unit_id").value(7)) + .andExpect(jsonPath("$.summaries[0].account_type").value("DRAFT")) + .andExpect(jsonPath("$.summaries[0].submitted_by").value("Tony")) + .andExpect(jsonPath("$.summaries[0].account_status").value("Submitted")) + .andReturn().getResponse().getContentAsString(); + } + + @Test + void testSearchDraftAccountsPost() throws Exception { + DraftAccountEntity draftAccountEntity = createDraftAccountEntity(); + + when(draftAccountService.searchDraftAccounts(any(DraftAccountSearchDto.class))) + .thenReturn(singletonList(draftAccountEntity)); + + mockMvc.perform(post(URL_BASE + "/search") + .header("authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"criteria\":\"value\"}")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$[0].draft_account_id").value(1)) + .andExpect(jsonPath("$[0].business_unit_id").value(7)) + .andExpect(jsonPath("$[0].account_type").value("DRAFT")) + .andExpect(jsonPath("$[0].submitted_by").value("Tony")) + .andExpect(jsonPath("$[0].account_status").value("Submitted")); + } + + @Test + void testSearchDraftAccountsPost_whenDraftAccountDoesNotExist() throws Exception { + mockMvc.perform(post(URL_BASE + "/search") + .header("authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"criteria\":\"2\"}")) + .andExpect(status().isOk()); + } + + + private DraftAccountEntity createDraftAccountEntity() { + return DraftAccountEntity.builder() + .draftAccountId(1L) + .businessUnit(BusinessUnitEntity.builder().businessUnitId((short)007).build()) + .createdDate(LocalDate.of(2023, 1, 2).atStartOfDay()) + .submittedBy("Tony") + .accountType("DRAFT") + .accountStatus(DraftAccountStatus.SUBMITTED) + .account("{}") + .accountSnapshot("{ \"data\": \"something snappy\"}") + .timelineData("{}") + .build(); + } + + + @Test + void testDeleteDraftAccountById_success() throws Exception { + DraftAccountEntity draftAccountEntity = createDraftAccountEntity(); + + when(draftAccountService.getDraftAccount(1L)).thenReturn(draftAccountEntity); + + MvcResult result = mockMvc.perform(delete(URL_BASE + "/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)); + } + + @Test + void testReplaceDraftAccount_success() throws Exception { + Long draftAccountId = 241L; + + LocalDateTime testDateTime = LocalDateTime.of(2024, 9, 26, 15, 0, 0); + + DraftAccountEntity updatedEntity = DraftAccountEntity.builder() + .draftAccountId(draftAccountId) + .businessUnit(BusinessUnitEntity.builder().businessUnitId((short) 5) + .businessUnitName("Cambridgeshire").build()) + .createdDate(testDateTime) + .submittedBy("BUUID1") + .account("{\"account_create_request\":{\"defendant\":{\"company_name\":\"Company ABC\",\"surname\"" + + ":\"LNAME\",\"fornames\":\"FNAME\",\"dob\":\"2000-01-01\"},\"account\"" + + ":{\"account_type\":\"Fine\"}}}") + .accountSnapshot("{\"defendant_name\":\"Company ABC\",\"created_date\":\"2024-09-26T15:00:00Z\"," + + "\"account_type\":\"Fine\",\"submitted_by\":\"BUUID1\"," + + "\"business_unit_name\":\"Cambridgeshire\"}") + .accountType("Fines") + .accountStatus(DraftAccountStatus.RESUBMITTED) + .timelineData("{\"stuff\":\"yes\"}") + .build(); + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + when(draftAccountService.replaceDraftAccount(eq(draftAccountId), any(ReplaceDraftAccountRequestDto.class))) + .thenReturn(updatedEntity); + + MvcResult result = mockMvc.perform(put(URL_BASE + "/" + draftAccountId) + .header("authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content(validCreateRequestBody())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.draft_account_id").value(draftAccountId)) + .andExpect(jsonPath("$.business_unit_id").value(5)) + .andExpect(jsonPath("$.created_at").value("2024-09-26T15:00:00Z")) + .andExpect(jsonPath("$.submitted_by").value("BUUID1")) + .andExpect(jsonPath("$.account.account_create_request.defendant.company_name") + .value("Company ABC")) + .andExpect(jsonPath("$.account_snapshot.defendant_name").value("Company ABC")) + .andExpect(jsonPath("$.account_type").value("Fines")) + .andExpect(jsonPath("$.account_status").value("Resubmitted")) + .andExpect(jsonPath("$.timeline_data.stuff").value("yes")) + .andReturn(); + + String body = result.getResponse().getContentAsString(); + logger.info(":testReplaceDraftAccount: Response body:\n" + ToJsonString.toPrettyJson(body)); + + assertTrue(jsonSchemaValidationService.isValid(body, GET_DRAFT_ACCOUNT_RESPONSE)); + + verify(draftAccountService).replaceDraftAccount(eq(draftAccountId), any(ReplaceDraftAccountRequestDto.class)); + } + + @Test + void testReplaceDraftAccount_trap403Response_boPermission() throws Exception { + Long draftAccountId = 241L; + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(noPermissionsUser()); + + mockMvc.perform(put(URL_BASE + "/" + draftAccountId) + .header("authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content(validCreateRequestBody())) + .andExpect(status().isForbidden()) + .andReturn(); + + } + + @Test + void testUpdateDraftAccount_success() throws Exception { + Long draftAccountId = 241L; + + LocalDateTime testDateTime = LocalDateTime.of(2024, 10, 3, 14, 30, 0); + + DraftAccountEntity updatedEntity = DraftAccountEntity.builder() + .draftAccountId(draftAccountId) + .businessUnit(BusinessUnitEntity.builder().businessUnitId((short) 5) + .businessUnitName("Cambridgeshire").build()) + .createdDate(testDateTime.minusDays(1)) + .submittedBy("BUUID1") + .validatedDate(testDateTime) + .validatedBy("BUUID1") + .account("{\"account_create_request\":{\"defendant\":{\"company_name\":\"Company ABC\"," + + "\"surname\":\"LNAME\",\"fornames\":\"FNAME\",\"dob\":\"2000-01-01\"}," + + "\"account\":{\"account_type\":\"Fine\"}}}") + .accountSnapshot("{\"defendant_name\":\"Company ABC\",\"created_date\":\"2024-10-02T14:30:00Z\"," + + "\"account_type\":\"Fine\",\"submitted_by\":\"BUUID1\"," + + "\"business_unit_name\":\"Cambridgeshire\"," + + "\"approved_date\":\"2024-10-03T14:30:00Z\"}") + .accountType("Fines") + .accountStatus(DraftAccountStatus.PENDING) + .timelineData("{\"test\":\"yes\"}") + .build(); + + when(draftAccountService.updateDraftAccount(eq(draftAccountId), any(UpdateDraftAccountRequestDto.class))) + .thenReturn(updatedEntity); + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + MvcResult result = mockMvc.perform(patch(URL_BASE + "/" + draftAccountId) + .header("authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content(validUpdateRequestBody())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.draft_account_id").value(draftAccountId)) + .andExpect(jsonPath("$.business_unit_id").value(5)) + .andExpect(jsonPath("$.created_at").value("2024-10-02T14:30:00Z")) + .andExpect(jsonPath("$.submitted_by").value("BUUID1")) + .andExpect(jsonPath("$.validated_at").value("2024-10-03T14:30:00Z")) + .andExpect(jsonPath("$.validated_by").value("BUUID1")) + .andExpect(jsonPath("$.account.account_create_request.defendant.company_name") + .value("Company ABC")) + .andExpect(jsonPath("$.account_snapshot.defendant_name").value("Company ABC")) + .andExpect(jsonPath("$.account_snapshot.approved_date") + .value("2024-10-03T14:30:00Z")) + .andExpect(jsonPath("$.account_type").value("Fines")) + .andExpect(jsonPath("$.account_status").value("Pending")) + .andExpect(jsonPath("$.timeline_data.test").value("yes")) + .andReturn(); + + String body = result.getResponse().getContentAsString(); + logger.info(":testUpdateDraftAccount: Response body:\n" + ToJsonString.toPrettyJson(body)); + + assertTrue(jsonSchemaValidationService.isValid(body, GET_DRAFT_ACCOUNT_RESPONSE)); + + verify(draftAccountService).updateDraftAccount(eq(draftAccountId), any(UpdateDraftAccountRequestDto.class)); + } + + @Test + void testUpdateDraftAccount_trap403Response_noPermission() throws Exception { + Long draftAccountId = 241L; + String requestBody = """ + { + "account_status": "PENDING", + "validated_by": "BUUID1", + "business_unit_id": 5, + "timeline_data": {"test":"yes"} + } + """; + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(noPermissionsUser()); + + mockMvc.perform(patch(URL_BASE + "/" + draftAccountId) + .header("authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andExpect(status().isForbidden()) + .andReturn(); + + } + + @Test + void testUpdateDraftAccount_trap406Response() throws Exception { + when(draftAccountService.updateDraftAccount(any(), any())).thenReturn(createDraftAccountEntity()); + shouldReturn406WhenResponseContentTypeNotSupported( + patch(URL_BASE + "/1").contentType(MediaType.APPLICATION_JSON).content(validUpdateRequestBody()) + ); + } + + @Test + void testUpdateDraftAccount_trap408Response() throws Exception { + shouldReturn408WhenTimeout( + patch(URL_BASE + "/1").contentType(MediaType.APPLICATION_JSON).content(validUpdateRequestBody()), + when(draftAccountService.updateDraftAccount(any(), any())) + ); + } + + @Test + void testUpdateDraftAccount_trap503Response() throws Exception { + shouldReturn503WhenDownstreamServiceIsUnavailable( + patch(URL_BASE + "/1").contentType(MediaType.APPLICATION_JSON).content(validUpdateRequestBody()), + when(draftAccountService.updateDraftAccount(any(), any())) + ); + } + + @Test + void testPostDraftAccount_trap400Response() throws Exception { + + String expectedErrorMessageStart = + "JSON Schema Validation Error: Validating against JSON schema 'addDraftAccountRequest.json'," + + " found 3 validation errors:"; + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + mockMvc.perform(post(URL_BASE) + .header("authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content(invalidCreateRequestBody())) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.error").value("Bad Request")) + .andExpect(jsonPath("$.message").value(containsString(expectedErrorMessageStart))) + .andExpect(jsonPath("$.message").value(containsString("required property 'account_type' not found"))) + .andExpect(jsonPath("$.message").value(containsString("required property 'submitted_by' not found"))) + .andExpect(jsonPath("$.message").value(containsString("required property 'timeline_data' not found"))); + + } + + @Test + void testPostDraftAccount_permission() throws Exception { + + String validRequestBody = setupValidPostRequest(); + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + MvcResult result = mockMvc.perform(post(URL_BASE) + .header("authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content(validRequestBody)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.submitted_by").value("BUUID1")) + .andExpect(jsonPath("$.account_type").value("Fines")) + .andExpect(jsonPath("$.account_status").value("Submitted")) + .andExpect(jsonPath("$.account.accountCreateRequest.Account.AccountType") + .value("Fine")) + .andExpect(jsonPath("$.account.accountCreateRequest.Defendant.Surname") + .value("LNAME")) + .andReturn(); + + String body = result.getResponse().getContentAsString(); + + logger.info(":testPostDraftAccount_permission: Response body:\n" + ToJsonString.toPrettyJson(body)); + } + + @Test + void testPostDraftAccount_trap403Response_noPermission() throws Exception { + + String validRequestBody = setupValidPostRequest(); + when(userStateService.checkForAuthorisedUser(any())).thenReturn(noPermissionsUser()); + + MvcResult result = mockMvc.perform(post(URL_BASE) + .header("authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content(validRequestBody)) + .andExpect(status().isForbidden()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.error").value("Forbidden")) + .andExpect(jsonPath("$.message").value( + "For user null, [CREATE_MANAGE_DRAFT_ACCOUNTS] permission(s) are not enabled for the user.")) + .andReturn(); + + String body = result.getResponse().getContentAsString(); + + logger.info(":testPostDraftAccount_permission: Response body:\n" + ToJsonString.toPrettyJson(body)); + } + + @Test + void testPostDraftAccount_trap403Response_wrongPermission() throws Exception { + + String validRequestBody = setupValidPostRequest(); + + when(userStateService.checkForAuthorisedUser(any())).thenReturn( + permissionUser((short)5, Permissions.CHECK_VALIDATE_DRAFT_ACCOUNTS, Permissions.ACCOUNT_ENQUIRY)); + + MvcResult result = mockMvc.perform(post(URL_BASE) + .header("authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content(validRequestBody)) + .andExpect(status().isForbidden()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.error").value("Forbidden")) + .andExpect(jsonPath("$.message").value( + "For user null, [CREATE_MANAGE_DRAFT_ACCOUNTS] permission(s) are not enabled for the user.")) + .andReturn(); + + String body = result.getResponse().getContentAsString(); + + logger.info(":testPostDraftAccount_permission: Response body:\n" + ToJsonString.toPrettyJson(body)); + } + + @Test + void testPostDraftAccount_trap406Response() throws Exception { + String validRequestBody = setupValidPostRequest(); + shouldReturn406WhenResponseContentTypeNotSupported( + post(URL_BASE).contentType(MediaType.APPLICATION_JSON).content(validRequestBody)); + } + + @Test + void testPostDraftAccount_trap408Response() throws Exception { + String validRequestBody = setupValidPostRequest(); + shouldReturn408WhenTimeout( + post(URL_BASE).contentType(MediaType.APPLICATION_JSON).content(validRequestBody), + when(draftAccountService.submitDraftAccount(any()))); + } + + @Test + void testPostDraftAccount_trap503Response() throws Exception { + String validRequestBody = setupValidPostRequest(); + shouldReturn503WhenDownstreamServiceIsUnavailable( + post(URL_BASE).contentType(MediaType.APPLICATION_JSON).content(validRequestBody), + when(draftAccountService.submitDraftAccount(any()))); + } + + private String setupValidPostRequest() { + String validRequestBody = validCreateRequestBody(); + AddDraftAccountRequestDto dto = ToJsonString.toClassInstance(validRequestBody, AddDraftAccountRequestDto.class); + LocalDateTime created = LocalDateTime.now(); + DraftAccountEntity entity = toEntity(dto, created); + when(draftAccountService.submitDraftAccount(any())).thenReturn(entity); + return validRequestBody; + } + + void shouldReturn406WhenResponseContentTypeNotSupported(MockHttpServletRequestBuilder reqBuilder) throws Exception { + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + mockMvc.perform(reqBuilder + .header("Authorization", "Bearer " + "some_value") + .accept("application/xml")) + .andExpect(status().isNotAcceptable()); + } + + + void shouldReturn408WhenTimeout(MockHttpServletRequestBuilder reqBuilder, OngoingStubbing stubbing) + throws Exception { + // Simulating a timeout exception when the service is called + stubbing.thenThrow(new QueryTimeoutException()); + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + mockMvc.perform(reqBuilder + .header("Authorization", "Bearer " + "some_value")) + .andExpect(status().isRequestTimeout()) + .andExpect(content().contentType("application/json")) + .andExpect(content().json(""" + { + "error": "Request Timeout", + "message": "The request did not receive a response from the database within the timeout period" + }""")); + } + + + void shouldReturn503WhenDownstreamServiceIsUnavailable(MockHttpServletRequestBuilder reqBuilder, + OngoingStubbing stubbing) throws Exception { + stubbing.thenAnswer( + invocation -> { + throw new PSQLException("Connection refused", PSQLState.CONNECTION_FAILURE, new ConnectException()); + }); + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + mockMvc.perform(reqBuilder + .header("Authorization", "Bearer " + "some_value")) + .andExpect(status().isServiceUnavailable()) + .andExpect(content().contentType("application/json")) + .andExpect(content().json(""" + { + "error": "Service Unavailable", + "message": "Opal Fines Database is currently unavailable" + }""")); + } + + private static String validCreateRequestBody() { + return """ +{ + "account": { + "accountCreateRequest": { + "Defendant": { + "CompanyName": "Company ABC", + "Surname": "LNAME", + "Fornames": "FNAME", + "DOB": "2000-01-01" + }, + "Account": { + "AccountType": "Fine" + } + } + }, + "account_type": "Fines", + "business_unit_id": 5, + "submitted_by": "BUUID1", + "timeline_data": { + "stuff": "yes" + } +}"""; + } + + private static String invalidCreateRequestBody() { + return """ +{ + "invalid_field": "This field shouldn't be here", + "account": { + "account_create_request": { + "defendant": { + "company_name": "Company ABC", + "surname": "LNAME", + "fornames": "FNAME", + "dob": "2000-01-01" + }, + "account": { + "account_type": "Invalid" + } + } + }, + "business_unit_id": 1 +}"""; + } + + private static String validUpdateRequestBody() { + return """ +{ + "account_status": "PENDING", + "validated_by": "BUUID1", + "business_unit_id": 5, + "timeline_data": {"test":"yes"} +}"""; + } + + private DraftAccountEntity toEntity(AddDraftAccountRequestDto dto, LocalDateTime created) { + return DraftAccountEntity.builder() + .businessUnit(BusinessUnitEntity.builder().build()) + .createdDate(created) + .submittedBy(dto.getSubmittedBy()) + .account(dto.getAccount()) + .accountSnapshot("{ \"data\": \"something snappy\"}") + .accountType(dto.getAccountType()) + .accountStatus(DraftAccountStatus.SUBMITTED) + .timelineData(dto.getTimelineData()) + .build(); + } + + //CEP 1 CEP1 - Invalid Request Payload (400) + @ParameterizedTest + @MethodSource("endpointsWithInvalidBodiesProvider") + void methodsShouldReturn400WhenRequestPayloadIsInvalid(HttpMethod method, String fullPath, String requestBody) + throws Exception { + MockHttpServletRequestBuilder requestBuilder = switch (method.name()) { + case "GET" -> get(fullPath); + case "POST" -> post(fullPath); + case "PUT" -> put(fullPath); + case "PATCH" -> patch(fullPath); + default -> throw new IllegalArgumentException("Unsupported HTTP method: " + method); + }; + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + mockMvc.perform(requestBuilder + .header("Authorization", "Bearer some_value") + .header("Accept", "application/json") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andExpect(status().isBadRequest()); + } + + private static Stream endpointsWithInvalidBodiesProvider() { + return Stream.of(Arguments.of(POST, "/draft-accounts",invalidCreateRequestBody()), + Arguments.of(PUT, "/draft-accounts/1",invalidCreateRequestBody()), + Arguments.of(PATCH, "/draft-accounts/1",invalidCreateRequestBody()) + ); + } + + //CEP2 - Invalid or No Access Token (401) - Security Context required - test elsewhere + + //CEP3 - Not Authorised to perform the requested action (403) + @ParameterizedTest + @MethodSource("testCasesRequiringAuthorizationProvider") + void methodsShouldReturn403WhenUserLacksPermission(HttpMethod method, String fullPath, String requestBody) + throws Exception { + MockHttpServletRequestBuilder requestBuilder = switch (method.name()) { + case "GET" -> get(fullPath); + case "POST" -> post(fullPath); + case "PUT" -> put(fullPath); + case "PATCH" -> patch(fullPath); + default -> throw new IllegalArgumentException("Unsupported HTTP method: " + method); + }; + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(noPermissionsUser()); + + mockMvc.perform(requestBuilder + .header("Authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andExpect(status().isForbidden()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.error").value("Forbidden")); + } + + private static Stream testCasesRequiringAuthorizationProvider() { + return Stream.of( + Arguments.of(POST, "/draft-accounts", validCreateRequestBody()), + Arguments.of(PUT, "/draft-accounts/1", validCreateRequestBody()), + Arguments.of(PATCH, "/draft-accounts/1", validUpdateRequestBody()), + Arguments.of(GET, "/draft-accounts", "") // GET endpoints with empty body + ); + } + + //CEP4 - Resource Not Found (404) - applies to GET PUT PATCH & DELETE + @ParameterizedTest + @MethodSource("testCasesForResourceNotFoundProvider") + void methodsShouldReturn404WhenResourceNotFound(HttpMethod method, String fullPath, String requestBody) + throws Exception { + // Set up a non-existent ID + long nonExistentId = 999L; + + MockHttpServletRequestBuilder requestBuilder = switch (method.name()) { + case "GET" -> get(fullPath); + case "PUT" -> put(fullPath); + case "PATCH" -> patch(fullPath); + case "DELETE" -> delete(fullPath); + default -> throw new IllegalArgumentException("Unsupported HTTP method: " + method); + }; + + // Mock the service behavior + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + // For GET return null + when(draftAccountService.getDraftAccount(nonExistentId)).thenReturn(null); + + // For PUT, throw EntityNotFoundException + when(draftAccountService.replaceDraftAccount(eq(nonExistentId), any(ReplaceDraftAccountRequestDto.class))) + .thenThrow(new jakarta.persistence.EntityNotFoundException("Draft Account not found with id: " + + nonExistentId)); + // For PATCH, throw EntityNotFoundException + when(draftAccountService.updateDraftAccount(eq(nonExistentId), any(UpdateDraftAccountRequestDto.class))) + .thenThrow(new jakarta.persistence.EntityNotFoundException("Draft Account not found with id: " + + nonExistentId)); + mockMvc.perform(requestBuilder + .header("Authorization", "Bearer some_value") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andExpect(status().isNotFound()); + } + + private static Stream testCasesForResourceNotFoundProvider() { + return Stream.of( + Arguments.of(GET, "/draft-accounts/999", ""), + Arguments.of(PUT, "/draft-accounts/999", validCreateRequestBody()), + Arguments.of(PATCH, "/draft-accounts/999", validUpdateRequestBody()) + ); + } + + //CEP5 - Unsupported Content Type for Response (406) + @ParameterizedTest + @MethodSource("testCasesWithValidBodiesProvider") + void methodsShouldReturn406WhenAcceptHeaderIsNotSupported(HttpMethod method, String fullPath, String requestBody) + throws Exception { + MockHttpServletRequestBuilder requestBuilder = switch (method.name()) { + case "GET" -> get(fullPath); + case "POST" -> post(fullPath); + case "PUT" -> put(fullPath); + case "PATCH" -> patch(fullPath); + default -> throw new IllegalArgumentException("Unsupported HTTP method: " + method); + }; + + when(userStateService.checkForAuthorisedUser(any())).thenReturn(allPermissionsUser()); + + mockMvc.perform(requestBuilder + .header("Authorization", "Bearer some_value") + .header("Accept", "application/xml") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andExpect(status().isNotAcceptable()); + } + + private static Stream testCasesWithValidBodiesProvider() { + return Stream.of(Arguments.of(POST, "/draft-accounts",validCreateRequestBody()), + Arguments.of(PUT, "/draft-accounts/1","{}"), + Arguments.of(PATCH, "/draft-accounts/1","{}"), + Arguments.of(GET, "/draft-accounts/1","{}") + ); + } + + +} diff --git a/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedException.java b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedException.java index fde1567ed..91377a213 100644 --- a/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedException.java +++ b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedException.java @@ -14,20 +14,26 @@ public class PermissionNotAllowedException extends RuntimeException { private final BusinessUnitUser businessUnitUser; public PermissionNotAllowedException(Permissions... value) { - super(Arrays.toString(value) + " permission(s) are not allowed for the user."); + super(Arrays.toString(value) + " permission(s) are not enabled for the user."); + this.permission = value; + this.businessUnitUser = null; + } + + public PermissionNotAllowedException(Short buIds, Permissions... value) { + super(Arrays.toString(value) + " permission(s) are not enabled in business unit: " + buIds); this.permission = value; this.businessUnitUser = null; } public PermissionNotAllowedException(Collection buIds, Permissions... value) { - super(Arrays.toString(value) + " permission(s) are not allowed for the user in business units: " + buIds); + super(Arrays.toString(value) + " permission(s) are not enabled in business units: " + buIds); this.permission = value; this.businessUnitUser = null; } public PermissionNotAllowedException(Permissions permission, BusinessUnitUser businessUnitUser) { - super(permission + " permission is not allowed for the business unit user: " + super(permission + " permission is not enabled for the business unit user: " + businessUnitUser.getBusinessUnitUserId()); this.permission = new Permissions[] {permission}; this.businessUnitUser = businessUnitUser; 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 373eb1274..52d1ccc3b 100644 --- a/src/main/java/uk/gov/hmcts/opal/controllers/DraftAccountController.java +++ b/src/main/java/uk/gov/hmcts/opal/controllers/DraftAccountController.java @@ -81,9 +81,17 @@ public ResponseEntity getDraftAccountById( log.info(":GET:getDraftAccountById: draftAccountId: {}", draftAccountId); UserState userState = userStateService.checkForAuthorisedUser(authHeaderValue); - DraftAccountEntity response = draftAccountService.getDraftAccount(draftAccountId); - - return buildResponse(Optional.ofNullable(response).map(this::toGetResponseDto).orElse(null)); + if (userState.anyBusinessUnitUserHasAnyPermission(Permissions.DRAFT_ACCOUNT_PERMISSIONS)) { + DraftAccountEntity response = draftAccountService.getDraftAccount(draftAccountId); + Short buId = response.getBusinessUnit().getBusinessUnitId(); + if (userState.hasBusinessUnitUserWithAnyPermission(buId, Permissions.DRAFT_ACCOUNT_PERMISSIONS)) { + return buildResponse(Optional.ofNullable(response).map(this::toGetResponseDto).orElse(null)); + } else { + throw new PermissionNotAllowedException(buId, Permissions.DRAFT_ACCOUNT_PERMISSIONS); + } + } else { + throw new PermissionNotAllowedException(Permissions.DRAFT_ACCOUNT_PERMISSIONS); + } } @GetMapping() @@ -115,6 +123,8 @@ public ResponseEntity getDraftAccountSummaries( log.info(":GET:getDraftAccountSummaries: summaries count: {}", response.size()); + // TODO filter responses by permissions of logged in user + return buildResponse( DraftAccountsResponseDto.builder() .summaries(response.stream().map(this::toSummaryDto).toList()).build()); diff --git a/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedExceptionTest.java b/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedExceptionTest.java index c97f95f86..925be6821 100644 --- a/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedExceptionTest.java +++ b/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedExceptionTest.java @@ -21,7 +21,7 @@ void constructor_ShouldSetMessage() { Permissions permission = Permissions.ACCOUNT_ENQUIRY_NOTES; PermissionNotAllowedException exception = new PermissionNotAllowedException(permission); - assertEquals("[" + permission + "]" + " permission(s) are not allowed for the user.", + assertEquals("[" + permission + "]" + " permission(s) are not enabled for the user.", exception.getMessage()); } @@ -31,7 +31,7 @@ void constructor2_ShouldSetMessage() { PermissionNotAllowedException exception = new PermissionNotAllowedException( permission, BusinessUnitUser.builder().businessUnitUserId("A001").build()); - assertEquals(permission + " permission is not allowed for the business unit user: A001", + assertEquals(permission + " permission is not enabled for the business unit user: A001", exception.getMessage()); } } diff --git a/src/test/java/uk/gov/hmcts/opal/controllers/DefendantAccountControllerTest.java b/src/test/java/uk/gov/hmcts/opal/controllers/DefendantAccountControllerTest.java index ba852d040..63cbf117e 100644 --- a/src/test/java/uk/gov/hmcts/opal/controllers/DefendantAccountControllerTest.java +++ b/src/test/java/uk/gov/hmcts/opal/controllers/DefendantAccountControllerTest.java @@ -7,6 +7,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import uk.gov.hmcts.opal.authorisation.model.Permissions; +import uk.gov.hmcts.opal.authorisation.model.UserState; +import uk.gov.hmcts.opal.controllers.util.UserStateUtil; import uk.gov.hmcts.opal.dto.AccountDetailsDto; import uk.gov.hmcts.opal.dto.AccountEnquiryDto; import uk.gov.hmcts.opal.dto.AddNoteDto; @@ -26,7 +29,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static uk.gov.hmcts.opal.controllers.UserStateBuilder.createUserState; @ExtendWith(MockitoExtension.class) class DefendantAccountControllerTest { @@ -143,9 +145,11 @@ public void testPostDefendantAccountSearch_Success() { public void testAddNote_Success() { // Arrange NoteDto mockResponse = new NoteDto(); + UserState userState = UserStateUtil.permissionUser( + (short)50, Permissions.ACCOUNT_ENQUIRY, Permissions.ACCOUNT_ENQUIRY_NOTES); when(noteService.saveNote(any(NoteDto.class))).thenReturn(mockResponse); - when(userStateService.getUserStateUsingAuthToken(any())).thenReturn(createUserState()); + when(userStateService.getUserStateUsingAuthToken(any())).thenReturn(userState); // Act AddNoteDto addNote = AddNoteDto.builder().businessUnitId((short) 50).build(); @@ -162,9 +166,11 @@ public void testAddNote_Success() { @Test public void testAddNote_NoContent() { // Arrange + UserState userState = UserStateUtil.permissionUser( + (short)50, Permissions.ACCOUNT_ENQUIRY, Permissions.ACCOUNT_ENQUIRY_NOTES); when(noteService.saveNote(any(NoteDto.class))).thenReturn(null); - when(userStateService.getUserStateUsingAuthToken(any())).thenReturn(createUserState()); + when(userStateService.getUserStateUsingAuthToken(any())).thenReturn(userState); // Act AddNoteDto addNote = AddNoteDto.builder().businessUnitId((short) 50).build(); 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 4d4c1bb3e..cee48318d 100644 --- a/src/test/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerTest.java +++ b/src/test/java/uk/gov/hmcts/opal/controllers/DraftAccountControllerTest.java @@ -9,6 +9,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import uk.gov.hmcts.opal.authorisation.model.UserState; +import uk.gov.hmcts.opal.controllers.util.UserStateUtil; import uk.gov.hmcts.opal.dto.AddDraftAccountRequestDto; import uk.gov.hmcts.opal.dto.DraftAccountResponseDto; import uk.gov.hmcts.opal.dto.DraftAccountSummaryDto; @@ -52,9 +53,10 @@ class DraftAccountControllerTest { void testGetDraftAccount_Success() { // Arrange DraftAccountEntity entity = DraftAccountEntity.builder() - .businessUnit(BusinessUnitEntity.builder().build()) + .businessUnit(BusinessUnitEntity.builder().businessUnitId((short)1).build()) .build(); + when(userStateService.checkForAuthorisedUser(any())).thenReturn(UserStateUtil.allPermissionsUser()); when(draftAccountService.getDraftAccount(any(Long.class))).thenReturn(entity); // Act diff --git a/src/test/java/uk/gov/hmcts/opal/controllers/UserStateBuilder.java b/src/test/java/uk/gov/hmcts/opal/controllers/UserStateBuilder.java deleted file mode 100644 index 0fe824413..000000000 --- a/src/test/java/uk/gov/hmcts/opal/controllers/UserStateBuilder.java +++ /dev/null @@ -1,52 +0,0 @@ -package uk.gov.hmcts.opal.controllers; - -import uk.gov.hmcts.opal.authorisation.model.BusinessUnitUser; -import uk.gov.hmcts.opal.authorisation.model.Permission; -import uk.gov.hmcts.opal.authorisation.model.Permissions; -import uk.gov.hmcts.opal.authorisation.model.UserState; - -import java.util.Set; - -public class UserStateBuilder { - - public static UserState createUserState() { - return createUserState(Set.of( - createBusinessUnitUser(Set.of( - createPermission( - Permissions.ACCOUNT_ENQUIRY_NOTES.id, - Permissions.ACCOUNT_ENQUIRY_NOTES.description - ), - createPermission( - Permissions.ACCOUNT_ENQUIRY.id, - Permissions.ACCOUNT_ENQUIRY.description - ) - )))); - } - - public static UserState createUserState(Set businessUnitUser) { - return UserState.builder() - .userId(345L) - .userName("John Smith") - .businessUnitUser(businessUnitUser) - .build(); - } - - public static BusinessUnitUser createBusinessUnitUser(Set permissions) { - return BusinessUnitUser.builder() - .businessUnitUserId("JK0320") - .businessUnitId((short)50) - .permissions(permissions) - .build(); - } - - public static Set createSinglePermissions(long id) { - return Set.of(createPermission(id, "any desc")); - } - - public static Permission createPermission(long id, String desc) { - return Permission.builder() - .permissionId(id) - .permissionName("Do Stuff") - .build(); - } -} diff --git a/src/test/java/uk/gov/hmcts/opal/controllers/develop/NoteControllerTest.java b/src/test/java/uk/gov/hmcts/opal/controllers/develop/NoteControllerTest.java index ad594f16d..28a614bc7 100644 --- a/src/test/java/uk/gov/hmcts/opal/controllers/develop/NoteControllerTest.java +++ b/src/test/java/uk/gov/hmcts/opal/controllers/develop/NoteControllerTest.java @@ -8,6 +8,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import uk.gov.hmcts.opal.authorisation.model.Permissions; +import uk.gov.hmcts.opal.authorisation.model.UserState; +import uk.gov.hmcts.opal.controllers.util.UserStateUtil; import uk.gov.hmcts.opal.dto.NoteDto; import uk.gov.hmcts.opal.dto.search.NoteSearchDto; import uk.gov.hmcts.opal.service.opal.NoteService; @@ -22,7 +25,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static uk.gov.hmcts.opal.controllers.UserStateBuilder.createUserState; @ExtendWith(MockitoExtension.class) class NoteControllerTest { @@ -44,9 +46,11 @@ void testCreateNote_Success() { HttpServletRequest request = mock(HttpServletRequest.class); NoteDto noteDtoRequest = NoteDto.builder().businessUnitId((short) 50).build(); NoteDto noteDtoResponse = NoteDto.builder().noteId(1L).build(); + UserState userState = UserStateUtil.permissionUser( + (short)50, Permissions.ACCOUNT_ENQUIRY, Permissions.ACCOUNT_ENQUIRY_NOTES); when(noteService.saveNote(any(NoteDto.class))).thenReturn(noteDtoResponse); - when(userStateService.getUserStateUsingAuthToken(any())).thenReturn(createUserState()); + when(userStateService.getUserStateUsingAuthToken(any())).thenReturn(userState); // Act ResponseEntity response = noteController.createNote(noteDtoRequest, BEARER_TOKEN); diff --git a/src/test/java/uk/gov/hmcts/opal/controllers/util/UserStateUtil.java b/src/test/java/uk/gov/hmcts/opal/controllers/util/UserStateUtil.java new file mode 100644 index 000000000..6ceb3fb5e --- /dev/null +++ b/src/test/java/uk/gov/hmcts/opal/controllers/util/UserStateUtil.java @@ -0,0 +1,72 @@ +package uk.gov.hmcts.opal.controllers.util; + +import uk.gov.hmcts.opal.authorisation.model.BusinessUnitUser; +import uk.gov.hmcts.opal.authorisation.model.Permission; +import uk.gov.hmcts.opal.authorisation.model.Permissions; +import uk.gov.hmcts.opal.authorisation.model.UserState; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +public class UserStateUtil { + + public static final UserState noPermissionsUser() { + return UserState.builder() + .userId(999L) + .userName("no-permissions@users.com") + .businessUnitUser(Collections.emptySet()) + .build(); + } + + public static final UserState allPermissionsUser() { + return new UserState.DeveloperUserState(); + } + + public static final UserState permissionUser(Short buid, Permissions... permissions) { + return UserState.builder() + .userId(1L) + .userName("normal@users.com") + .businessUnitUser(Set.of(permissions(buid, permissionsFor(permissions)))) + .build(); + } + + public static final UserState permissionUser(Short buid, Permission... permissions) { + return UserState.builder() + .userId(1L) + .userName("normal@users.com") + .businessUnitUser(Set.of(permissions(buid, permissions))) + .build(); + } + + public static final UserState permissionUser(Set permissions) { + return UserState.builder() + .userId(1L) + .userName("normal@users.com") + .businessUnitUser(permissions) + .build(); + } + + public static final BusinessUnitUser permissions(Short buid, Permission... permissions) { + return permissions(buid, new HashSet<>(Arrays.asList(permissions))); + } + + public static final BusinessUnitUser permissions(Short buid, Set permissions) { + return BusinessUnitUser.builder() + .businessUnitUserId("USER01") + .businessUnitId(buid) + .permissions(permissions) + .build(); + } + + public static final Set permissionsFor(Permissions... permissions) { + return Arrays.stream(permissions).map(p -> new Permission(p.id, p.description)).collect(Collectors.toSet()); + } + + public static final Permission permissionFor(Permissions p) { + return new Permission(p.id, p.description); + } + +} diff --git a/src/test/java/uk/gov/hmcts/opal/util/PermissionUtilTest.java b/src/test/java/uk/gov/hmcts/opal/util/PermissionUtilTest.java index 06376ca89..9aec4a4f8 100644 --- a/src/test/java/uk/gov/hmcts/opal/util/PermissionUtilTest.java +++ b/src/test/java/uk/gov/hmcts/opal/util/PermissionUtilTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDeniedException; import uk.gov.hmcts.opal.authorisation.model.BusinessUnitUser; +import uk.gov.hmcts.opal.authorisation.model.Permission; import uk.gov.hmcts.opal.authorisation.model.Permissions; import uk.gov.hmcts.opal.authorisation.model.UserState; @@ -12,11 +13,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static uk.gov.hmcts.opal.controllers.UserStateBuilder.createBusinessUnitUser; -import static uk.gov.hmcts.opal.controllers.UserStateBuilder.createSinglePermissions; -import static uk.gov.hmcts.opal.controllers.UserStateBuilder.createUserState; -class PermissionUtilTest { +public class PermissionUtilTest { @Test void testCheckBusinessUnitUserHasPermission_success() { @@ -71,4 +69,31 @@ void testCheckAnyBusinessUnitUserHasPermission_fail2() { () -> PermissionUtil.checkAnyBusinessUnitUserHasPermission(userState, permission)); assertEquals("User does not have the required permission: Account Enquiry", ade.getMessage()); } + + private static UserState createUserState(Set businessUnitUser) { + return UserState.builder() + .userId(345L) + .userName("John Smith") + .businessUnitUser(businessUnitUser) + .build(); + } + + private static BusinessUnitUser createBusinessUnitUser(Set permissions) { + return BusinessUnitUser.builder() + .businessUnitUserId("JK0320") + .businessUnitId((short)50) + .permissions(permissions) + .build(); + } + + private static Set createSinglePermissions(long id) { + return Set.of(createPermission(id, "any desc")); + } + + private static Permission createPermission(long id, String desc) { + return Permission.builder() + .permissionId(id) + .permissionName("Do Stuff") + .build(); + } }