diff --git a/src/main/java/uk/gov/hmcts/ccd/domain/service/createevent/MidEventCallback.java b/src/main/java/uk/gov/hmcts/ccd/domain/service/createevent/MidEventCallback.java index 01c81ad669..ffcd14f899 100644 --- a/src/main/java/uk/gov/hmcts/ccd/domain/service/createevent/MidEventCallback.java +++ b/src/main/java/uk/gov/hmcts/ccd/domain/service/createevent/MidEventCallback.java @@ -20,6 +20,7 @@ import uk.gov.hmcts.ccd.domain.model.definition.WizardPage; import uk.gov.hmcts.ccd.domain.model.std.CaseDataContent; import uk.gov.hmcts.ccd.domain.model.std.Event; +import uk.gov.hmcts.ccd.domain.service.casedeletion.TimeToLiveService; import uk.gov.hmcts.ccd.domain.service.common.CaseService; import uk.gov.hmcts.ccd.domain.service.common.EventTriggerService; import uk.gov.hmcts.ccd.domain.service.stdapi.CallbackInvoker; @@ -35,19 +36,22 @@ public class MidEventCallback { private final EventTriggerService eventTriggerService; private final CaseDefinitionRepository caseDefinitionRepository; private final CaseService caseService; + private final TimeToLiveService timeToLiveService; @Autowired public MidEventCallback(CallbackInvoker callbackInvoker, UIDefinitionRepository uiDefinitionRepository, EventTriggerService eventTriggerService, @Qualifier(CachedCaseDefinitionRepository.QUALIFIER) - CaseDefinitionRepository caseDefinitionRepository, - CaseService caseService) { + CaseDefinitionRepository caseDefinitionRepository, + CaseService caseService, + TimeToLiveService timeToLiveService) { this.callbackInvoker = callbackInvoker; this.uiDefinitionRepository = uiDefinitionRepository; this.eventTriggerService = eventTriggerService; this.caseDefinitionRepository = caseDefinitionRepository; this.caseService = caseService; + this.timeToLiveService = timeToLiveService; } @Transactional @@ -85,6 +89,7 @@ public Map invoke(String caseTypeId, CaseDataContent content, } removeNextPageFieldData(currentOrNewCaseDetails, wizardPageOptional.get().getOrder(), caseTypeId, event.getEventId()); + applyTtlIncrementIfConfigured(currentOrNewCaseDetails, caseEventDefinition, caseTypeDefinition); CaseDetails caseDetailsFromMidEventCallback = callbackInvoker.invokeMidEventCallback(wizardPageOptional.get(), @@ -130,4 +135,13 @@ private CaseEventDefinition getCaseEvent(Event event, CaseTypeDefinition caseTyp } return caseEventDefinition; } + + private void applyTtlIncrementIfConfigured(CaseDetails caseDetails, + CaseEventDefinition caseEventDefinition, + CaseTypeDefinition caseTypeDefinition) { + if (timeToLiveService.isCaseTypeUsingTTL(caseTypeDefinition)) { + caseDetails.setData(timeToLiveService.updateCaseDetailsWithTTL( + caseDetails.getData(), caseEventDefinition, caseTypeDefinition)); + } + } } diff --git a/src/main/java/uk/gov/hmcts/ccd/domain/service/validate/AuthorisedValidateCaseFieldsOperation.java b/src/main/java/uk/gov/hmcts/ccd/domain/service/validate/AuthorisedValidateCaseFieldsOperation.java index 3b11704092..299a00d473 100644 --- a/src/main/java/uk/gov/hmcts/ccd/domain/service/validate/AuthorisedValidateCaseFieldsOperation.java +++ b/src/main/java/uk/gov/hmcts/ccd/domain/service/validate/AuthorisedValidateCaseFieldsOperation.java @@ -7,22 +7,35 @@ import org.springframework.stereotype.Service; import uk.gov.hmcts.ccd.ApplicationParams; import uk.gov.hmcts.ccd.config.JacksonUtils; +import uk.gov.hmcts.ccd.data.casedetails.CachedCaseDetailsRepository; +import uk.gov.hmcts.ccd.data.casedetails.CaseDetailsRepository; import uk.gov.hmcts.ccd.data.definition.CachedCaseDefinitionRepository; import uk.gov.hmcts.ccd.data.definition.CaseDefinitionRepository; +import uk.gov.hmcts.ccd.domain.model.callbacks.EventTokenProperties; import uk.gov.hmcts.ccd.domain.model.casedataaccesscontrol.AccessProfile; +import uk.gov.hmcts.ccd.domain.model.definition.CaseDetails; import uk.gov.hmcts.ccd.domain.model.definition.CaseTypeDefinition; import uk.gov.hmcts.ccd.domain.model.std.CaseDataContent; +import uk.gov.hmcts.ccd.domain.model.std.Event; +import uk.gov.hmcts.ccd.domain.service.callbacks.EventTokenService; import uk.gov.hmcts.ccd.domain.service.common.AccessControlService; import uk.gov.hmcts.ccd.domain.service.common.CaseAccessService; import uk.gov.hmcts.ccd.domain.service.common.ConditionalFieldRestorer; import uk.gov.hmcts.ccd.domain.service.createevent.MidEventCallback; +import uk.gov.hmcts.ccd.domain.service.getcase.GetCaseOperation; +import uk.gov.hmcts.ccd.endpoint.exceptions.ResourceNotFoundException; import uk.gov.hmcts.ccd.endpoint.exceptions.ValidationException; import java.util.Map; import java.util.Set; import static com.google.common.collect.Maps.newHashMap; +import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.CAN_CREATE; import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.CAN_READ; +import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.CAN_UPDATE; +import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.NO_CASE_STATE_FOUND; +import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.NO_CASE_TYPE_FOUND; +import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.NO_EVENT_FOUND; @Service @Slf4j @@ -37,6 +50,9 @@ public class AuthorisedValidateCaseFieldsOperation implements ValidateCaseFields private final ConditionalFieldRestorer conditionalFieldRestorer; private final ApplicationParams applicationParams; private final MidEventCallback midEventCallback; + private final GetCaseOperation getCaseOperation; + private final EventTokenService eventTokenService; + private final CaseDetailsRepository caseDetailsRepository; public AuthorisedValidateCaseFieldsOperation(AccessControlService accessControlService, @Qualifier(CachedCaseDefinitionRepository.QUALIFIER) @@ -46,7 +62,11 @@ public AuthorisedValidateCaseFieldsOperation(AccessControlService accessControlS ValidateCaseFieldsOperation validateCaseFieldsOperation, ConditionalFieldRestorer conditionalFieldRestorer, ApplicationParams applicationParams, - MidEventCallback midEventCallback) { + MidEventCallback midEventCallback, + @Qualifier("default") GetCaseOperation getCaseOperation, + EventTokenService eventTokenService, + @Qualifier(CachedCaseDetailsRepository.QUALIFIER) + CaseDetailsRepository caseDetailsRepository) { this.accessControlService = accessControlService; this.caseDefinitionRepository = caseDefinitionRepository; this.caseAccessService = caseAccessService; @@ -54,6 +74,9 @@ public AuthorisedValidateCaseFieldsOperation(AccessControlService accessControlS this.conditionalFieldRestorer = conditionalFieldRestorer; this.applicationParams = applicationParams; this.midEventCallback = midEventCallback; + this.getCaseOperation = getCaseOperation; + this.eventTokenService = eventTokenService; + this.caseDetailsRepository = caseDetailsRepository; } @Override @@ -64,8 +87,18 @@ public Map validateCaseDetails(OperationContext operationConte String caseTypeId = operationContext.caseTypeId(); String pageId = operationContext.pageId(); + resolveCaseReferenceFromEventToken(content); + + if (StringUtils.isNotBlank(pageId)) { + verifyEventIsPresent(content); + } + callMidEventCallback(caseTypeId, content, pageId); + if (StringUtils.isNotBlank(pageId)) { + verifyEventAccessAfterMidEvent(operationContext, content); + } + if (applicationParams.getExcludeVerifyAccessCaseTypesForValidate() .stream() .anyMatch(c -> c.equalsIgnoreCase(caseTypeId))) { @@ -90,6 +123,140 @@ public Map validateCaseDetails(OperationContext operationConte return content.getData(); } + private void verifyEventIsPresent(CaseDataContent content) { + Event event = content.getEvent(); + if (event == null || StringUtils.isEmpty(event.getEventId())) { + throw new ResourceNotFoundException(NO_EVENT_FOUND); + } + } + + private void verifyEventAccessAfterMidEvent(OperationContext operationContext, CaseDataContent content) { + String caseTypeId = operationContext.caseTypeId(); + + final CaseTypeDefinition caseTypeDefinition = getCaseDefinitionType(caseTypeId); + + if (StringUtils.isEmpty(content.getCaseReference())) { + if (hasUnresolvedCaseIdInEventToken(content)) { + throw new ResourceNotFoundException("Cannot find matching start trigger"); + } + verifyCreateCaseEventAccess(content, caseTypeDefinition); + } else { + verifyUpdateCaseEventAccess(content); + } + } + + private boolean hasUnresolvedCaseIdInEventToken(CaseDataContent content) { + if (StringUtils.isEmpty(content.getToken()) || StringUtils.isNotEmpty(content.getCaseReference())) { + return false; + } + try { + EventTokenProperties eventTokenProperties = eventTokenService.parseToken(content.getToken()); + return StringUtils.isNotEmpty(eventTokenProperties.getCaseId()); + } catch (RuntimeException e) { + log.debug("Unable to determine case id from event token: {}", e.getMessage()); + return false; + } + } + + private void resolveCaseReferenceFromEventToken(CaseDataContent content) { + if (StringUtils.isNotEmpty(content.getCaseReference()) || StringUtils.isEmpty(content.getToken())) { + return; + } + try { + EventTokenProperties eventTokenProperties = eventTokenService.parseToken(content.getToken()); + if (StringUtils.isNotEmpty(eventTokenProperties.getCaseId())) { + content.setCaseReference(toCaseReference(eventTokenProperties.getCaseId())); + } + } catch (RuntimeException e) { + log.debug("Unable to resolve case reference from event token: {}", e.getMessage()); + } + } + + private String toCaseReference(String caseIdFromToken) { + if (StringUtils.isEmpty(caseIdFromToken)) { + return caseIdFromToken; + } + try { + if (getCaseOperation.execute(caseIdFromToken).isPresent()) { + return caseIdFromToken; + } + } catch (RuntimeException e) { + log.debug("Unable to load case by reference from event token: {}", e.getMessage()); + } + try { + CaseDetails caseDetails = caseDetailsRepository.findById(Long.valueOf(caseIdFromToken)); + if (caseDetails != null && StringUtils.isNotEmpty(caseDetails.getReferenceAsString())) { + return caseDetails.getReferenceAsString(); + } + } catch (NumberFormatException e) { + log.debug("Case id from event token is not a numeric entity id: {}", caseIdFromToken); + } catch (RuntimeException e) { + log.debug("Unable to load case by entity id from event token: {}", e.getMessage()); + } + return caseIdFromToken; + } + + private void verifyCreateCaseEventAccess(CaseDataContent content, CaseTypeDefinition caseTypeDefinition) { + Set userRoles = caseAccessService.getCaseCreationRoles(caseTypeDefinition.getId()); + if (userRoles == null || userRoles.isEmpty()) { + throw new ValidationException("Cannot find user roles for the user"); + } + if (!accessControlService.canAccessCaseTypeWithCriteria( + caseTypeDefinition, + userRoles, + CAN_CREATE)) { + throw new ResourceNotFoundException(NO_CASE_TYPE_FOUND); + } + if (!accessControlService.canAccessCaseEventWithCriteria( + content.getEvent().getEventId(), + caseTypeDefinition.getEvents(), + userRoles, + CAN_CREATE)) { + throw new ResourceNotFoundException(NO_EVENT_FOUND); + } + } + + private void verifyUpdateCaseEventAccess(CaseDataContent content) { + String caseReference = content.getCaseReference(); + CaseDetails existingCaseDetails = getCaseOperation.execute(caseReference) + .orElseThrow(() -> new ResourceNotFoundException("Case not found")); + + final CaseTypeDefinition caseTypeDefinition = + getCaseDefinitionType(existingCaseDetails.getCaseTypeId()); + + String caseReferenceForAccess = existingCaseDetails.getReferenceAsString(); + Set accessProfiles = + caseAccessService.getAccessProfilesByCaseReference(caseReferenceForAccess); + if (accessProfiles == null || accessProfiles.isEmpty()) { + throw new ValidationException("Cannot find user roles for the user"); + } + + verifyCaseTypeAndStateAccessForUpdate(existingCaseDetails, caseTypeDefinition, accessProfiles); + + if (!accessControlService.canAccessCaseEventWithCriteria( + content.getEvent().getEventId(), + caseTypeDefinition.getEvents(), + accessProfiles, + CAN_CREATE)) { + throw new ResourceNotFoundException(NO_EVENT_FOUND); + } + } + + private void verifyCaseTypeAndStateAccessForUpdate(CaseDetails existingCaseDetails, + CaseTypeDefinition caseTypeDefinition, + Set accessProfiles) { + if (!accessControlService.canAccessCaseTypeWithCriteria(caseTypeDefinition, accessProfiles, CAN_UPDATE)) { + throw new ResourceNotFoundException(NO_CASE_TYPE_FOUND); + } + if (!accessControlService.canAccessCaseStateWithCriteria( + existingCaseDetails.getState(), + caseTypeDefinition, + accessProfiles, + CAN_UPDATE)) { + throw new ResourceNotFoundException(NO_CASE_STATE_FOUND); + } + } + private void callMidEventCallback(String caseTypeId, CaseDataContent content, String pageId) { content.setData(midEventCallback.invoke(caseTypeId, content, pageId)); } diff --git a/src/test/java/uk/gov/hmcts/ccd/domain/service/createevent/MidEventCallbackTest.java b/src/test/java/uk/gov/hmcts/ccd/domain/service/createevent/MidEventCallbackTest.java index 6c15740763..ac56dfc2d8 100644 --- a/src/test/java/uk/gov/hmcts/ccd/domain/service/createevent/MidEventCallbackTest.java +++ b/src/test/java/uk/gov/hmcts/ccd/domain/service/createevent/MidEventCallbackTest.java @@ -23,6 +23,7 @@ import uk.gov.hmcts.ccd.domain.model.definition.WizardPageField; import uk.gov.hmcts.ccd.domain.model.std.CaseDataContent; import uk.gov.hmcts.ccd.domain.model.std.Event; +import uk.gov.hmcts.ccd.domain.service.casedeletion.TimeToLiveService; import uk.gov.hmcts.ccd.domain.service.common.CaseService; import uk.gov.hmcts.ccd.domain.service.common.EventTriggerService; import uk.gov.hmcts.ccd.domain.service.stdapi.CallbackInvoker; @@ -65,6 +66,8 @@ class MidEventCallbackTest { private UIDefinitionRepository uiDefinitionRepository; @Mock private CaseService caseService; + @Mock + private TimeToLiveService timeToLiveService; @Mock private CaseEventDefinition caseEventDefinition; @@ -93,6 +96,7 @@ void setUp() { WizardPage wizardPageWithoutCallback = createWizardPage("createCase2"); given(uiDefinitionRepository.getWizardPageCollection(CASE_TYPE_ID, event.getEventId())) .willReturn(asList(wizardPageWithCallback, wizardPageWithoutCallback)); + given(timeToLiveService.isCaseTypeUsingTTL(caseTypeDefinition)).willReturn(false); } @Test @@ -439,6 +443,40 @@ void shouldFilterCaseDataContentWhenWizardPageOrderExists() { IGNORE_WARNINGS); } + @Test + @DisplayName("should apply TTL increment before invoking mid event callback for an existing case") + void shouldApplyTtlIncrementBeforeInvokingMidEventCallbackForExistingCase() { + Map populatedData = new HashMap<>(data); + populatedData.put("SystemTTLField", new TextNode("2036-05-18")); + CaseDetails existingCaseDetails = caseDetails(data); + CaseDetails populatedCaseDetails = caseDetails(populatedData); + + CaseDataContent content = newCaseDataContent() + .withEvent(event) + .withData(data) + .withCaseReference(CASE_REFERENCE) + .withIgnoreWarning(IGNORE_WARNINGS) + .build(); + + given(caseService.getCaseDetails(JURISDICTION_ID, CASE_REFERENCE)).willReturn(existingCaseDetails); + given(caseService.clone(existingCaseDetails)).willReturn(existingCaseDetails); + given(caseService.populateCurrentCaseDetailsWithEventFields(content, existingCaseDetails)) + .willReturn(populatedCaseDetails); + given(timeToLiveService.isCaseTypeUsingTTL(caseTypeDefinition)).willReturn(true); + given(timeToLiveService.updateCaseDetailsWithTTL(populatedData, caseEventDefinition, caseTypeDefinition)) + .willReturn(populatedData); + given(callbackInvoker.invokeMidEventCallback(wizardPageWithCallback, + caseTypeDefinition, + caseEventDefinition, + existingCaseDetails, + populatedCaseDetails, + IGNORE_WARNINGS)).willReturn(populatedCaseDetails); + + midEventCallback.invoke(CASE_TYPE_ID, content, "createCase1"); + + verify(timeToLiveService).updateCaseDetailsWithTTL(populatedData, caseEventDefinition, caseTypeDefinition); + } + private Map createData() { Map data = new HashMap<>(); data.put("createCase2_field1", new TextNode("test1")); diff --git a/src/test/java/uk/gov/hmcts/ccd/domain/service/validate/AuthorisedValidateCaseFieldsOperationTest.java b/src/test/java/uk/gov/hmcts/ccd/domain/service/validate/AuthorisedValidateCaseFieldsOperationTest.java index 6c4e03acba..9eae836ca0 100644 --- a/src/test/java/uk/gov/hmcts/ccd/domain/service/validate/AuthorisedValidateCaseFieldsOperationTest.java +++ b/src/test/java/uk/gov/hmcts/ccd/domain/service/validate/AuthorisedValidateCaseFieldsOperationTest.java @@ -3,28 +3,37 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import uk.gov.hmcts.ccd.ApplicationParams; import uk.gov.hmcts.ccd.config.JacksonUtils; +import uk.gov.hmcts.ccd.data.casedetails.CaseDetailsRepository; import uk.gov.hmcts.ccd.data.definition.CaseDefinitionRepository; +import uk.gov.hmcts.ccd.domain.model.callbacks.EventTokenProperties; import uk.gov.hmcts.ccd.domain.model.casedataaccesscontrol.AccessProfile; +import uk.gov.hmcts.ccd.domain.model.definition.CaseDetails; import uk.gov.hmcts.ccd.domain.model.definition.CaseTypeDefinition; import uk.gov.hmcts.ccd.domain.model.std.CaseDataContent; +import uk.gov.hmcts.ccd.domain.model.std.Event; +import uk.gov.hmcts.ccd.domain.service.callbacks.EventTokenService; import uk.gov.hmcts.ccd.domain.service.common.AccessControlService; import uk.gov.hmcts.ccd.domain.service.common.CaseAccessService; import uk.gov.hmcts.ccd.domain.service.common.ConditionalFieldRestorer; import uk.gov.hmcts.ccd.domain.service.createevent.MidEventCallback; +import uk.gov.hmcts.ccd.domain.service.getcase.GetCaseOperation; +import uk.gov.hmcts.ccd.endpoint.exceptions.BadRequestException; +import uk.gov.hmcts.ccd.endpoint.exceptions.ResourceNotFoundException; import uk.gov.hmcts.ccd.endpoint.exceptions.ValidationException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import static java.util.Collections.emptyMap; @@ -38,10 +47,18 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static uk.gov.hmcts.ccd.config.JacksonUtils.DATA; +import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.CAN_CREATE; +import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.CAN_READ; +import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.CAN_UPDATE; +import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.NO_CASE_STATE_FOUND; +import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.NO_CASE_TYPE_FOUND; +import static uk.gov.hmcts.ccd.domain.service.common.AccessControlService.NO_EVENT_FOUND; class AuthorisedValidateCaseFieldsOperationTest { private static final JsonNodeFactory JSON_NODE_FACTORY = new JsonNodeFactory(false); @@ -49,6 +66,7 @@ class AuthorisedValidateCaseFieldsOperationTest { private static final String PAGE_ID = "1"; private static final String USER_ROLE_1 = "user-role-1"; private static final String CASE_REFERENCE = "1234123412341234"; + private static final String EVENT_ID = "testEvent"; @Mock private AccessControlService accessControlService; @@ -71,7 +89,15 @@ class AuthorisedValidateCaseFieldsOperationTest { @Mock private MidEventCallback midEventCallback; - @InjectMocks + @Mock + private GetCaseOperation getCaseOperation; + + @Mock + private EventTokenService eventTokenService; + + @Mock + private CaseDetailsRepository caseDetailsRepository; + private AuthorisedValidateCaseFieldsOperation authorisedValidateCaseFieldsOperation; AutoCloseable openMocks; @@ -79,15 +105,48 @@ class AuthorisedValidateCaseFieldsOperationTest { @BeforeEach void setUp() { openMocks = MockitoAnnotations.openMocks(this); + authorisedValidateCaseFieldsOperation = new AuthorisedValidateCaseFieldsOperation( + accessControlService, + caseDefinitionRepository, + caseAccessService, + validateCaseFieldsOperation, + conditionalFieldRestorer, + applicationParams, + midEventCallback, + getCaseOperation, + eventTokenService, + caseDetailsRepository + ); CaseTypeDefinition caseTypeDefinition = new CaseTypeDefinition(); + caseTypeDefinition.setId(CASE_TYPE_ID); when(caseDefinitionRepository.getCaseType(anyString())).thenReturn(caseTypeDefinition); + + CaseDetails loadedCase = new CaseDetails(); + loadedCase.setReference(Long.valueOf(CASE_REFERENCE)); + loadedCase.setCaseTypeId(CASE_TYPE_ID); + loadedCase.setState("Open"); + loadedCase.setData(new HashMap<>()); + when(getCaseOperation.execute(eq(CASE_REFERENCE))).thenReturn(Optional.of(loadedCase)); + + when(caseAccessService.getCaseCreationRoles(anyString())).thenReturn( + Set.of(AccessProfile.builder().accessProfile(USER_ROLE_1).build())); + when(caseAccessService.getAccessProfilesByCaseReference(eq(CASE_REFERENCE))).thenReturn( + Set.of(AccessProfile.builder().accessProfile(USER_ROLE_1).build())); + + when(accessControlService.canAccessCaseTypeWithCriteria(any(), any(), any())).thenReturn(true); + when(accessControlService.canAccessCaseEventWithCriteria(anyString(), any(), any(), any())).thenReturn(true); + when(accessControlService.canAccessCaseFieldsWithCriteria(any(), any(), any(), any())).thenReturn(true); + when(accessControlService.canAccessCaseStateWithCriteria(anyString(), any(), any(), any())).thenReturn(true); + + when(applicationParams.getExcludeVerifyAccessCaseTypesForValidate()).thenReturn(List.of()); } @Test @DisplayName("should Skip VerifyAccess When CaseTypeId Is Excluded") void shouldSkipVerifyAccessWhenCaseTypeIdIsExcluded() { CaseDataContent content = new CaseDataContent(); + attachEvent(content); content.setCaseReference(CASE_REFERENCE); Map inputData = new HashMap<>(); inputData.put("field1", JSON_NODE_FACTORY.textNode("value1")); @@ -111,8 +170,8 @@ void shouldSkipVerifyAccessWhenCaseTypeIdIsExcluded() { () -> assertNotEquals(inputData, result), () -> assertTrue(result.containsKey("data")), () -> assertEquals("value1", result.get("data").get("field1").asText()), - () -> verify(caseAccessService, never()).getAccessProfilesByCaseReference(anyString()), - () -> verify(caseDefinitionRepository, never()).getCaseType(anyString()) + () -> verify(caseAccessService).getAccessProfilesByCaseReference(CASE_REFERENCE), + () -> verify(caseDefinitionRepository, times(2)).getCaseType(CASE_TYPE_ID) ); } @@ -120,6 +179,7 @@ void shouldSkipVerifyAccessWhenCaseTypeIdIsExcluded() { @DisplayName("should Continue VerifyAccess When CaseTypeId Not Excluded") void shouldContinueVerifyAccessWhenCaseTypeIdNotExcluded() { CaseDataContent content = new CaseDataContent(); + attachEvent(content); content.setCaseReference(CASE_REFERENCE); content.setData(new HashMap<>()); @@ -153,39 +213,37 @@ void shouldContinueVerifyAccessWhenCaseTypeIdNotExcluded() { assertAll( () -> verify(validateCaseFieldsOperation).validateCaseDetails(operationContext), - () -> verify(caseAccessService).getAccessProfilesByCaseReference(CASE_REFERENCE), - () -> verify(caseDefinitionRepository).getCaseType(CASE_TYPE_ID), + () -> verify(caseAccessService, times(2)).getAccessProfilesByCaseReference(CASE_REFERENCE), + () -> verify(caseDefinitionRepository, times(3)).getCaseType(CASE_TYPE_ID), () -> assertNotNull(result), () -> assertEquals("filtered_value1", result.get(DATA).get("filtered_field1").asText()) ); } @Test - @DisplayName("should Return Empty CaseDetails With No Access Profile") - void shouldReturnEmptyCaseDetailsWithNoAccessProfile() { + @DisplayName("should reject validate when user has no access profiles for case") + void shouldRejectValidateWhenUserHasNoAccessProfilesForCase() { CaseDataContent content = new CaseDataContent(); + attachEvent(content); content.setCaseReference(CASE_REFERENCE); content.setData(emptyMap()); OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); when(caseAccessService.getAccessProfilesByCaseReference(anyString())).thenReturn(Set.of()); + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(emptyMap()); - Map result = authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext); + assertThrows(ValidationException.class, + () -> authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext)); - assertAll( - () -> verify(validateCaseFieldsOperation).validateCaseDetails(operationContext), - () -> verify(caseAccessService).getAccessProfilesByCaseReference(CASE_REFERENCE), - () -> verify(caseDefinitionRepository).getCaseType(CASE_TYPE_ID), - () -> assertNotNull(result), - () -> assertTrue(result.containsKey(DATA)) - ); + verify(midEventCallback).invoke(CASE_TYPE_ID, content, PAGE_ID); } @Test @DisplayName("should Return CaseDetails With Access Profile") void shouldReturnCaseDetailsWithAccessProfile() { CaseDataContent content = new CaseDataContent(); + attachEvent(content); content.setCaseReference(CASE_REFERENCE); content.setData(JacksonUtils.convertValue(new ObjectNode(null))); @@ -211,8 +269,8 @@ void shouldReturnCaseDetailsWithAccessProfile() { assertAll( () -> verify(validateCaseFieldsOperation).validateCaseDetails(operationContext), - () -> verify(caseAccessService).getAccessProfilesByCaseReference(CASE_REFERENCE), - () -> verify(caseDefinitionRepository).getCaseType(CASE_TYPE_ID), + () -> verify(caseAccessService, times(2)).getAccessProfilesByCaseReference(CASE_REFERENCE), + () -> verify(caseDefinitionRepository, times(3)).getCaseType(CASE_TYPE_ID), () -> assertNotNull(result), () -> assertTrue(result.containsKey(DATA)), () -> assertEquals(2, result.get(DATA).size()) @@ -223,6 +281,7 @@ void shouldReturnCaseDetailsWithAccessProfile() { @DisplayName("should Return CaseDetails With Restored Missing Field") void shouldReturnCaseDetailsWithRestoredMissingField() { CaseDataContent content = new CaseDataContent(); + attachEvent(content); content.setCaseReference(CASE_REFERENCE); content.setData(emptyMap()); @@ -254,8 +313,8 @@ void shouldReturnCaseDetailsWithRestoredMissingField() { assertAll( () -> verify(validateCaseFieldsOperation).validateCaseDetails(operationContext), - () -> verify(caseAccessService).getAccessProfilesByCaseReference(CASE_REFERENCE), - () -> verify(caseDefinitionRepository).getCaseType(CASE_TYPE_ID), + () -> verify(caseAccessService, times(2)).getAccessProfilesByCaseReference(CASE_REFERENCE), + () -> verify(caseDefinitionRepository, times(3)).getCaseType(CASE_TYPE_ID), () -> assertNotNull(result), () -> assertTrue(result.containsKey(DATA)), () -> assertEquals(3, result.get(DATA).size()), @@ -267,6 +326,7 @@ void shouldReturnCaseDetailsWithRestoredMissingField() { @DisplayName("should Apply CaseCreationRoles When Case Not Found") void shouldApplyCaseCreationRolesWhenCaseNotFound() { CaseDataContent content = new CaseDataContent(); + attachEvent(content); content.setCaseReference(""); content.setData(emptyMap()); @@ -278,8 +338,8 @@ void shouldApplyCaseCreationRolesWhenCaseNotFound() { assertAll( () -> verify(validateCaseFieldsOperation).validateCaseDetails(operationContext), - () -> verify(caseAccessService).getCaseCreationRoles(CASE_TYPE_ID), - () -> verify(caseDefinitionRepository).getCaseType(CASE_TYPE_ID) + () -> verify(caseAccessService, atLeast(2)).getCaseCreationRoles(CASE_TYPE_ID), + () -> verify(caseDefinitionRepository, times(2)).getCaseType(CASE_TYPE_ID) ); } @@ -288,6 +348,7 @@ void shouldGetCaseDefinitionTypeThrowsException() { when(caseDefinitionRepository.getCaseType(anyString())).thenReturn(null); CaseDataContent content = new CaseDataContent(); + attachEvent(content); OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); assertThrows(ValidationException.class, @@ -309,6 +370,7 @@ void shouldValidateData() { @DisplayName("should invoke mid event callback and update content data") void shouldInvokeMidEventCallbackAndUpdateContentData() { CaseDataContent content = new CaseDataContent(); + attachEvent(content); content.setCaseReference(CASE_REFERENCE); Map inputData = new HashMap<>(); @@ -341,6 +403,7 @@ void shouldInvokeMidEventCallbackAndUpdateContentData() { @DisplayName("should invoke mid event callback with empty page id") void shouldInvokeMidEventCallbackWithEmptyPageId() { CaseDataContent content = new CaseDataContent(); + attachEvent(content); content.setCaseReference(CASE_REFERENCE); Map inputData = new HashMap<>(); @@ -366,6 +429,7 @@ void shouldInvokeMidEventCallbackWithEmptyPageId() { @DisplayName("should invoke mid event callback with null page id") void shouldInvokeMidEventCallbackWithNullPageId() { CaseDataContent content = new CaseDataContent(); + attachEvent(content); content.setCaseReference(CASE_REFERENCE); Map inputData = new HashMap<>(); @@ -391,6 +455,7 @@ void shouldInvokeMidEventCallbackWithNullPageId() { @DisplayName("should invoke mid event callback and preserve data when continuing verify access") void shouldInvokeMidEventCallbackAndPreserveDataWhenContinuingVerifyAccess() { CaseDataContent content = new CaseDataContent(); + attachEvent(content); content.setCaseReference(CASE_REFERENCE); Map inputData = new HashMap<>(); @@ -425,11 +490,401 @@ void shouldInvokeMidEventCallbackAndPreserveDataWhenContinuingVerifyAccess() { assertAll( () -> verify(midEventCallback).invoke(CASE_TYPE_ID, content, PAGE_ID), () -> verify(validateCaseFieldsOperation).validateCaseDetails(operationContext), - () -> verify(caseAccessService).getAccessProfilesByCaseReference(CASE_REFERENCE), + () -> verify(caseAccessService, times(2)).getAccessProfilesByCaseReference(CASE_REFERENCE), () -> assertNotNull(result) ); } + @Test + @DisplayName("should use create path when event token has no case id") + void shouldUseCreatePathWhenEventTokenHasNoCaseId() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setToken("create-event-token"); + content.setData(emptyMap()); + + when(eventTokenService.parseToken("create-event-token")).thenReturn(new EventTokenProperties( + "user-id", + null, + "BEFTA_MASTER", + EVENT_ID, + CASE_TYPE_ID, + null, + null, + null, + null + )); + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(emptyMap()); + + ObjectNode filteredData = new ObjectNode(JSON_NODE_FACTORY); + when(accessControlService.filterCaseFieldsByAccess(any(), any(), any(), any(), anyBoolean())) + .thenReturn(filteredData); + when(conditionalFieldRestorer.restoreConditionalFields(any(), any(), any(), any())) + .thenReturn(JacksonUtils.convertValue(filteredData)); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext); + + assertTrue(StringUtils.isEmpty(content.getCaseReference())); + verify(getCaseOperation, never()).execute(anyString()); + verify(caseAccessService, atLeast(1)).getCaseCreationRoles(CASE_TYPE_ID); + verify(midEventCallback).invoke(CASE_TYPE_ID, content, PAGE_ID); + } + + @Test + @DisplayName("should continue validate when event token cannot be parsed") + void shouldContinueValidateWhenEventTokenCannotBeParsed() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setToken("testToken"); + content.setData(emptyMap()); + + when(eventTokenService.parseToken("testToken")).thenThrow(new IllegalArgumentException("Malformed JWT")); + + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(""))).thenReturn(emptyMap()); + + ObjectNode filteredData = new ObjectNode(JSON_NODE_FACTORY); + when(accessControlService.filterCaseFieldsByAccess(any(), any(), any(), any(), anyBoolean())) + .thenReturn(filteredData); + when(conditionalFieldRestorer.restoreConditionalFields(any(), any(), any(), any())) + .thenReturn(JacksonUtils.convertValue(filteredData)); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, ""); + + authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext); + + assertTrue(StringUtils.isEmpty(content.getCaseReference())); + verify(midEventCallback).invoke(CASE_TYPE_ID, content, ""); + } + + @Test + @DisplayName("should skip event access check when page id is blank") + void shouldSkipEventAccessCheckWhenPageIdIsBlank() { + CaseDataContent content = new CaseDataContent(); + content.setCaseReference(CASE_REFERENCE); + + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(""))).thenReturn(emptyMap()); + + ObjectNode filteredData = new ObjectNode(JSON_NODE_FACTORY); + when(accessControlService.filterCaseFieldsByAccess(any(), any(), any(), any(), anyBoolean())) + .thenReturn(filteredData); + when(conditionalFieldRestorer.restoreConditionalFields(any(), any(), any(), any())) + .thenReturn(JacksonUtils.convertValue(filteredData)); + + final OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, ""); + authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext); + + verify(getCaseOperation, never()).execute(anyString()); + verify(midEventCallback).invoke(CASE_TYPE_ID, content, ""); + } + + @Test + @DisplayName("should throw when event is missing before mid event") + void shouldThrowWhenEventIsMissing() { + CaseDataContent content = new CaseDataContent(); + content.setCaseReference(CASE_REFERENCE); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, + () -> authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext)); + + assertEquals(NO_EVENT_FOUND, exception.getMessage()); + verify(midEventCallback, never()).invoke(anyString(), any(), any()); + } + + @Test + @DisplayName("should throw when event id is empty before mid event") + void shouldThrowWhenEventIdIsEmpty() { + CaseDataContent content = new CaseDataContent(); + Event event = new Event(); + event.setEventId(""); + content.setEvent(event); + content.setCaseReference(CASE_REFERENCE); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, + () -> authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext)); + + assertEquals(NO_EVENT_FOUND, exception.getMessage()); + verify(midEventCallback, never()).invoke(anyString(), any(), any()); + } + + @Test + @DisplayName("should throw when create case user has no roles") + void shouldThrowWhenCreateCaseUserHasNoRoles() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setCaseReference(""); + content.setData(emptyMap()); + + when(caseAccessService.getCaseCreationRoles(CASE_TYPE_ID)).thenReturn(Set.of()); + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(emptyMap()); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + ValidationException exception = assertThrows(ValidationException.class, + () -> authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext)); + + assertEquals("Cannot find user roles for the user", exception.getMessage()); + verify(midEventCallback).invoke(CASE_TYPE_ID, content, PAGE_ID); + } + + @Test + @DisplayName("should throw when create case type access is denied") + void shouldThrowWhenCreateCaseTypeAccessDenied() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setCaseReference(""); + content.setData(emptyMap()); + + when(accessControlService.canAccessCaseTypeWithCriteria(any(), any(), eq(CAN_CREATE))) + .thenReturn(false); + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(emptyMap()); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, + () -> authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext)); + + assertEquals(NO_CASE_TYPE_FOUND, exception.getMessage()); + verify(midEventCallback).invoke(CASE_TYPE_ID, content, PAGE_ID); + } + + @Test + @DisplayName("should throw when create case event access is denied") + void shouldThrowWhenCreateCaseEventAccessDenied() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setCaseReference(""); + content.setData(emptyMap()); + + when(accessControlService.canAccessCaseEventWithCriteria(anyString(), any(), any(), eq(CAN_CREATE))) + .thenReturn(false); + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(emptyMap()); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, + () -> authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext)); + + assertEquals(NO_EVENT_FOUND, exception.getMessage()); + verify(midEventCallback).invoke(CASE_TYPE_ID, content, PAGE_ID); + } + + @Test + @DisplayName("should resolve entity id from event token to case reference for update validate") + void shouldResolveEntityIdFromEventTokenToCaseReference() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setToken("event-token"); + content.setData(emptyMap()); + + CaseDetails caseByEntityId = new CaseDetails(); + caseByEntityId.setReference(Long.valueOf(CASE_REFERENCE)); + caseByEntityId.setCaseTypeId(CASE_TYPE_ID); + caseByEntityId.setState("Open"); + + when(eventTokenService.parseToken("event-token")).thenReturn(new EventTokenProperties( + "user-id", + "42", + "BEFTA_MASTER", + EVENT_ID, + CASE_TYPE_ID, + "case-version", + "Open", + "1", + "1" + )); + when(getCaseOperation.execute("42")).thenThrow(new BadRequestException("Case reference is not valid")); + when(getCaseOperation.execute(CASE_REFERENCE)).thenReturn(Optional.of(caseByEntityId)); + when(caseDetailsRepository.findById(42L)).thenReturn(caseByEntityId); + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(emptyMap()); + + ObjectNode filteredData = new ObjectNode(JSON_NODE_FACTORY); + when(accessControlService.filterCaseFieldsByAccess(any(), any(), any(), any(), anyBoolean())) + .thenReturn(filteredData); + when(conditionalFieldRestorer.restoreConditionalFields(any(), any(), any(), any())) + .thenReturn(JacksonUtils.convertValue(filteredData)); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext); + + assertEquals(CASE_REFERENCE, content.getCaseReference()); + verify(getCaseOperation).execute(CASE_REFERENCE); + verify(caseAccessService, atLeast(1)).getAccessProfilesByCaseReference(CASE_REFERENCE); + } + + @Test + @DisplayName("should use update path when case reference is resolved from event token") + void shouldUseUpdatePathWhenCaseReferenceResolvedFromEventToken() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setCaseReference(""); + content.setToken("event-token"); + content.setData(emptyMap()); + + when(eventTokenService.parseToken("event-token")).thenReturn(new EventTokenProperties( + "user-id", + CASE_REFERENCE, + "BEFTA_MASTER", + EVENT_ID, + CASE_TYPE_ID, + "case-version", + "Open", + "1", + "1" + )); + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(emptyMap()); + + ObjectNode filteredData = new ObjectNode(JSON_NODE_FACTORY); + when(accessControlService.filterCaseFieldsByAccess(any(), any(), any(), any(), anyBoolean())) + .thenReturn(filteredData); + when(conditionalFieldRestorer.restoreConditionalFields(any(), any(), any(), any())) + .thenReturn(JacksonUtils.convertValue(filteredData)); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext); + + assertEquals(CASE_REFERENCE, content.getCaseReference()); + verify(getCaseOperation, atLeast(1)).execute(CASE_REFERENCE); + verify(caseAccessService, atLeast(1)).getAccessProfilesByCaseReference(CASE_REFERENCE); + verify(caseAccessService, never()).getCaseCreationRoles(anyString()); + verify(midEventCallback).invoke(CASE_TYPE_ID, content, PAGE_ID); + } + + @Test + @DisplayName("should throw when update case is not found") + void shouldThrowWhenUpdateCaseNotFound() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setCaseReference(CASE_REFERENCE); + content.setData(emptyMap()); + + when(getCaseOperation.execute(CASE_REFERENCE)).thenReturn(Optional.empty()); + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(emptyMap()); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, + () -> authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext)); + + assertEquals("Case not found", exception.getMessage()); + verify(midEventCallback).invoke(CASE_TYPE_ID, content, PAGE_ID); + } + + @Test + @DisplayName("should throw when update case type access is denied") + void shouldThrowWhenUpdateCaseTypeAccessDenied() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setCaseReference(CASE_REFERENCE); + content.setData(emptyMap()); + + when(accessControlService.canAccessCaseTypeWithCriteria(any(), any(), eq(CAN_UPDATE))) + .thenReturn(false); + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(emptyMap()); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, + () -> authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext)); + + assertEquals(NO_CASE_TYPE_FOUND, exception.getMessage()); + verify(midEventCallback).invoke(CASE_TYPE_ID, content, PAGE_ID); + } + + @Test + @DisplayName("should throw when update case state access is denied") + void shouldThrowWhenUpdateCaseStateAccessDenied() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setCaseReference(CASE_REFERENCE); + content.setData(emptyMap()); + + when(accessControlService.canAccessCaseStateWithCriteria(anyString(), any(), any(), eq(CAN_UPDATE))) + .thenReturn(false); + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(emptyMap()); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, + () -> authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext)); + + assertEquals(NO_CASE_STATE_FOUND, exception.getMessage()); + verify(midEventCallback).invoke(CASE_TYPE_ID, content, PAGE_ID); + } + + @Test + @DisplayName("should return empty data when read access to case type is denied") + void shouldReturnEmptyDataWhenReadAccessToCaseTypeIsDenied() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setCaseReference(CASE_REFERENCE); + content.setData(Map.of("field1", JSON_NODE_FACTORY.textNode("value1"))); + + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))) + .thenReturn(Map.of("field1", JSON_NODE_FACTORY.textNode("value1"))); + when(accessControlService.canAccessCaseTypeWithCriteria(any(), any(), eq(CAN_READ))) + .thenReturn(false); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + Map result = authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext); + + assertTrue(result.containsKey(DATA)); + assertTrue(result.get(DATA).isEmpty()); + verify(accessControlService, never()).filterCaseFieldsByAccess(any(), any(), any(), any(), anyBoolean()); + } + + @Test + @DisplayName("should return empty data when content data is null after mid event") + void shouldReturnEmptyDataWhenContentDataIsNullAfterMidEvent() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setCaseReference(CASE_REFERENCE); + + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(null); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + Map result = authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext); + + assertTrue(result.containsKey(DATA)); + assertTrue(result.get(DATA).isEmpty()); + verify(accessControlService, never()).filterCaseFieldsByAccess(any(), any(), any(), any(), anyBoolean()); + } + + @Test + @DisplayName("should invoke mid event before rejecting when user lacks case event access") + void shouldInvokeMidEventBeforeRejectingWhenUserLacksCaseEventAccess() { + CaseDataContent content = new CaseDataContent(); + attachEvent(content); + content.setCaseReference(CASE_REFERENCE); + content.setData(new HashMap<>()); + + when(midEventCallback.invoke(eq(CASE_TYPE_ID), eq(content), eq(PAGE_ID))).thenReturn(emptyMap()); + when(accessControlService.canAccessCaseEventWithCriteria(anyString(), any(), any(), any())) + .thenReturn(false); + + OperationContext operationContext = new OperationContext(CASE_TYPE_ID, content, PAGE_ID); + + assertThrows(ResourceNotFoundException.class, + () -> authorisedValidateCaseFieldsOperation.validateCaseDetails(operationContext)); + + verify(midEventCallback).invoke(CASE_TYPE_ID, content, PAGE_ID); + } + + private static void attachEvent(CaseDataContent content) { + Event event = new Event(); + event.setEventId(EVENT_ID); + content.setEvent(event); + } + @AfterEach void tearDown() throws Exception { openMocks.close(); diff --git a/src/test/java/uk/gov/hmcts/ccd/v2/external/controller/BaseCaseAssignedUserRolesControllerIT.java b/src/test/java/uk/gov/hmcts/ccd/v2/external/controller/BaseCaseAssignedUserRolesControllerIT.java index 62d0c5ed03..23082f71f4 100644 --- a/src/test/java/uk/gov/hmcts/ccd/v2/external/controller/BaseCaseAssignedUserRolesControllerIT.java +++ b/src/test/java/uk/gov/hmcts/ccd/v2/external/controller/BaseCaseAssignedUserRolesControllerIT.java @@ -31,6 +31,7 @@ import uk.gov.hmcts.ccd.data.casedetails.supplementarydata.SupplementaryDataRepository; import uk.gov.hmcts.ccd.domain.model.std.CaseAssignedUserRoleWithOrganisation; import uk.gov.hmcts.ccd.domain.service.casedataaccesscontrol.RoleAssignmentCategoryService; +import uk.gov.hmcts.ccd.test.RoleAssignmentsHelper; import uk.gov.hmcts.reform.idam.client.models.UserInfo; import jakarta.inject.Inject; @@ -41,6 +42,7 @@ import java.util.stream.Collectors; import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -166,6 +168,9 @@ void setUp() throws IOException { .willReturn(okJson(mapper.writeValueAsString(userInfo)).withStatus(200))); stubFor(WireMock.get(urlMatching("/api/v1/users/.*")) .willReturn(okJson(mapper.writeValueAsString(userInfo)).withStatus(200))); + + stubFor(post(urlMatching("/am/role-assignments/query")) + .willReturn(okJson(RoleAssignmentsHelper.emptyRoleAssignmentResponseJson()).withStatus(200))); } protected HttpHeaders createHttpHeaders() { diff --git a/src/test/java/uk/gov/hmcts/ccd/v2/external/controller/GetCaseAssignedUserRolesControllerIT.java b/src/test/java/uk/gov/hmcts/ccd/v2/external/controller/GetCaseAssignedUserRolesControllerIT.java index c77400f7a0..5477957a01 100644 --- a/src/test/java/uk/gov/hmcts/ccd/v2/external/controller/GetCaseAssignedUserRolesControllerIT.java +++ b/src/test/java/uk/gov/hmcts/ccd/v2/external/controller/GetCaseAssignedUserRolesControllerIT.java @@ -41,6 +41,15 @@ class GetCaseAssignedUserRolesControllerIT extends BaseCaseAssignedUserRolesCont void getUserCaseRolesAssignedToUser() throws Exception { MockUtils.setSecurityAuthorities(authentication, MockUtils.ROLE_CASEWORKER_PUBLIC, caseworkerCaa); + String amResponseJson = roleAssignmentResponseJson( + userRoleAssignmentJson("89000", "[CREATOR]", CASE_ID_1), + userRoleAssignmentJson("89001", "[DEFENDANT]", CASE_ID_2), + userRoleAssignmentJson("89001", "[SOLICITOR]", CASE_ID_2) + ); + + stubFor(post(urlMatching("/am/role-assignments/query")) + .willReturn(okJson(amResponseJson).withStatus(200))); + final MvcResult result = mockMvc.perform(get(getCaseAssignedUserRoles) .contentType(JSON_CONTENT_TYPE) .param(PARAM_CASE_IDS, CASE_IDS)