Skip to content

Commit

Permalink
Merge pull request #249 from bcgov/feature/edx-1726
Browse files Browse the repository at this point in the history
Put business logic back into EDX-API
  • Loading branch information
arcshiftsolutions authored Oct 18, 2023
2 parents b6533a9 + 198c715 commit 4aede99
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ public enum EventOutcome {
SCHOOL_UPDATED,
SCHOOL_MOVED,
USERS_TO_NEW_SCHOOL_COPIED,
CREATE_SCHOOL_SAGA_HAS_ADMIN,
CREATE_SCHOOL_SAGA_HAS_NO_ADMIN,
NO_INITIAL_USER_FOUND,
INITIAL_USER_FOUND,
INITIAL_USER_INVITED

}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public enum EventType {
COPY_USERS_TO_NEW_SCHOOL,
GET_PAGINATED_SCHOOLS,
MOVE_SCHOOL,
ONBOARD_INITIAL_USER,
INVITE_INITIAL_USER

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package ca.bc.gov.educ.api.edx.orchestrator;

import static ca.bc.gov.educ.api.edx.constants.EventOutcome.CREATE_SCHOOL_SAGA_HAS_ADMIN;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.CREATE_SCHOOL_SAGA_HAS_NO_ADMIN;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.SCHOOL_PRIMARY_CODE_CREATED;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.PRIMARY_ACTIVATION_CODE_SENT;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.INITIAL_USER_FOUND;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.INITIAL_USER_INVITED;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.NO_INITIAL_USER_FOUND;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.PRIMARY_ACTIVATION_CODE_SENT;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.SCHOOL_CREATED;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.SCHOOL_PRIMARY_CODE_CREATED;
import static ca.bc.gov.educ.api.edx.constants.EventType.CREATE_SCHOOL;
import static ca.bc.gov.educ.api.edx.constants.EventType.CREATE_SCHOOL_PRIMARY_CODE;
import static ca.bc.gov.educ.api.edx.constants.EventType.INVITE_INITIAL_USER;
import static ca.bc.gov.educ.api.edx.constants.EventType.ONBOARD_INITIAL_USER;
import static ca.bc.gov.educ.api.edx.constants.EventType.SEND_PRIMARY_ACTIVATION_CODE;
import static ca.bc.gov.educ.api.edx.constants.EventType.CREATE_SCHOOL_PRIMARY_CODE;
import static ca.bc.gov.educ.api.edx.constants.SagaEnum.CREATE_NEW_SCHOOL_SAGA;
import static ca.bc.gov.educ.api.edx.constants.TopicsEnum.EDX_API_TOPIC;
import static ca.bc.gov.educ.api.edx.constants.TopicsEnum.INSTITUTE_API_TOPIC;
Expand All @@ -18,7 +20,6 @@

import com.fasterxml.jackson.core.JsonProcessingException;


import ca.bc.gov.educ.api.edx.messaging.MessagePublisher;
import ca.bc.gov.educ.api.edx.messaging.jetstream.Publisher;
import ca.bc.gov.educ.api.edx.model.v1.SagaEntity;
Expand Down Expand Up @@ -79,9 +80,10 @@ protected CreateSchoolOrchestrator(
public void populateStepsToExecuteMap() {
this.stepBuilder()
.begin(CREATE_SCHOOL, this::createSchool)
.step(CREATE_SCHOOL, CREATE_SCHOOL_SAGA_HAS_ADMIN, CREATE_SCHOOL_PRIMARY_CODE, this::createPrimaryCode)
.end(CREATE_SCHOOL, CREATE_SCHOOL_SAGA_HAS_NO_ADMIN, this::completeCreateSchoolSagaWithNoUser)
.step(CREATE_SCHOOL, SCHOOL_CREATED, ONBOARD_INITIAL_USER, this::checkForInitialUser)
.end(ONBOARD_INITIAL_USER, NO_INITIAL_USER_FOUND, this::completeCreateSchoolSagaWithNoUser)
.or()
.step(ONBOARD_INITIAL_USER, INITIAL_USER_FOUND, CREATE_SCHOOL_PRIMARY_CODE, this::createPrimaryCode)
.step(CREATE_SCHOOL_PRIMARY_CODE, SCHOOL_PRIMARY_CODE_CREATED, SEND_PRIMARY_ACTIVATION_CODE, this::sendPrimaryCode)
.step(SEND_PRIMARY_ACTIVATION_CODE, PRIMARY_ACTIVATION_CODE_SENT, INVITE_INITIAL_USER, this::inviteInitialUser)
.end(INVITE_INITIAL_USER, INITIAL_USER_INVITED);
Expand All @@ -96,20 +98,39 @@ public void createSchool(Event event, SagaEntity saga, CreateSchoolSagaData saga

School school = sagaData.getSchool();

final Event.EventBuilder eventBuilder = Event.builder()
final Event instituteEvent = Event.builder()
.eventType(CREATE_SCHOOL)
.replyTo(this.getTopicToSubscribe())
.eventPayload(JsonUtil.getJsonStringFromObject(school))
.sagaId(event.getSagaId())
.build();

this.postMessageToTopic(INSTITUTE_API_TOPIC.toString(), instituteEvent);
log.info("message sent to INSTITUTE_API_TOPIC for CREATE_SCHOOL Event. :: {}", saga.getSagaId());
}

public void checkForInitialUser(Event event, SagaEntity saga, CreateSchoolSagaData sagaData)
throws JsonProcessingException {
final SagaEventStatesEntity eventStates =
this.createEventState(saga, event.getEventType(), event.getEventOutcome(), event.getEventPayload());
saga.setSagaState(ONBOARD_INITIAL_USER.toString());
this.getSagaService().updateAttachedSagaWithEvents(saga, eventStates);

final Event.EventBuilder nextEventBuilder = Event.builder()
.eventType(ONBOARD_INITIAL_USER)
.replyTo(this.getTopicToSubscribe())
.eventPayload(JsonUtil.getJsonStringFromObject(sagaData))
.sagaId(event.getSagaId());

if (sagaData.getInitialEdxUser().isEmpty()) {
eventBuilder.eventOutcome(CREATE_SCHOOL_SAGA_HAS_NO_ADMIN);
if (sagaData.getInitialEdxUser().isPresent()) {
nextEventBuilder.eventOutcome(INITIAL_USER_FOUND);
} else {
eventBuilder.eventOutcome(CREATE_SCHOOL_SAGA_HAS_ADMIN);
nextEventBuilder.eventOutcome(NO_INITIAL_USER_FOUND);
}

this.postMessageToTopic(INSTITUTE_API_TOPIC.toString(), eventBuilder.build());
log.info("message sent to INSTITUTE_API_TOPIC for CREATE_SCHOOL Event. :: {}", saga.getSagaId());
Event nextEvent = nextEventBuilder.build();
this.postMessageToTopic(this.getTopicToSubscribe(), nextEvent);
log.info("message sent to EDX_API_TOPIC for ONBOARD_INITIAL_USER Event. :: {}", saga.getSagaId());
}

public void createPrimaryCode(Event event, SagaEntity saga, CreateSchoolSagaData sagaData)
Expand All @@ -134,7 +155,6 @@ public void createPrimaryCode(Event event, SagaEntity saga, CreateSchoolSagaData
.eventPayload(JsonUtil.getJsonStringFromObject(updatedSagaData))
.build();
this.postMessageToTopic(this.getTopicToSubscribe(), nextEvent);
publishToJetStream(nextEvent, saga);
log.info("message sent to EDX_API_TOPIC for CREATE_SCHOOL_PRIMARY_CODE Event. :: {}", saga.getSagaId());
}

Expand All @@ -152,7 +172,6 @@ public void sendPrimaryCode(Event event, SagaEntity saga, CreateSchoolSagaData s
.eventPayload(JsonUtil.getJsonStringFromObject(sagaData))
.build();
this.postMessageToTopic(this.getTopicToSubscribe(), nextEvent);
publishToJetStream(nextEvent, saga);
log.info("message sent to EDX_API_TOPIC for SEND_PRIMARY_ACTIVATION_CODE Event. :: {}", saga.getSagaId());
}

Expand All @@ -170,7 +189,6 @@ private void inviteInitialUser(Event event, SagaEntity saga, CreateSchoolSagaDat
.eventPayload(JsonUtil.getJsonStringFromObject(sagaData))
.build();
this.postMessageToTopic(this.getTopicToSubscribe(), nextEvent);
publishToJetStream(nextEvent, saga);
log.info("message sent to EDX_API_TOPIC for INVITE_INITIAL_USER Event. :: {}", saga.getSagaId());
}

Expand All @@ -179,18 +197,6 @@ private void completeCreateSchoolSagaWithNoUser(
final SagaEntity saga,
final CreateSchoolSagaData sagaData
) throws JsonProcessingException {
final Event nextEvent = Event.builder().sagaId(saga.getSagaId())
.eventType(CREATE_SCHOOL)
.eventOutcome(CREATE_SCHOOL_SAGA_HAS_NO_ADMIN)
.replyTo(getTopicToSubscribe())
.eventPayload(JsonUtil.getJsonStringFromObject(sagaData))
.build();
this.postMessageToTopic(this.getTopicToSubscribe(), nextEvent);
publishToJetStream(nextEvent, saga);
log.info("CreateSchoolSaga has ended without an initial admin user being created :: {}", saga.getSagaId());
}

private void publishToJetStream(final Event event, SagaEntity saga) {
publisher.dispatchChoreographyEvent(event, saga);
log.info("CREATE_NEW_SCHOOL_SAGA has ended with NO_INITIAL_USER_FOUND :: {}", saga.getSagaId());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
package ca.bc.gov.educ.api.edx.orchestrator;

import static ca.bc.gov.educ.api.edx.constants.EventOutcome.INITIAL_USER_FOUND;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.INITIAL_USER_INVITED;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.INITIATE_SUCCESS;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.NO_INITIAL_USER_FOUND;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.PRIMARY_ACTIVATION_CODE_SENT;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.SAGA_COMPLETED;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.SCHOOL_CREATED;
import static ca.bc.gov.educ.api.edx.constants.EventOutcome.SCHOOL_PRIMARY_CODE_CREATED;
import static ca.bc.gov.educ.api.edx.constants.EventType.CREATE_SCHOOL;
import static ca.bc.gov.educ.api.edx.constants.EventType.CREATE_SCHOOL_PRIMARY_CODE;
import static ca.bc.gov.educ.api.edx.constants.EventType.INITIATED;
import static ca.bc.gov.educ.api.edx.constants.EventType.INVITE_INITIAL_USER;
import static ca.bc.gov.educ.api.edx.constants.EventType.MARK_SAGA_COMPLETE;
import static ca.bc.gov.educ.api.edx.constants.EventType.ONBOARD_INITIAL_USER;
import static ca.bc.gov.educ.api.edx.constants.EventType.SEND_PRIMARY_ACTIVATION_CODE;
import static ca.bc.gov.educ.api.edx.constants.SagaEnum.EDX_SCHOOL_USER_ACTIVATION_INVITE_SAGA;
import static ca.bc.gov.educ.api.edx.constants.TopicsEnum.INSTITUTE_API_TOPIC;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.verify;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeoutException;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;

import com.fasterxml.jackson.core.JsonProcessingException;

import ca.bc.gov.educ.api.edx.constants.SagaEnum;
import ca.bc.gov.educ.api.edx.constants.SagaStatusEnum;
import ca.bc.gov.educ.api.edx.controller.BaseSagaControllerTest;
Expand All @@ -9,7 +48,12 @@
import ca.bc.gov.educ.api.edx.model.v1.EdxActivationCodeEntity;
import ca.bc.gov.educ.api.edx.model.v1.SagaEntity;
import ca.bc.gov.educ.api.edx.model.v1.SagaEventStatesEntity;
import ca.bc.gov.educ.api.edx.repository.*;
import ca.bc.gov.educ.api.edx.repository.EdxActivationCodeRepository;
import ca.bc.gov.educ.api.edx.repository.EdxPermissionRepository;
import ca.bc.gov.educ.api.edx.repository.EdxRoleRepository;
import ca.bc.gov.educ.api.edx.repository.EdxUserSchoolRepository;
import ca.bc.gov.educ.api.edx.repository.SagaEventStateRepository;
import ca.bc.gov.educ.api.edx.repository.SagaRepository;
import ca.bc.gov.educ.api.edx.rest.RestUtils;
import ca.bc.gov.educ.api.edx.service.v1.SagaService;
import ca.bc.gov.educ.api.edx.struct.v1.CreateSchoolSagaData;
Expand All @@ -18,29 +62,6 @@
import ca.bc.gov.educ.api.edx.struct.v1.School;
import ca.bc.gov.educ.api.edx.utils.JsonUtil;
import ca.bc.gov.educ.api.edx.utils.RequestUtil;
import com.fasterxml.jackson.core.JsonProcessingException;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeoutException;

import static ca.bc.gov.educ.api.edx.constants.EventOutcome.*;
import static ca.bc.gov.educ.api.edx.constants.EventType.*;
import static ca.bc.gov.educ.api.edx.constants.SagaEnum.EDX_SCHOOL_USER_ACTIVATION_INVITE_SAGA;
import static ca.bc.gov.educ.api.edx.constants.TopicsEnum.INSTITUTE_API_TOPIC;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

class CreateSchoolOrchestratorTest extends BaseSagaControllerTest {

Expand Down Expand Up @@ -118,6 +139,60 @@ void testCreateSchool_GivenEventAndSagaData_shouldPostEventToInstituteApi() thro
assertThat(sagaStates.get(0).getSagaEventOutcome()).isEqualTo(INITIATE_SUCCESS.toString());
}

@Test
void testCheckForIntitialUser_GivenAnInitialUser_sagaShouldOnboardUser()
throws JsonProcessingException, IOException, TimeoutException, InterruptedException {
final School mockSchoolFromInstitute = this.createMockSchool();
final CreateSchoolSagaData mockData = createMockCreateSchoolSagaData(mockSchoolFromInstitute);
mockData.setInitialEdxUser(createMockInitialUser());
SagaEntity saga = saveMockSaga(mockData);

final int invocations = mockingDetails(messagePublisher).getInvocations().size();
final Event event = Event.builder()
.eventType(CREATE_SCHOOL)
.eventOutcome(SCHOOL_CREATED)
.sagaId(saga.getSagaId())
.eventPayload(getJsonString(mockSchoolFromInstitute))
.build();
orchestrator.handleEvent(event);

verify(messagePublisher, atMost(invocations + 1))
.dispatchMessage(eq(orchestrator.getTopicToSubscribe()), eventCaptor.capture());

final Event nextEvent = JsonUtil.getJsonObjectFromString(Event.class, new String(eventCaptor.getValue()));
CreateSchoolSagaData newData =
JsonUtil.getJsonObjectFromString(CreateSchoolSagaData.class, nextEvent.getEventPayload());

assertThat(newData.getInitialEdxUser()).isPresent();
assertThat(nextEvent.getEventType()).isEqualTo(ONBOARD_INITIAL_USER);
assertThat(nextEvent.getEventOutcome()).isEqualTo(INITIAL_USER_FOUND);
}

@Test
void testCheckForIntitialUser_GivenNoIntialUser_sagaShouldBeCompleted()
throws JsonProcessingException, IOException, TimeoutException, InterruptedException {
final School mockSchoolFromInstitute = this.createMockSchool();
final CreateSchoolSagaData mockData = createMockCreateSchoolSagaData(mockSchoolFromInstitute);
SagaEntity saga = saveMockSaga(mockData);

final int invocations = mockingDetails(messagePublisher).getInvocations().size();
final Event event = Event.builder()
.eventType(ONBOARD_INITIAL_USER)
.eventOutcome(NO_INITIAL_USER_FOUND)
.sagaId(saga.getSagaId())
.eventPayload(getJsonString(mockSchoolFromInstitute))
.build();
orchestrator.handleEvent(event);

verify(messagePublisher, atMost(invocations + 1))
.dispatchMessage(eq(orchestrator.getTopicToSubscribe()), eventCaptor.capture());

final Event nextEvent = JsonUtil.getJsonObjectFromString(Event.class, new String(eventCaptor.getValue()));

assertThat(nextEvent.getEventType()).isEqualTo(MARK_SAGA_COMPLETE);
assertThat(nextEvent.getEventOutcome()).isEqualTo(SAGA_COMPLETED);
}

@Test
void testCreatePrimaryCode_GivenAnInitialUserAndSchool_sagaShouldCreatePrimarySchoolCode()
throws TimeoutException, IOException, InterruptedException {
Expand All @@ -128,8 +203,8 @@ void testCreatePrimaryCode_GivenAnInitialUserAndSchool_sagaShouldCreatePrimarySc

final int invocations = mockingDetails(messagePublisher).getInvocations().size();
final Event event = Event.builder()
.eventType(CREATE_SCHOOL)
.eventOutcome(CREATE_SCHOOL_SAGA_HAS_ADMIN)
.eventType(ONBOARD_INITIAL_USER)
.eventOutcome(INITIAL_USER_FOUND)
.sagaId(saga.getSagaId())
.eventPayload(getJsonString(mockSchoolFromInstitute))
.build();
Expand Down Expand Up @@ -161,8 +236,8 @@ void testSendPrimaryCode_GivenAnInitialUser_School_AndPrimaryCode_sagaShouldSend

final int invocations = mockingDetails(messagePublisher).getInvocations().size();
final Event event = Event.builder()
.eventType(CREATE_SCHOOL)
.eventOutcome(CREATE_SCHOOL_SAGA_HAS_ADMIN)
.eventType(ONBOARD_INITIAL_USER)
.eventOutcome(INITIAL_USER_FOUND)
.sagaId(saga.getSagaId())
.eventPayload(getJsonString(mockSchoolFromInstitute))
.build();
Expand Down Expand Up @@ -219,32 +294,6 @@ void testInviteInitialUser_GivenEventAndSaga_sagaShouldStartInviteSaga() throws
assertThat(sagaInProgress).isPresent();
}

@Test
void testCreateSchool_GivenNoInitialUser_sagaShouldEndEarly() throws TimeoutException, IOException, InterruptedException {
CreateSchoolSagaData mockData = createMockCreateSchoolSagaData(createMockSchool());
SagaEntity saga = saveMockSaga(mockData);

final int invocations = mockingDetails(messagePublisher).getInvocations().size();
final Event event = Event.builder()
.eventType(CREATE_SCHOOL)
.eventOutcome(CREATE_SCHOOL_SAGA_HAS_NO_ADMIN)
.sagaId(saga.getSagaId())
.eventPayload(getJsonString(mockData))
.build();

orchestrator.handleEvent(event);

verify(messagePublisher, atMost(invocations + 2))
.dispatchMessage(eq(orchestrator.getTopicToSubscribe()), eventCaptor.capture());

final Event newEvent = JsonUtil.getJsonObjectFromString(Event.class, new String(eventCaptor.getValue()));

assertThat(newEvent.getEventType()).isEqualTo(MARK_SAGA_COMPLETE);
assertThat(newEvent.getEventOutcome()).isEqualTo(SAGA_COMPLETED);

tearDown();
}

private CreateSchoolSagaData createMockCreateSchoolSagaData(School school) {
CreateSchoolSagaData sagaData = new CreateSchoolSagaData();
RequestUtil.setAuditColumnsForCreate(school);
Expand Down

0 comments on commit 4aede99

Please sign in to comment.