From 8c94988cd0f857d92fb8b9c1ff3fa36796648b54 Mon Sep 17 00:00:00 2001 From: "QSL\\SumathiT" Date: Fri, 18 Oct 2024 09:44:29 -0700 Subject: [PATCH 1/5] Draft commit - WIP (Unit Test cases). --- .../educ/eas/api/constants/EventOutcome.java | 2 + .../gov/educ/eas/api/constants/EventType.java | 3 +- .../gov/educ/eas/api/constants/SagaEnum.java | 2 +- .../educ/eas/api/constants/TopicsEnum.java | 1 + .../StudentRegistrationOrchestrator.java | 84 +++++++++++++++ ...udentRegistrationOrchestrationService.java | 36 +++++++ .../events/EventHandlerDelegatorService.java | 6 +- .../v1/events/EventHandlerService.java | 30 +++--- .../bc/gov/educ/eas/api/BaseEasAPITest.java | 4 + .../StudentRegistrationOrchestratorTest.java | 102 ++++++++++++++++++ 10 files changed, 246 insertions(+), 24 deletions(-) create mode 100644 api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java create mode 100644 api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/StudentRegistrationOrchestrationService.java create mode 100644 api/src/test/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestratorTest.java diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventOutcome.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventOutcome.java index 7454608..c99bad1 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventOutcome.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventOutcome.java @@ -7,6 +7,8 @@ public enum EventOutcome { INITIATE_SUCCESS, STUDENT_REGISTRATION_FOUND, STUDENT_REGISTRATION_NOT_FOUND, + STUDENT_REGISTRATION_CREATED, + STUDENT_REGISTRATION_PUBLISHED, SAGA_COMPLETED, STUDENT_ALREADY_EXIST } diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventType.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventType.java index 7c50adf..196c1f5 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventType.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventType.java @@ -5,5 +5,6 @@ public enum EventType { MARK_SAGA_COMPLETE, //Default. Used by BaseOrchestrator. GET_PAGINATED_SCHOOLS, CREATE_STUDENT_REGISTRATION, - GET_STUDENT_REGISTRATION + GET_STUDENT_REGISTRATION, + PUBLISH_STUDENT_REGISTRATION } diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaEnum.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaEnum.java index 9779801..762f1a1 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaEnum.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaEnum.java @@ -1,5 +1,5 @@ package ca.bc.gov.educ.eas.api.constants; public enum SagaEnum { - + CREATE_STUDENT_REGISTRATION } diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/TopicsEnum.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/TopicsEnum.java index 4d44455..c3e2e76 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/TopicsEnum.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/TopicsEnum.java @@ -5,4 +5,5 @@ public enum TopicsEnum { EAS_API_TOPIC, INSTITUTE_API_TOPIC, EAS_EVENTS_TOPIC, + STUDENT_REGISTRATION_SAGA_TOPIC } diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java b/api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java new file mode 100644 index 0000000..f21fbca --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java @@ -0,0 +1,84 @@ +package ca.bc.gov.educ.eas.api.orchestrator; + +import ca.bc.gov.educ.eas.api.constants.SagaEnum; +import ca.bc.gov.educ.eas.api.constants.SagaStatusEnum; +import ca.bc.gov.educ.eas.api.constants.TopicsEnum; +import ca.bc.gov.educ.eas.api.messaging.MessagePublisher; +import ca.bc.gov.educ.eas.api.messaging.jetstream.Publisher; +import ca.bc.gov.educ.eas.api.model.v1.EasSagaEntity; +import ca.bc.gov.educ.eas.api.model.v1.SagaEventStatesEntity; +import ca.bc.gov.educ.eas.api.orchestrator.base.BaseOrchestrator; +import ca.bc.gov.educ.eas.api.service.v1.StudentRegistrationOrchestrationService; +import ca.bc.gov.educ.eas.api.service.v1.SagaService; +import ca.bc.gov.educ.eas.api.struct.Event; +import ca.bc.gov.educ.eas.api.struct.v1.AssessmentStudent; +import ca.bc.gov.educ.eas.api.util.JsonUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import static ca.bc.gov.educ.eas.api.constants.EventType.CREATE_STUDENT_REGISTRATION; +import static ca.bc.gov.educ.eas.api.constants.EventType.PUBLISH_STUDENT_REGISTRATION; + +import static ca.bc.gov.educ.eas.api.constants.EventOutcome.STUDENT_REGISTRATION_CREATED; +import static ca.bc.gov.educ.eas.api.constants.EventOutcome.STUDENT_REGISTRATION_PUBLISHED; + + +@Component +@Slf4j +public class StudentRegistrationOrchestrator extends BaseOrchestrator { + + private final StudentRegistrationOrchestrationService studentRegistrationOrchestrationService; + private final Publisher publisher; + + protected StudentRegistrationOrchestrator(final SagaService sagaService, final MessagePublisher messagePublisher, StudentRegistrationOrchestrationService studentRegistrationOrchestrationService, Publisher publisher) { + super(sagaService, messagePublisher, AssessmentStudent.class, SagaEnum.CREATE_STUDENT_REGISTRATION.toString(), TopicsEnum.STUDENT_REGISTRATION_SAGA_TOPIC.toString()); + this.studentRegistrationOrchestrationService = studentRegistrationOrchestrationService; + this.publisher = publisher; + } + + @Override + public void populateStepsToExecuteMap() { + this.stepBuilder() + .begin(CREATE_STUDENT_REGISTRATION, this::createStudentRegistration) + .step(CREATE_STUDENT_REGISTRATION, STUDENT_REGISTRATION_CREATED, PUBLISH_STUDENT_REGISTRATION, this::publishStudentRegistration) + .end(PUBLISH_STUDENT_REGISTRATION, STUDENT_REGISTRATION_PUBLISHED); + } + + private void createStudentRegistration(Event event, EasSagaEntity saga, AssessmentStudent sagaData) throws JsonProcessingException { + final SagaEventStatesEntity eventStates = this.createEventState(saga, event.getEventType(), event.getEventOutcome(), event.getEventPayload()); + saga.setSagaState(CREATE_STUDENT_REGISTRATION.toString()); + saga.setStatus(SagaStatusEnum.IN_PROGRESS.toString()); + this.getSagaService().updateAttachedSagaWithEvents(saga, eventStates); + //Service call + studentRegistrationOrchestrationService.createNewStudentRegistration(sagaData); + final Event nextEvent = Event.builder().sagaId(saga.getSagaId()) + .eventType(CREATE_STUDENT_REGISTRATION) + .eventOutcome(STUDENT_REGISTRATION_CREATED) + .eventPayload(JsonUtil.getJsonStringFromObject(sagaData)) + .build(); + this.postMessageToTopic(this.getTopicToSubscribe(), nextEvent); + logMessage(nextEvent, saga); + } + + private void publishStudentRegistration(Event event, EasSagaEntity saga, AssessmentStudent sagaData) throws JsonProcessingException { + final SagaEventStatesEntity eventStates = this.createEventState(saga, event.getEventType(), event.getEventOutcome(), event.getEventPayload()); + saga.setSagaState(PUBLISH_STUDENT_REGISTRATION.toString()); + saga.setStatus(SagaStatusEnum.IN_PROGRESS.toString()); + this.getSagaService().updateAttachedSagaWithEvents(saga, eventStates); + //Service call + studentRegistrationOrchestrationService.publishStudentRegistration(sagaData, saga); + + final Event nextEvent = Event.builder().sagaId(saga.getSagaId()) + .eventType(PUBLISH_STUDENT_REGISTRATION).eventOutcome(STUDENT_REGISTRATION_PUBLISHED) + .eventPayload(JsonUtil.getJsonStringFromObject(sagaData)) + .build(); + this.postMessageToTopic(this.getTopicToSubscribe(), nextEvent); + logMessage(nextEvent, saga); + } + + private void logMessage(Event event, EasSagaEntity saga) { + log.debug("message sent to {} for {} Event. :: {}", this.getTopicToSubscribe(), event, saga.getSagaId()); + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/StudentRegistrationOrchestrationService.java b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/StudentRegistrationOrchestrationService.java new file mode 100644 index 0000000..6a19f10 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/StudentRegistrationOrchestrationService.java @@ -0,0 +1,36 @@ +package ca.bc.gov.educ.eas.api.service.v1; + +import ca.bc.gov.educ.eas.api.mappers.v1.AssessmentStudentMapper; +import ca.bc.gov.educ.eas.api.model.v1.EasSagaEntity; +import ca.bc.gov.educ.eas.api.struct.v1.AssessmentStudent; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class StudentRegistrationOrchestrationService { + + private static final AssessmentStudentMapper mapper = AssessmentStudentMapper.mapper; + protected final SagaService sagaService; + + //Inject Downstream Service + @Getter(AccessLevel.PRIVATE) + private final AssessmentStudentService assessmentStudentService; + + public StudentRegistrationOrchestrationService(AssessmentStudentService assessmentStudentService, SagaService sagaService) { + this.assessmentStudentService = assessmentStudentService; + this.sagaService = sagaService; + } + + public void createNewStudentRegistration(AssessmentStudent assessmentStudent) throws JsonProcessingException { + assessmentStudentService.createStudent(mapper.toModel(assessmentStudent)); + } + + public void publishStudentRegistration(AssessmentStudent assessmentStudent, EasSagaEntity sagaEntity) throws JsonProcessingException { + //Placeholder + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/events/EventHandlerDelegatorService.java b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/events/EventHandlerDelegatorService.java index 4d006d1..f46aa99 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/events/EventHandlerDelegatorService.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/events/EventHandlerDelegatorService.java @@ -52,7 +52,6 @@ public EventHandlerDelegatorService(MessagePublisher messagePublisher, EventHand */ public void handleEvent(final Event event, final Message message) { byte[] response; - Pair pair; boolean isSynchronous = message.getReplyTo() != null; try { switch (event.getEventType()) { @@ -66,10 +65,9 @@ public void handleEvent(final Event event, final Message message) { case CREATE_STUDENT_REGISTRATION: log.info("received create student event :: {}", event.getSagaId()); log.trace(PAYLOAD_LOG, event.getEventPayload()); - pair = eventHandlerService.handleCreateStudentRegistrationEvent(event); + response = eventHandlerService.handleCreateStudentRegistrationEvent(event); log.info(RESPONDING_BACK_TO_NATS_ON_CHANNEL, message.getReplyTo() != null ? message.getReplyTo() : event.getReplyTo()); - publishToNATS(event, message, isSynchronous, pair.getLeft()); - publishToJetStream(pair.getRight()); + publishToNATS(event, message, isSynchronous, response); break; default: log.info("silently ignoring other events :: {}", event); diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/events/EventHandlerService.java b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/events/EventHandlerService.java index c9f4edf..7ae2e1b 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/events/EventHandlerService.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/events/EventHandlerService.java @@ -4,9 +4,9 @@ import ca.bc.gov.educ.eas.api.constants.EventType; import ca.bc.gov.educ.eas.api.mappers.v1.AssessmentStudentMapper; import ca.bc.gov.educ.eas.api.model.v1.EasEventEntity; +import ca.bc.gov.educ.eas.api.orchestrator.StudentRegistrationOrchestrator; import ca.bc.gov.educ.eas.api.repository.v1.AssessmentStudentRepository; import ca.bc.gov.educ.eas.api.repository.v1.EasEventRepository; -import ca.bc.gov.educ.eas.api.service.v1.AssessmentStudentService; import ca.bc.gov.educ.eas.api.struct.Event; import ca.bc.gov.educ.eas.api.struct.v1.AssessmentStudent; import ca.bc.gov.educ.eas.api.struct.v1.AssessmentStudentGet; @@ -15,7 +15,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import lombok.val; -import org.apache.commons.lang3.tuple.Pair; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -55,32 +56,25 @@ public class EventHandlerService { private final AssessmentStudentRepository assessmentStudentRepository; - private final AssessmentStudentService assessmentStudentService; + private final StudentRegistrationOrchestrator studentRegistrationOrchestrator; private static final AssessmentStudentMapper assessmentStudentMapper = AssessmentStudentMapper.mapper; private final EasEventRepository easEventRepository; @Transactional(propagation = Propagation.REQUIRES_NEW) - public Pair handleCreateStudentRegistrationEvent(Event event) throws JsonProcessingException { - val studentEventOptional = easEventRepository.findBySagaIdAndEventType(event.getSagaId(), event.getEventType().toString()); - EasEventEntity easEvent; - EasEventEntity choreographyEvent = null; + public byte[] handleCreateStudentRegistrationEvent(final Event event) throws JsonProcessingException { + final val studentEventOptional = easEventRepository.findBySagaIdAndEventType(event.getSagaId(), event.getEventType().toString()); if (studentEventOptional.isEmpty()) { log.info(NO_RECORD_SAGA_ID_EVENT_TYPE); - log.trace(EVENT_PAYLOAD, event); - AssessmentStudent student = JsonUtil.getJsonObjectFromString(AssessmentStudent.class, event.getEventPayload()); - val optionalStudent = assessmentStudentRepository.findByAssessmentEntity_AssessmentIDAndStudentID(UUID.fromString(student.getAssessmentID()), UUID.fromString(student.getStudentID())); - easEvent = createAssessmentStudentEventRecord(event); + final AssessmentStudent student = JsonUtil.getJsonObjectFromString(AssessmentStudent.class, event.getEventPayload()); + val saga = studentRegistrationOrchestrator.createSaga(event.getEventPayload(), student.getUpdateUser()); + studentRegistrationOrchestrator.startSaga(saga); + return JsonUtil.getJsonBytesFromObject(ResponseEntity.ok(saga.getSagaId().toString())); } else { - log.info(RECORD_FOUND_FOR_SAGA_ID_EVENT_TYPE); - log.trace(EVENT_PAYLOAD, event); - easEvent = studentEventOptional.get(); - easEvent.setUpdateDate(LocalDateTime.now()); + log.trace("Execution is not required for this message returning EVENT is :: {}", event); + return JsonUtil.getJsonBytesFromObject(ResponseEntity.ok(studentEventOptional.get().getSagaId().toString())); } - - easEventRepository.save(easEvent); - return Pair.of(createResponseEvent(easEvent), choreographyEvent); } diff --git a/api/src/test/java/ca/bc/gov/educ/eas/api/BaseEasAPITest.java b/api/src/test/java/ca/bc/gov/educ/eas/api/BaseEasAPITest.java index d501a81..24163e3 100644 --- a/api/src/test/java/ca/bc/gov/educ/eas/api/BaseEasAPITest.java +++ b/api/src/test/java/ca/bc/gov/educ/eas/api/BaseEasAPITest.java @@ -2,6 +2,7 @@ import ca.bc.gov.educ.eas.api.model.v1.AssessmentEntity; import ca.bc.gov.educ.eas.api.model.v1.AssessmentStudentEntity; +import ca.bc.gov.educ.eas.api.model.v1.EasSagaEntity; import ca.bc.gov.educ.eas.api.model.v1.SessionEntity; import ca.bc.gov.educ.eas.api.struct.external.institute.v1.*; import ca.bc.gov.educ.eas.api.struct.v1.AssessmentStudent; @@ -9,6 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.SneakyThrows; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -81,6 +83,8 @@ public AssessmentStudent createMockStudent() { .studentID(UUID.randomUUID().toString()) .pen("120164447") .localID("123") + .createUser("ABC") + .updateUser("ABC") .build(); } diff --git a/api/src/test/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestratorTest.java b/api/src/test/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestratorTest.java new file mode 100644 index 0000000..2fe7e16 --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestratorTest.java @@ -0,0 +1,102 @@ +package ca.bc.gov.educ.eas.api.orchestrator; + +import ca.bc.gov.educ.eas.api.BaseEasAPITest; +import ca.bc.gov.educ.eas.api.constants.EventType; +import ca.bc.gov.educ.eas.api.constants.SagaEnum; +import ca.bc.gov.educ.eas.api.constants.SagaStatusEnum; +import ca.bc.gov.educ.eas.api.constants.v1.AssessmentTypeCodes; +import ca.bc.gov.educ.eas.api.messaging.MessagePublisher; +import ca.bc.gov.educ.eas.api.model.v1.*; +import ca.bc.gov.educ.eas.api.repository.v1.*; +import ca.bc.gov.educ.eas.api.rest.RestUtils; +import ca.bc.gov.educ.eas.api.service.v1.SagaService; +import ca.bc.gov.educ.eas.api.struct.v1.AssessmentStudent; +import ca.bc.gov.educ.eas.api.util.JsonUtil; +import com.fasterxml.jackson.databind.json.JsonMapper; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@ActiveProfiles("test") +@SpringBootTest +@Slf4j +public class StudentRegistrationOrchestratorTest extends BaseEasAPITest { + + @Autowired + SessionRepository sessionRepository; + @Autowired + AssessmentRepository assessmentRepository; + @Autowired + AssessmentStudentRepository studentRepository; + @Autowired + SagaRepository sagaRepository; + @Autowired + SagaEventRepository sagaEventRepository; + @Autowired + MessagePublisher messagePublisher; + @Autowired + StudentRegistrationOrchestrator studentRegistrationOrchestrator; + @Autowired + SagaService sagaService; + @MockBean + protected RestUtils restUtils; + @Captor + ArgumentCaptor eventCaptor; + + @BeforeEach + public void setUp() { + Mockito.reset(this.messagePublisher); + Mockito.reset(this.restUtils); + JsonMapper.builder() + .findAndAddModules() + .build(); + } + + @AfterEach + public void after() { + this.studentRepository.deleteAll(); + this.assessmentRepository.deleteAll(); + this.sessionRepository.deleteAll(); + } + + @SneakyThrows + @Test + public void testHandleEvent_createAssessmentStudent_CREATED() { + SessionEntity session = sessionRepository.save(createMockSessionEntity()); + AssessmentEntity assessment = assessmentRepository.save(createMockAssessmentEntity(session, AssessmentTypeCodes.LTF12.getCode())); + AssessmentStudent student = createMockStudent(); + student.setAssessmentStudentID(null); + student.setAssessmentID(assessment.getAssessmentID().toString()); + + EasSagaEntity sagaEntity = this.studentRegistrationOrchestrator.createSaga(JsonUtil.getJsonString(student).get(), student.getUpdateUser()); + this.studentRegistrationOrchestrator.startSaga(sagaEntity); + + Optional createdStudent = studentRepository.findByAssessmentEntity_AssessmentIDAndStudentID(UUID.fromString(student.getAssessmentID()), UUID.fromString(student.getStudentID())); + assertThat(createdStudent).isPresent(); + assertThat(createdStudent.get().getAssessmentStudentID()).isNotNull(); + assertThat(createdStudent.get().getStudentID().toString()).isEqualTo(student.getStudentID()); + + } + + + +} From 486b15e13e5722f4441d34ca6d3a5ccf7b88d126 Mon Sep 17 00:00:00 2001 From: "QSL\\SumathiT" Date: Fri, 18 Oct 2024 13:42:52 -0700 Subject: [PATCH 2/5] Unit test cases for student registration orchestrator. --- .../educ/eas/api/constants/TopicsEnum.java | 2 +- .../StudentRegistrationOrchestrator.java | 2 +- ...udentRegistrationOrchestrationService.java | 5 +- .../v1/events/EventHandlerService.java | 4 +- .../StudentRegistrationOrchestratorTest.java | 149 +++++++++++++----- 5 files changed, 116 insertions(+), 46 deletions(-) diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/TopicsEnum.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/TopicsEnum.java index c3e2e76..03145af 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/TopicsEnum.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/TopicsEnum.java @@ -5,5 +5,5 @@ public enum TopicsEnum { EAS_API_TOPIC, INSTITUTE_API_TOPIC, EAS_EVENTS_TOPIC, - STUDENT_REGISTRATION_SAGA_TOPIC + CREATE_STUDENT_REGISTRATION_SAGA_TOPIC } diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java b/api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java index f21fbca..e35c467 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java @@ -32,7 +32,7 @@ public class StudentRegistrationOrchestrator extends BaseOrchestrator eventCaptor; + private EasSagaEntity saga; + + @AfterEach + public void after() { + sagaEventRepository.deleteAll(); + sagaRepository.deleteAll(); + assessmentStudentRepository.deleteAll(); + assessmentRepository.deleteAll(); + sessionRepository.deleteAll(); + } @BeforeEach public void setUp() { Mockito.reset(this.messagePublisher); - Mockito.reset(this.restUtils); - JsonMapper.builder() - .findAndAddModules() - .build(); + SessionEntity session = sessionRepository.save(createMockSessionEntity()); + AssessmentEntity assessment = assessmentRepository.save(createMockAssessmentEntity(session, AssessmentTypeCodes.LTF12.getCode())); + AssessmentStudent sagaData = createMockStudent(); + sagaData.setAssessmentID(assessment.getAssessmentID().toString()); + MockitoAnnotations.openMocks(this); + sagaPayload = JsonUtil.getJsonString(sagaData).get(); + saga = this.sagaService.createSagaRecordInDB(SagaEnum.CREATE_STUDENT_REGISTRATION.name(), "test", sagaPayload); } - @AfterEach - public void after() { - this.studentRepository.deleteAll(); - this.assessmentRepository.deleteAll(); - this.sessionRepository.deleteAll(); + @SneakyThrows + @Test + public void testHandleEvent_createAssessmentStudent_CreateStudentAndPostMessageToNats() { + final var invocations = mockingDetails(this.messagePublisher).getInvocations().size(); + final var event = Event.builder() + .eventType(INITIATED) + .eventOutcome(EventOutcome.INITIATE_SUCCESS) + .sagaId(this.saga.getSagaId()) + .eventPayload(sagaPayload) + .build(); + this.studentRegistrationOrchestrator.handleEvent(event); + + verify(this.messagePublisher, atMost(invocations + 2)).dispatchMessage(eq(this.studentRegistrationOrchestrator.getTopicToSubscribe()), this.eventCaptor.capture()); + final var newEvent = JsonUtil.getJsonObjectFromString(Event.class, new String(this.eventCaptor.getValue())); + assertThat(newEvent.getEventType()).isEqualTo(CREATE_STUDENT_REGISTRATION); + assertThat(newEvent.getEventOutcome()).isEqualTo(STUDENT_REGISTRATION_CREATED); + + final var sagaFromDB = this.sagaService.findSagaById(this.saga.getSagaId()); + assertThat(sagaFromDB).isPresent(); + assertThat(sagaFromDB.get().getSagaState()).isEqualTo(CREATE_STUDENT_REGISTRATION.toString()); + var payload = JsonUtil.getJsonObjectFromString(AssessmentStudent.class, newEvent.getEventPayload()); + assertThat(payload.getStudentID()).isNotBlank(); + assertThat(payload.getAssessmentID()).isNotBlank(); + Optional assessmentStudent = assessmentStudentRepository.findByAssessmentEntity_AssessmentIDAndStudentID(UUID.fromString(payload.getAssessmentID()), UUID.fromString(payload.getStudentID())); + assertThat(assessmentStudent).isPresent(); + assertThat(assessmentStudent.get().getAssessmentStudentID()).isNotNull(); + assertThat(assessmentStudent.get().getStudentID().equals(payload.getStudentID())); + final var sagaStates = this.sagaService.findAllSagaStates(this.saga); + assertThat(sagaStates).hasSize(1); + assertThat(sagaStates.get(0).getSagaEventState()).isEqualTo(EventType.INITIATED.toString()); + assertThat(sagaStates.get(0).getSagaEventOutcome()).isEqualTo(EventOutcome.INITIATE_SUCCESS.toString()); } @SneakyThrows @Test - public void testHandleEvent_createAssessmentStudent_CREATED() { - SessionEntity session = sessionRepository.save(createMockSessionEntity()); - AssessmentEntity assessment = assessmentRepository.save(createMockAssessmentEntity(session, AssessmentTypeCodes.LTF12.getCode())); - AssessmentStudent student = createMockStudent(); - student.setAssessmentStudentID(null); - student.setAssessmentID(assessment.getAssessmentID().toString()); + public void testHandleEvent_createAssessmentStudent_ShouldPublishStudent() { + final var invocations = mockingDetails(this.messagePublisher).getInvocations().size(); + final var event = Event.builder() + .eventType(INITIATED) + .eventOutcome(EventOutcome.INITIATE_SUCCESS) + .sagaId(this.saga.getSagaId()) + .eventPayload(sagaPayload) + .build(); + this.studentRegistrationOrchestrator.handleEvent(event); + verify(this.messagePublisher, atMost(invocations + 2)).dispatchMessage(eq(this.studentRegistrationOrchestrator.getTopicToSubscribe()), this.eventCaptor.capture()); + final var newEvent = JsonUtil.getJsonObjectFromString(Event.class, new String(this.eventCaptor.getValue())); + assertThat(newEvent.getEventType()).isEqualTo(CREATE_STUDENT_REGISTRATION); + assertThat(newEvent.getEventOutcome()).isEqualTo(STUDENT_REGISTRATION_CREATED); - EasSagaEntity sagaEntity = this.studentRegistrationOrchestrator.createSaga(JsonUtil.getJsonString(student).get(), student.getUpdateUser()); - this.studentRegistrationOrchestrator.startSaga(sagaEntity); + final var sagaFromDB = this.sagaService.findSagaById(this.saga.getSagaId()); + assertThat(sagaFromDB).isPresent(); + assertThat(sagaFromDB.get().getSagaState()).isEqualTo(CREATE_STUDENT_REGISTRATION.toString()); - Optional createdStudent = studentRepository.findByAssessmentEntity_AssessmentIDAndStudentID(UUID.fromString(student.getAssessmentID()), UUID.fromString(student.getStudentID())); - assertThat(createdStudent).isPresent(); - assertThat(createdStudent.get().getAssessmentStudentID()).isNotNull(); - assertThat(createdStudent.get().getStudentID().toString()).isEqualTo(student.getStudentID()); + final var nextEvent = Event.builder() + .eventType(CREATE_STUDENT_REGISTRATION) + .eventOutcome(EventOutcome.STUDENT_REGISTRATION_CREATED) + .sagaId(this.saga.getSagaId()) + .eventPayload(newEvent.getEventPayload()) + .build(); + this.studentRegistrationOrchestrator.handleEvent(nextEvent); + verify(this.messagePublisher, atMost(invocations + 3)).dispatchMessage(eq(this.studentRegistrationOrchestrator.getTopicToSubscribe()), this.eventCaptor.capture()); + final var nextNewEvent = JsonUtil.getJsonObjectFromString(Event.class, new String(this.eventCaptor.getValue())); + assertThat(nextNewEvent.getEventType()).isEqualTo(PUBLISH_STUDENT_REGISTRATION); + assertThat(nextNewEvent.getEventOutcome()).isEqualTo(STUDENT_REGISTRATION_PUBLISHED); } + @SneakyThrows + @Test + public void testMarkSagaCompleteEvent_GivenEventAndSagaData_ShouldMarkSagaCompleted() throws IOException, InterruptedException, TimeoutException { + final var invocations = mockingDetails(this.messagePublisher).getInvocations().size(); + final var event = Event.builder() + .eventType(PUBLISH_STUDENT_REGISTRATION) + .eventOutcome(STUDENT_REGISTRATION_PUBLISHED) + .sagaId(this.saga.getSagaId()) + .eventPayload(sagaPayload) + .build(); + this.studentRegistrationOrchestrator.handleEvent(event); + + verify(this.messagePublisher, atMost(invocations + 1)).dispatchMessage(eq(this.studentRegistrationOrchestrator.getTopicToSubscribe()), this.eventCaptor.capture()); + final var newEvent = JsonUtil.getJsonObjectFromString(Event.class, new String(this.eventCaptor.getValue())); + assertThat(newEvent.getEventType()).isEqualTo(MARK_SAGA_COMPLETE); + assertThat(newEvent.getEventOutcome()).isEqualTo(SAGA_COMPLETED); + } } From 80595080732e3ba50d80260e2be9c9709ae3cf59 Mon Sep 17 00:00:00 2001 From: "QSL\\SumathiT" Date: Fri, 18 Oct 2024 13:53:36 -0700 Subject: [PATCH 3/5] Sonarlint findings. --- .../StudentRegistrationOrchestrator.java | 5 +---- .../java/ca/bc/gov/educ/eas/api/BaseEasAPITest.java | 2 -- .../StudentRegistrationOrchestratorTest.java | 13 +++++++------ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java b/api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java index e35c467..9a214a2 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestrator.java @@ -4,7 +4,6 @@ import ca.bc.gov.educ.eas.api.constants.SagaStatusEnum; import ca.bc.gov.educ.eas.api.constants.TopicsEnum; import ca.bc.gov.educ.eas.api.messaging.MessagePublisher; -import ca.bc.gov.educ.eas.api.messaging.jetstream.Publisher; import ca.bc.gov.educ.eas.api.model.v1.EasSagaEntity; import ca.bc.gov.educ.eas.api.model.v1.SagaEventStatesEntity; import ca.bc.gov.educ.eas.api.orchestrator.base.BaseOrchestrator; @@ -29,12 +28,10 @@ public class StudentRegistrationOrchestrator extends BaseOrchestrator { private final StudentRegistrationOrchestrationService studentRegistrationOrchestrationService; - private final Publisher publisher; - protected StudentRegistrationOrchestrator(final SagaService sagaService, final MessagePublisher messagePublisher, StudentRegistrationOrchestrationService studentRegistrationOrchestrationService, Publisher publisher) { + protected StudentRegistrationOrchestrator(final SagaService sagaService, final MessagePublisher messagePublisher, StudentRegistrationOrchestrationService studentRegistrationOrchestrationService) { super(sagaService, messagePublisher, AssessmentStudent.class, SagaEnum.CREATE_STUDENT_REGISTRATION.toString(), TopicsEnum.CREATE_STUDENT_REGISTRATION_SAGA_TOPIC.toString()); this.studentRegistrationOrchestrationService = studentRegistrationOrchestrationService; - this.publisher = publisher; } @Override diff --git a/api/src/test/java/ca/bc/gov/educ/eas/api/BaseEasAPITest.java b/api/src/test/java/ca/bc/gov/educ/eas/api/BaseEasAPITest.java index 24163e3..4211c19 100644 --- a/api/src/test/java/ca/bc/gov/educ/eas/api/BaseEasAPITest.java +++ b/api/src/test/java/ca/bc/gov/educ/eas/api/BaseEasAPITest.java @@ -2,7 +2,6 @@ import ca.bc.gov.educ.eas.api.model.v1.AssessmentEntity; import ca.bc.gov.educ.eas.api.model.v1.AssessmentStudentEntity; -import ca.bc.gov.educ.eas.api.model.v1.EasSagaEntity; import ca.bc.gov.educ.eas.api.model.v1.SessionEntity; import ca.bc.gov.educ.eas.api.struct.external.institute.v1.*; import ca.bc.gov.educ.eas.api.struct.v1.AssessmentStudent; @@ -10,7 +9,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import lombok.SneakyThrows; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; diff --git a/api/src/test/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestratorTest.java b/api/src/test/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestratorTest.java index f05611b..f28f581 100644 --- a/api/src/test/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestratorTest.java +++ b/api/src/test/java/ca/bc/gov/educ/eas/api/orchestrator/StudentRegistrationOrchestratorTest.java @@ -35,13 +35,14 @@ import static ca.bc.gov.educ.eas.api.constants.EventOutcome.*; import static ca.bc.gov.educ.eas.api.constants.EventType.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @SpringBootTest @ActiveProfiles("test") -public class StudentRegistrationOrchestratorTest extends BaseEasAPITest { +class StudentRegistrationOrchestratorTest extends BaseEasAPITest { @Autowired SessionRepository sessionRepository; @@ -79,7 +80,7 @@ public void setUp() { Mockito.reset(this.messagePublisher); SessionEntity session = sessionRepository.save(createMockSessionEntity()); AssessmentEntity assessment = assessmentRepository.save(createMockAssessmentEntity(session, AssessmentTypeCodes.LTF12.getCode())); - AssessmentStudent sagaData = createMockStudent(); + sagaData = createMockStudent(); sagaData.setAssessmentID(assessment.getAssessmentID().toString()); MockitoAnnotations.openMocks(this); sagaPayload = JsonUtil.getJsonString(sagaData).get(); @@ -88,7 +89,7 @@ public void setUp() { @SneakyThrows @Test - public void testHandleEvent_createAssessmentStudent_CreateStudentAndPostMessageToNats() { + void testHandleEvent_createAssessmentStudent_CreateStudentAndPostMessageToNats() { final var invocations = mockingDetails(this.messagePublisher).getInvocations().size(); final var event = Event.builder() .eventType(INITIATED) @@ -112,7 +113,7 @@ public void testHandleEvent_createAssessmentStudent_CreateStudentAndPostMessageT Optional assessmentStudent = assessmentStudentRepository.findByAssessmentEntity_AssessmentIDAndStudentID(UUID.fromString(payload.getAssessmentID()), UUID.fromString(payload.getStudentID())); assertThat(assessmentStudent).isPresent(); assertThat(assessmentStudent.get().getAssessmentStudentID()).isNotNull(); - assertThat(assessmentStudent.get().getStudentID().equals(payload.getStudentID())); + assertEquals(assessmentStudent.get().getStudentID().toString(), payload.getStudentID()); final var sagaStates = this.sagaService.findAllSagaStates(this.saga); assertThat(sagaStates).hasSize(1); assertThat(sagaStates.get(0).getSagaEventState()).isEqualTo(EventType.INITIATED.toString()); @@ -121,7 +122,7 @@ public void testHandleEvent_createAssessmentStudent_CreateStudentAndPostMessageT @SneakyThrows @Test - public void testHandleEvent_createAssessmentStudent_ShouldPublishStudent() { + void testHandleEvent_createAssessmentStudent_ShouldPublishStudent() { final var invocations = mockingDetails(this.messagePublisher).getInvocations().size(); final var event = Event.builder() .eventType(INITIATED) @@ -155,7 +156,7 @@ public void testHandleEvent_createAssessmentStudent_ShouldPublishStudent() { @SneakyThrows @Test - public void testMarkSagaCompleteEvent_GivenEventAndSagaData_ShouldMarkSagaCompleted() throws IOException, InterruptedException, TimeoutException { + void testMarkSagaCompleteEvent_GivenEventAndSagaData_ShouldMarkSagaCompleted() throws IOException, InterruptedException, TimeoutException { final var invocations = mockingDetails(this.messagePublisher).getInvocations().size(); final var event = Event.builder() .eventType(PUBLISH_STUDENT_REGISTRATION) From bd4df8bd7d1603d4128530d49f611e061fc7a047 Mon Sep 17 00:00:00 2001 From: "QSL\\SumathiT" Date: Fri, 18 Oct 2024 16:28:12 -0700 Subject: [PATCH 4/5] Changes to check student exists before creating new record. --- .../gov/educ/eas/api/service/v1/AssessmentStudentService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/AssessmentStudentService.java b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/AssessmentStudentService.java index e150774..40488ec 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/AssessmentStudentService.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/AssessmentStudentService.java @@ -13,6 +13,7 @@ import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import java.util.Optional; import java.util.UUID; @Service @@ -50,7 +51,8 @@ public AssessmentStudentEntity updateStudent(AssessmentStudentEntity assessmentS @Transactional(propagation = Propagation.REQUIRES_NEW) public AssessmentStudentEntity createStudent(AssessmentStudentEntity assessmentStudentEntity) { - return createAssessmentStudentWithHistory(assessmentStudentEntity); + Optional studentEntity = assessmentStudentRepository.findByAssessmentEntity_AssessmentIDAndStudentID(assessmentStudentEntity.getAssessmentEntity().getAssessmentID(), assessmentStudentEntity.getStudentID()); + return studentEntity.orElseGet(() -> createAssessmentStudentWithHistory(assessmentStudentEntity)); } public AssessmentStudentEntity createAssessmentStudentWithHistory(AssessmentStudentEntity assessmentStudentEntity) { From 5a23c83d0554972b639ea6035a0e323b750d83b9 Mon Sep 17 00:00:00 2001 From: "QSL\\SumathiT" Date: Mon, 21 Oct 2024 10:04:23 -0700 Subject: [PATCH 5/5] Changes to move the student exists check to student orchestration service. --- .../eas/api/service/v1/AssessmentStudentService.java | 4 ++++ .../v1/StudentRegistrationOrchestrationService.java | 12 ++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/AssessmentStudentService.java b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/AssessmentStudentService.java index 40488ec..5fb0f3a 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/AssessmentStudentService.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/AssessmentStudentService.java @@ -38,6 +38,10 @@ public AssessmentStudentEntity getStudentByID(UUID assessmentStudentID) { ); } + public Optional getStudentByAssessmentIDAndStudentID(UUID assessmentID, UUID studentID) { + return assessmentStudentRepository.findByAssessmentEntity_AssessmentIDAndStudentID(assessmentID, studentID); + } + @Transactional(propagation = Propagation.REQUIRES_NEW) public AssessmentStudentEntity updateStudent(AssessmentStudentEntity assessmentStudentEntity) { AssessmentStudentEntity currentAssessmentStudentEntity = assessmentStudentRepository.findById(assessmentStudentEntity.getAssessmentStudentID()).orElseThrow(() -> diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/StudentRegistrationOrchestrationService.java b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/StudentRegistrationOrchestrationService.java index 98e55e3..519bcbd 100644 --- a/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/StudentRegistrationOrchestrationService.java +++ b/api/src/main/java/ca/bc/gov/educ/eas/api/service/v1/StudentRegistrationOrchestrationService.java @@ -1,6 +1,7 @@ package ca.bc.gov.educ.eas.api.service.v1; import ca.bc.gov.educ.eas.api.mappers.v1.AssessmentStudentMapper; +import ca.bc.gov.educ.eas.api.model.v1.AssessmentStudentEntity; import ca.bc.gov.educ.eas.api.model.v1.EasSagaEntity; import ca.bc.gov.educ.eas.api.struct.v1.AssessmentStudent; import lombok.AccessLevel; @@ -8,6 +9,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.Optional; +import java.util.UUID; + @Service @Slf4j public class StudentRegistrationOrchestrationService { @@ -15,7 +19,6 @@ public class StudentRegistrationOrchestrationService { private static final AssessmentStudentMapper mapper = AssessmentStudentMapper.mapper; protected final SagaService sagaService; - //Inject Downstream Service @Getter(AccessLevel.PRIVATE) private final AssessmentStudentService assessmentStudentService; @@ -25,7 +28,12 @@ public StudentRegistrationOrchestrationService(AssessmentStudentService assessme } public void createNewStudentRegistration(AssessmentStudent assessmentStudent) { - assessmentStudentService.createStudent(mapper.toModel(assessmentStudent)); + Optional studentEntity = assessmentStudentService.getStudentByAssessmentIDAndStudentID(UUID.fromString(assessmentStudent.getAssessmentID()), UUID.fromString(assessmentStudent.getStudentID())); + if (studentEntity.isEmpty()) { + assessmentStudentService.createStudent(mapper.toModel(assessmentStudent)); + } else { + log.info("Student already exists in assessment {} ", assessmentStudent); + } } public void publishStudentRegistration(AssessmentStudent assessmentStudent, EasSagaEntity sagaEntity) {