diff --git a/assessment-api/open-api-specification.yml b/assessment-api/open-api-specification.yml index cf75cd4..82b6bd9 100644 --- a/assessment-api/open-api-specification.yml +++ b/assessment-api/open-api-specification.yml @@ -1,12 +1,12 @@ openapi: '3.0.0' info: - title: 'laa-ccms-assessment-api' + title: 'laa-ccms-caab-assessment-api' version: '1.0.0' paths: /assessments: get: tags: - - 'assessment' + - assessments summary: 'get assessments' operationId: 'getAssessments' parameters: @@ -44,7 +44,7 @@ paths: /assessments/{assessment-id}: get: tags: - - 'assessment' + - assessments summary: 'get assessment by id ' operationId: 'getAssessment' parameters: @@ -70,6 +70,41 @@ paths: description: 'Not found' '500': description: 'Internal server error' + patch: + tags: + - assessments + summary: 'update assessment' + operationId: 'updateAssessment' + requestBody: + description: Create a application + content: + application/json: + schema: + $ref: '#/components/schemas/baseAssessmentDetail' + parameters: + - name: 'assessment-id' + in: 'path' + required: true + schema: + type: 'integer' + format: 'int64' + example: '1234567890' + - $ref: '#/components/parameters/requiredUserLoginId' + responses: + '200': + description: 'Successful operation' + content: + application/json: + schema: + $ref: "#/components/schemas/assessmentDetail" + '400': + description: 'Bad request' + '401': + description: 'Unauthorized' + '404': + description: 'Not found' + '500': + description: 'Internal server error' components: parameters: @@ -89,6 +124,14 @@ components: - 'meritsAssessment_PREPOP' example: 'meansAssessment' + requiredUserLoginId: + name: 'Caab-User-Login-Id' + in: header + required: true + schema: + type: 'string' + example: 'SOMEUSER@COMPANY.CO.UK' + schemas: assessmentDetails: type: 'object' @@ -99,10 +142,24 @@ components: items: $ref: "#/components/schemas/assessmentDetail" assessmentDetail: + allOf: + - $ref: "#/components/schemas/baseAssessmentDetail" + - type: 'object' + properties: + id: + type: 'string' + entity_types: + type: array + default: [ ] + items: + $ref: '#/components/schemas/assessmentEntityTypeDetail' + audit_detail: + $ref: '#/components/schemas/auditDetail' + baseAssessmentDetail: type: 'object' + discriminator: + propertyName: baseAssessmentDetail properties: - id: - type: 'string' provider_id: type: 'string' case_reference_number: @@ -111,13 +168,6 @@ components: type: 'string' status: type: 'string' - entity_types: - type: array - default: [ ] - items: - $ref: '#/components/schemas/assessmentEntityTypeDetail' - audit_detail: - $ref: '#/components/schemas/auditDetail' auditDetail: type: 'object' properties: diff --git a/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/advice/AuditAdvice.java b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/advice/AuditAdvice.java new file mode 100644 index 0000000..d39a626 --- /dev/null +++ b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/advice/AuditAdvice.java @@ -0,0 +1,27 @@ +package uk.gov.laa.ccms.caab.assessment.advice; + +import static uk.gov.laa.ccms.caab.assessment.audit.AuditorAwareImpl.currentUserHolder; + +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestHeader; + +/** + * Controller advice class responsible for setting the current user holder if available. + */ +@ControllerAdvice +public class AuditAdvice { + + /** + * Sets the current user holder if available from the request header. + * + * @param caabUserLoginId the caab user login id + */ + @ModelAttribute + public void setCurrentUserHolderIfAvailable( + @RequestHeader(value = "Caab-User-Login-Id", required = false) String caabUserLoginId) { + if (caabUserLoginId != null) { + currentUserHolder.set(caabUserLoginId); + } + } +} diff --git a/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/audit/AuditorAwareImpl.java b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/audit/AuditorAwareImpl.java new file mode 100644 index 0000000..9541a0a --- /dev/null +++ b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/audit/AuditorAwareImpl.java @@ -0,0 +1,20 @@ +package uk.gov.laa.ccms.caab.assessment.audit; + + +import java.util.Optional; +import org.springframework.data.domain.AuditorAware; + +/** + * Auditor provider implementation for the application. This class is responsible for storing the + * current user in a thread local variable, and returning it when requested. + */ +public class AuditorAwareImpl implements AuditorAware { + + public static final ThreadLocal currentUserHolder = new ThreadLocal(); + + @Override + public Optional getCurrentAuditor() { + return Optional.of(currentUserHolder.get()); + } + +} diff --git a/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/config/ApplicationConfig.java b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/config/ApplicationConfig.java new file mode 100644 index 0000000..ba1a441 --- /dev/null +++ b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/config/ApplicationConfig.java @@ -0,0 +1,21 @@ +package uk.gov.laa.ccms.caab.assessment.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.AuditorAware; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import uk.gov.laa.ccms.caab.assessment.audit.AuditorAwareImpl; + +/** + * Configuration for the application, and the auditor provider. + */ +@Configuration +@EnableJpaAuditing(auditorAwareRef = "auditorProvider") +public class ApplicationConfig { + + @Bean("auditorProvider") + public AuditorAware auditorProvider() { + return new AuditorAwareImpl(); + } + +} diff --git a/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/controller/AssessmentController.java b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/controller/AssessmentController.java index 0fc4980..52d03ba 100644 --- a/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/controller/AssessmentController.java +++ b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/controller/AssessmentController.java @@ -1,22 +1,30 @@ package uk.gov.laa.ccms.caab.assessment.controller; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import uk.gov.laa.ccms.caab.assessment.api.AssessmentApi; +import org.springframework.web.bind.annotation.RestController; +import uk.gov.laa.ccms.caab.assessment.api.AssessmentsApi; import uk.gov.laa.ccms.caab.assessment.model.AssessmentDetail; import uk.gov.laa.ccms.caab.assessment.model.AssessmentDetails; +import uk.gov.laa.ccms.caab.assessment.model.BaseAssessmentDetail; import uk.gov.laa.ccms.caab.assessment.service.AssessmentService; /** * Controller handling assessment requests. */ -@Controller +@RestController @RequiredArgsConstructor -public class AssessmentController implements AssessmentApi { +public class AssessmentController implements AssessmentsApi { private final AssessmentService assessmentService; + /** + * Retrieves a single assessment by its ID. + * + * @param assessmentId The ID of the assessment to retrieve. + * @return ResponseEntity containing the requested AssessmentDetail. + */ @Override public ResponseEntity getAssessment( final Long assessmentId) { @@ -24,6 +32,15 @@ public ResponseEntity getAssessment( return ResponseEntity.ok(assessmentService.getAssessment(assessmentId)); } + /** + * Retrieves a list of assessments matching the given criteria. + * + * @param name The name to search for. + * @param providerId The provider ID to search for. + * @param caseReferenceNumber The case reference number to search for. + * @param status The status to search for. + * @return ResponseEntity containing the list of matching AssessmentDetails. + */ @Override public ResponseEntity getAssessments( final String name, @@ -39,4 +56,22 @@ public ResponseEntity getAssessments( return ResponseEntity.ok(assessmentService.getAssessments(criteria)); } + + /** + * Updates an assessment with the provided details. + * + * @param assessmentId The ID of the assessment to update. + * @param caabUserLoginId The CAAB user login ID performing the update. + * @param baseAssessmentDetail The details to update the assessment with. + * @return ResponseEntity with the status of the update operation. + */ + @Override + public ResponseEntity updateAssessment( + final Long assessmentId, + final String caabUserLoginId, + final BaseAssessmentDetail baseAssessmentDetail) { + + assessmentService.updateAssessment(assessmentId, baseAssessmentDetail); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } } diff --git a/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/mapper/AssessmentMapper.java b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/mapper/AssessmentMapper.java index c32c258..02e4603 100644 --- a/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/mapper/AssessmentMapper.java +++ b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/mapper/AssessmentMapper.java @@ -1,9 +1,13 @@ package uk.gov.laa.ccms.caab.assessment.mapper; import java.util.List; +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritConfiguration; import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; import uk.gov.laa.ccms.caab.assessment.entity.OpaAttribute; import uk.gov.laa.ccms.caab.assessment.entity.OpaEntity; import uk.gov.laa.ccms.caab.assessment.entity.OpaListEntity; @@ -13,6 +17,7 @@ import uk.gov.laa.ccms.caab.assessment.model.AssessmentDetails; import uk.gov.laa.ccms.caab.assessment.model.AssessmentEntityDetail; import uk.gov.laa.ccms.caab.assessment.model.AssessmentEntityTypeDetail; +import uk.gov.laa.ccms.caab.assessment.model.BaseAssessmentDetail; /** * Mapper for mapping between assessment entities and models. @@ -28,7 +33,7 @@ public interface AssessmentMapper { @Mapping(target = "opaListEntities", ignore = true) OpaSession toOpaSession(AssessmentDetail assessmentDetail); - @InheritInverseConfiguration + @InheritInverseConfiguration(name = "toOpaSession") @Mapping(target = "auditDetail", source = "auditTrail") @Mapping(target = "entityTypes", source = "opaListEntities") AssessmentDetail toAssessmentDetail(OpaSession opaSession); @@ -45,6 +50,13 @@ public interface AssessmentMapper { @Mapping(target = "type", source = "attributeType") AssessmentAttributeDetail toAssessmentAttributeDetail(OpaAttribute opaAttribute); + @InheritConfiguration(name = "toOpaSession") + @Mapping(target = "opaListEntities", ignore = true) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void mapIntoOpaSession( + @MappingTarget OpaSession opaSession, + AssessmentDetail assessmentDetail); + /** * Maps a list of {@link OpaSession} entities to an {@link AssessmentDetails} model. * @@ -62,4 +74,6 @@ default AssessmentDetails toAssessmentDetails(List opaSessions) { } + + } diff --git a/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/service/AssessmentService.java b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/service/AssessmentService.java index 8eab5c3..6427e5d 100644 --- a/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/service/AssessmentService.java +++ b/assessment-service/src/main/java/uk/gov/laa/ccms/caab/assessment/service/AssessmentService.java @@ -1,5 +1,6 @@ package uk.gov.laa.ccms.caab.assessment.service; +import jakarta.transaction.Transactional; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Example; @@ -10,6 +11,7 @@ import uk.gov.laa.ccms.caab.assessment.mapper.AssessmentMapper; import uk.gov.laa.ccms.caab.assessment.model.AssessmentDetail; import uk.gov.laa.ccms.caab.assessment.model.AssessmentDetails; +import uk.gov.laa.ccms.caab.assessment.model.BaseAssessmentDetail; import uk.gov.laa.ccms.caab.assessment.repository.OpaSessionRepository; /** @@ -51,6 +53,27 @@ public AssessmentDetail getAssessment(Long assessmentId) { HttpStatus.NOT_FOUND)); } + /** + * Updates an assessment's details in the database. + * + * @param assessmentId The ID of the assessment to update. + * @param baseAssessmentDetail The new details to be applied to the assessment. + * @throws ApplicationException if the assessment with the specified ID + * does not exist. + */ + @Transactional + public void updateAssessment( + final Long assessmentId, + final BaseAssessmentDetail baseAssessmentDetail) { + OpaSession opaSession = opaSessionRepository.findById(assessmentId) + .orElseThrow(() -> new ApplicationException( + String.format("Assessment with id %s not found", assessmentId), + HttpStatus.NOT_FOUND)); + + assessmentMapper.mapIntoOpaSession(opaSession, (AssessmentDetail) baseAssessmentDetail); + opaSessionRepository.save(opaSession); + } + } diff --git a/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/advice/AuditAdviceTest.java b/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/advice/AuditAdviceTest.java new file mode 100644 index 0000000..fbece23 --- /dev/null +++ b/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/advice/AuditAdviceTest.java @@ -0,0 +1,77 @@ +package uk.gov.laa.ccms.caab.assessment.advice; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import uk.gov.laa.ccms.caab.assessment.audit.AuditorAwareImpl; + +@ExtendWith(SpringExtension.class) +class AuditAdviceTest { + + @Mock + private AuditorAwareImpl auditorAware; + + @InjectMocks + private AuditAdvice auditAdvice; + + private MockMvc mockMvc; + + @BeforeEach + public void setup() { + // Setup MockMvc with the AuditAdvice + mockMvc = MockMvcBuilders.standaloneSetup(new MockController()) + .setControllerAdvice(auditAdvice) + .build(); + } + + @AfterEach + public void tearDown() { + // Reset currentUserHolder after each test + AuditorAwareImpl.currentUserHolder.remove(); + } + + @Test + void setCurrentUserHolderIfAvailable() throws Exception { + String caabUserLoginId = "testUser"; + + mockMvc.perform(get("/test") + .header("Caab-User-Login-Id", caabUserLoginId)) + .andDo(print()) + .andExpect(status().isOk()); + + assertEquals(caabUserLoginId, AuditorAwareImpl.currentUserHolder.get()); + } + + @Test + void currentUserHolderIsNullWhenCaabUserLoginIdNotProvided() throws Exception { + mockMvc.perform(get("/test")) // No Caab-User-Login-Id header + .andDo(print()) + .andExpect(status().isOk()); + + // Assert that currentUserHolder is null or unchanged + assertNull(AuditorAwareImpl.currentUserHolder.get()); + } + + // Mock controller for testing purposes + @RestController + private static class MockController { + @GetMapping("/test") + public void testEndpoint() { + // Test endpoint for AuditAdvice + } + } +} diff --git a/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/advice/GlobalExceptionHandlerTest.java b/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/advice/GlobalExceptionHandlerTest.java new file mode 100644 index 0000000..8da0d9b --- /dev/null +++ b/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/advice/GlobalExceptionHandlerTest.java @@ -0,0 +1,38 @@ +package uk.gov.laa.ccms.caab.assessment.advice; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.ui.Model; +import uk.gov.laa.ccms.caab.assessment.exception.ApplicationException; + +@ExtendWith(MockitoExtension.class) +public class GlobalExceptionHandlerTest { + + @Mock + private Logger loggerMock; + + @Mock + private Model model; + + @InjectMocks + private GlobalExceptionHandler globalExceptionHandler; + + @Test + public void testHandleDataApiClientException() { + final String errorMsg = "Test Exception"; + ApplicationException e = new ApplicationException(errorMsg, HttpStatus.BAD_GATEWAY); + + ResponseEntity response = globalExceptionHandler.handleApplicationException(e); + + assertEquals(502, response.getStatusCode().value()); + } + +} diff --git a/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/mapper/AssessmentMapperTest.java b/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/mapper/AssessmentMapperTest.java index 01ae120..4f200b4 100644 --- a/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/mapper/AssessmentMapperTest.java +++ b/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/mapper/AssessmentMapperTest.java @@ -10,7 +10,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Answers; @@ -111,7 +110,7 @@ void testToAssessmentDetailWithNonNullSession() { @Test void testToAssessmentDetailWithNullSession() { - AssessmentDetail detail = assessmentMapper.toAssessmentDetail(null); + AssessmentDetail detail = assessmentMapper.toAssessmentDetail((OpaSession) null); assertNull(detail); } @@ -324,5 +323,41 @@ void testOpaRelationshipListToAssessmentRelationshipDetailListWithNonNullList() assertEquals(opaRelationships.size(), details.size()); } + @Test + void mapIntoOpaSession_WithFullDetail_MapsAllFields() { + OpaSession opaSession = new OpaSession(); + AssessmentDetail assessmentDetail = new AssessmentDetail() + .name("Assessment Name") + .providerId("Provider123") + .caseReferenceNumber("CaseRef456") + .id("789") + .status("Completed"); + + assessmentMapper.mapIntoOpaSession(opaSession, assessmentDetail); + + assertEquals("Assessment Name", opaSession.getAssessment()); + assertEquals("Provider123", opaSession.getOwnerId()); + assertEquals("CaseRef456", opaSession.getTargetId()); + assertEquals(789L, opaSession.getId()); + assertEquals("Completed", opaSession.getStatus()); + } + + @Test + void mapIntoOpaSession_WithPartialDetail_MapsNonNullFields() { + OpaSession opaSession = new OpaSession(); + AssessmentDetail assessmentDetail = new AssessmentDetail() + .name("Partial Name") + .caseReferenceNumber("PartialCaseRef123"); + + assessmentMapper.mapIntoOpaSession(opaSession, assessmentDetail); + + assertEquals("Partial Name", opaSession.getAssessment()); + assertNull(opaSession.getOwnerId()); + assertEquals("PartialCaseRef123", opaSession.getTargetId()); + assertNull(opaSession.getId()); + assertNull(opaSession.getStatus()); + } + + } \ No newline at end of file diff --git a/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/service/AssessmentServiceTest.java b/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/service/AssessmentServiceTest.java index 8095d99..618510c 100644 --- a/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/service/AssessmentServiceTest.java +++ b/assessment-service/src/test/java/uk/gov/laa/ccms/caab/assessment/service/AssessmentServiceTest.java @@ -1,6 +1,8 @@ package uk.gov.laa.ccms.caab.assessment.service; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -97,7 +99,37 @@ void testGetAssessmentNotFound() { verify(opaSessionRepository).findById(assessmentId); } + @Test + void testUpdateAssessmentSuccess() { + final Long assessmentId = 1L; + final AssessmentDetail baseAssessmentDetail = new AssessmentDetail(); + final OpaSession opaSession = new OpaSession(); + + when(opaSessionRepository.findById(assessmentId)).thenReturn(Optional.of(opaSession)); + + assessmentService.updateAssessment(assessmentId, baseAssessmentDetail); + + verify(opaSessionRepository).findById(assessmentId); + verify(assessmentMapper).mapIntoOpaSession(opaSession, baseAssessmentDetail); + verify(opaSessionRepository).save(opaSession); + } + + @Test + void testUpdateAssessmentNotFound() { + final Long assessmentId = 1L; + final AssessmentDetail baseAssessmentDetail = new AssessmentDetail(); + when(opaSessionRepository.findById(assessmentId)).thenReturn(Optional.empty()); + ApplicationException thrown = assertThrows(ApplicationException.class, () -> { + assessmentService.updateAssessment(assessmentId, baseAssessmentDetail); + }); + + assertEquals(HttpStatus.NOT_FOUND, thrown.getHttpStatus()); + assertTrue(thrown.getMessage().contains("Assessment with id " + assessmentId + " not found")); + verify(opaSessionRepository).findById(assessmentId); + verify(assessmentMapper, never()).mapIntoOpaSession(any(OpaSession.class), any(AssessmentDetail.class)); + verify(opaSessionRepository, never()).save(any(OpaSession.class)); + } } \ No newline at end of file