Skip to content

Commit

Permalink
Merge pull request #254 from bcgov/feature/edx-1726
Browse files Browse the repository at this point in the history
Tweak saga step flow
  • Loading branch information
mightycox authored Oct 20, 2023
2 parents 1f3c227 + 747ddad commit 3fca623
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@
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.SagaStatusEnum.IN_PROGRESS;
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;
import static lombok.AccessLevel.PRIVATE;

import java.util.UUID;

import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.JsonProcessingException;
Expand Down Expand Up @@ -83,9 +82,9 @@ public void populateStepsToExecuteMap() {
this.stepBuilder()
.begin(CREATE_SCHOOL, this::createSchool)
.step(CREATE_SCHOOL, SCHOOL_CREATED, ONBOARD_INITIAL_USER, this::checkForInitialUser)
.step(ONBOARD_INITIAL_USER, INITIAL_USER_FOUND, CREATE_SCHOOL_PRIMARY_CODE, this::createPrimaryCode)
.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,6 +95,7 @@ public void createSchool(Event event, SagaEntity saga, CreateSchoolSagaData saga
final SagaEventStatesEntity eventStates =
this.createEventState(saga, event.getEventType(), event.getEventOutcome(), event.getEventPayload());
saga.setSagaState(CREATE_SCHOOL.toString());
saga.setStatus(IN_PROGRESS.toString());
this.getSagaService().updateAttachedSagaWithEvents(saga, eventStates);

School school = sagaData.getSchool();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public CreateSchoolOrchestratorService(
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void attachInstituteSchoolToSaga(String schoolId, SagaEntity saga)
throws JsonProcessingException {
List<School> result = restUtils.getSchoolById(saga.getSagaId(), schoolId);
List<School> result = this.restUtils.getSchoolById(saga.getSagaId(), schoolId);

if (result.isEmpty()) {
log.error("Could find School in Institute API :: {}", saga.getSagaId());
Expand All @@ -85,7 +85,7 @@ public void attachInstituteSchoolToSaga(String schoolId, SagaEntity saga)
CreateSchoolSagaData payload = JsonUtil.getJsonObjectFromString(CreateSchoolSagaData.class, saga.getPayload());
payload.setSchool(school);
saga.setPayload(JsonUtil.getJsonStringFromObject(payload));
sagaService.updateAttachedEntityDuringSagaProcess(saga);
this.sagaService.updateAttachedEntityDuringSagaProcess(saga);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
Expand All @@ -95,7 +95,7 @@ public void createPrimaryActivationCode(CreateSchoolSagaData sagaData) {
edxPrimaryActivationCode.setSchoolID(UUID.fromString(school.getSchoolId()));
RequestUtil.setAuditColumnsForCreate(edxPrimaryActivationCode);

service.generateOrRegeneratePrimaryEdxActivationCode(SCHOOL, school.getSchoolId(), edxPrimaryActivationCode);
this.service.generateOrRegeneratePrimaryEdxActivationCode(SCHOOL, school.getSchoolId(), edxPrimaryActivationCode);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
Expand All @@ -104,12 +104,13 @@ public void sendPrimaryActivationCodeNotification(CreateSchoolSagaData sagaData)
School school = sagaData.getSchool();
UUID schoolId = UUID.fromString(school.getSchoolId());

Optional<EdxActivationCodeEntity> edxActivationCodeEntity = edxActivationCodeRepository.findEdxActivationCodeEntitiesBySchoolIDAndIsPrimaryTrueAndDistrictIDIsNull(schoolId);
Optional<EdxActivationCodeEntity> edxActivationCodeEntity =
edxActivationCodeRepository.findEdxActivationCodeEntitiesBySchoolIDAndIsPrimaryTrueAndDistrictIDIsNull(schoolId);

EmailNotification emailNotification = EmailNotification.builder()
.fromEmail(emailProperties.getEdxSchoolUserActivationInviteEmailFrom())
.fromEmail(this.emailProperties.getEdxSchoolUserActivationInviteEmailFrom())
.toEmail(user.getEmail())
.subject(emailProperties.getEdxSecureExchangePrimaryCodeNotificationEmailSubject())
.subject(this.emailProperties.getEdxSecureExchangePrimaryCodeNotificationEmailSubject())
.templateName("edx.school.primary-code.notification")
.emailFields(Map.of(
"firstName", user.getFirstName(),
Expand All @@ -120,7 +121,7 @@ public void sendPrimaryActivationCodeNotification(CreateSchoolSagaData sagaData)
))
.build();

emailNotificationService.sendEmail(emailNotification);
this.emailNotificationService.sendEmail(emailNotification);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
Expand Down Expand Up @@ -150,16 +151,16 @@ public void startEdxSchoolUserInviteSaga(CreateSchoolSagaData sagaData) {
if (sagaInProgress.isEmpty()) {
try {
SagaEntity sagaEntity = SAGA_DATA_MAPPER.toModel(String.valueOf(sagaName), inviteSagaData);
final SagaEntity saga = activationInviteOrchestrator.createSaga(sagaEntity);
activationInviteOrchestrator.startSaga(saga);
final SagaEntity saga = this.activationInviteOrchestrator.createSaga(sagaEntity);
this.activationInviteOrchestrator.startSaga(saga);
} catch (JsonProcessingException e) {
throw new SagaRuntimeException(e);
}
}
}

public EdxActivationCodeEntity findPrimaryCode(String schoolId) {
return service.findPrimaryEdxActivationCode(SCHOOL, schoolId);
return this.service.findPrimaryEdxActivationCode(SCHOOL, schoolId);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -125,26 +125,26 @@ public void after() {
@Test
void testCreateSchool_GivenEventAndSagaData_shouldPostEventToInstituteApi() throws JsonProcessingException {
final CreateSchoolSagaData mockData = createMockCreateSchoolSagaData(this.mockSchool);
final SagaEntity saga = saveMockSaga(mockData);
final SagaEntity saga = this.saveMockSaga(mockData);

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

orchestrator.createSchool(event, saga, mockData);
this.orchestrator.createSchool(event, saga, mockData);

verify(messagePublisher, atMost(invocations + 1))
.dispatchMessage(eq(INSTITUTE_API_TOPIC.toString()), eventCaptor.capture());
verify(this.messagePublisher, atMost(invocations + 1))
.dispatchMessage(eq(INSTITUTE_API_TOPIC.toString()), this.eventCaptor.capture());

final Optional<SagaEntity> sagaFromDB = sagaService.findSagaById(saga.getSagaId());
final Optional<SagaEntity> sagaFromDB = this.sagaService.findSagaById(saga.getSagaId());
assertThat(sagaFromDB).isPresent();
assertThat(sagaFromDB.get().getSagaState()).isEqualTo(CREATE_SCHOOL.toString());

final List<SagaEventStatesEntity> sagaStates = sagaService.findAllSagaStates(saga);
final List<SagaEventStatesEntity> sagaStates = this.sagaService.findAllSagaStates(saga);
assertThat(sagaStates).hasSize(1);

assertThat(sagaStates.get(0).getSagaEventState()).isEqualTo(INITIATED.toString());
Expand All @@ -156,21 +156,21 @@ void testCheckForIntitialUser_GivenAnInitialUser_sagaShouldOnboardUser()
throws JsonProcessingException, IOException, TimeoutException, InterruptedException {
final CreateSchoolSagaData mockData = createMockCreateSchoolSagaData(this.mockSchool);
mockData.setInitialEdxUser(createMockInitialUser());
SagaEntity saga = saveMockSaga(mockData);
SagaEntity saga = this.saveMockSaga(mockData);

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

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

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

Expand All @@ -183,32 +183,32 @@ void testCheckForIntitialUser_GivenAnInitialUser_sagaShouldOnboardUser()
void testCheckForIntitialUser_GivenNoIntialUser_sagaShouldBeCompleted()
throws JsonProcessingException, IOException, TimeoutException, InterruptedException {
final CreateSchoolSagaData mockData = createMockCreateSchoolSagaData(this.mockSchool);
SagaEntity saga = saveMockSaga(mockData);
SagaEntity saga = this.saveMockSaga(mockData);

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

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

final Event onboardingEvent = JsonUtil.getJsonObjectFromString(Event.class, new String(eventCaptor.getValue()));
final Event onboardingEvent = JsonUtil.getJsonObjectFromString(Event.class, new String(this.eventCaptor.getValue()));
CreateSchoolSagaData onboardingSagaData =
JsonUtil.getJsonObjectFromString(CreateSchoolSagaData.class, onboardingEvent.getEventPayload());

assertThat(onboardingSagaData.getInitialEdxUser()).isNotPresent();

orchestrator.handleEvent(onboardingEvent);
this.orchestrator.handleEvent(onboardingEvent);

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

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

assertThat(lastEvent.getEventType()).isEqualTo(MARK_SAGA_COMPLETE);
assertThat(lastEvent.getEventOutcome()).isEqualTo(SAGA_COMPLETED);
Expand All @@ -219,26 +219,27 @@ void testCreatePrimaryCode_GivenAnInitialUserAndSchool_sagaShouldCreatePrimarySc
throws TimeoutException, IOException, InterruptedException {
final CreateSchoolSagaData mockData = createMockCreateSchoolSagaData(this.mockInstituteSchool);
mockData.setInitialEdxUser(createMockInitialUser());
SagaEntity saga = saveMockSaga(mockData);
SagaEntity saga = this.saveMockSaga(mockData);

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

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

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

UUID schooId = UUID.fromString(newData.getSchool().getSchoolId());
Optional<EdxActivationCodeEntity> codeOptional = edxActivationCodeRepository.findEdxActivationCodeEntitiesBySchoolIDAndIsPrimaryTrueAndDistrictIDIsNull(schooId);
Optional<EdxActivationCodeEntity> codeOptional =
edxActivationCodeRepository.findEdxActivationCodeEntitiesBySchoolIDAndIsPrimaryTrueAndDistrictIDIsNull(schooId);

assertThat(codeOptional).isPresent();
assertThat(newData.getInitialEdxUser()).isPresent();
Expand All @@ -251,27 +252,27 @@ void testSendPrimaryCode_GivenAnInitialUser_School_AndPrimaryCode_sagaShouldSend
throws TimeoutException, IOException, InterruptedException {
final CreateSchoolSagaData mockData = createMockCreateSchoolSagaData(this.mockInstituteSchool);
mockData.setInitialEdxUser(createMockInitialUser());
SagaEntity saga = saveMockSaga(mockData);
SagaEntity saga = this.saveMockSaga(mockData);

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

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

final Event createCodeEvent = JsonUtil.getJsonObjectFromBytes(Event.class, eventCaptor.getValue());
orchestrator.handleEvent(createCodeEvent);
final Event createCodeEvent = JsonUtil.getJsonObjectFromBytes(Event.class, this.eventCaptor.getValue());
this.orchestrator.handleEvent(createCodeEvent);

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

final Event sendCodeEvent = JsonUtil.getJsonObjectFromBytes(Event.class, eventCaptor.getValue());
final Event sendCodeEvent = JsonUtil.getJsonObjectFromBytes(Event.class, this.eventCaptor.getValue());

assertThat(sendCodeEvent.getEventType()).isEqualTo(SEND_PRIMARY_ACTIVATION_CODE);
assertThat(sendCodeEvent.getEventOutcome()).isEqualTo(PRIMARY_ACTIVATION_CODE_SENT);
Expand All @@ -281,29 +282,29 @@ void testSendPrimaryCode_GivenAnInitialUser_School_AndPrimaryCode_sagaShouldSend
void testInviteInitialUser_GivenEventAndSaga_sagaShouldStartInviteSaga() throws IOException, InterruptedException, TimeoutException {
final CreateSchoolSagaData mockData = createMockCreateSchoolSagaData(this.mockInstituteSchool);
mockData.setInitialEdxUser(createMockInitialUser());
SagaEntity saga = saveMockSaga(mockData);
createRoleAndPermissionData(edxPermissionRepository, edxRoleRepository);
SagaEntity saga = this.saveMockSaga(mockData);
createRoleAndPermissionData(this.edxPermissionRepository, this.edxRoleRepository);

final int invocations = mockingDetails(messagePublisher).getInvocations().size();
final int invocations = mockingDetails(this.messagePublisher).getInvocations().size();
final Event event = Event.builder()
.eventType(SEND_PRIMARY_ACTIVATION_CODE)
.eventOutcome(PRIMARY_ACTIVATION_CODE_SENT)
.sagaId(saga.getSagaId())
.eventPayload(getJsonString(mockData))
.build();
orchestrator.handleEvent(event);
this.orchestrator.handleEvent(event);

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

Event currentEventState = JsonUtil.getJsonObjectFromBytes(Event.class, eventCaptor.getValue());
Event currentEventState = JsonUtil.getJsonObjectFromBytes(Event.class, this.eventCaptor.getValue());
assertThat(currentEventState.getEventType()).isEqualTo(INVITE_INITIAL_USER);
assertThat(currentEventState.getEventOutcome()).isEqualTo(INITIAL_USER_INVITED);

School school = mockData.getSchool();
EdxUser user = mockData.getInitialEdxUser().orElseThrow();

final Optional<SagaEntity> sagaInProgress = sagaService
final Optional<SagaEntity> sagaInProgress = this.sagaService
.findAllActiveUserActivationInviteSagasBySchoolIDAndEmailId(
UUID.fromString(school.getSchoolId()),
user.getEmail(),
Expand Down Expand Up @@ -357,19 +358,19 @@ private Optional<EdxUser> createMockInitialUser() {
}

private void tearDown() {
sagaEventStateRepository.deleteAll();
sagaRepository.deleteAll();
edxUserSchoolRepository.deleteAll();
edxActivationCodeRepository.deleteAll();
edxRoleRepository.deleteAll();
edxPermissionRepository.deleteAll();
this.sagaEventStateRepository.deleteAll();
this.sagaRepository.deleteAll();
this.edxUserSchoolRepository.deleteAll();
this.edxActivationCodeRepository.deleteAll();
this.edxRoleRepository.deleteAll();
this.edxPermissionRepository.deleteAll();
}

private SagaEntity saveMockSaga(CreateSchoolSagaData mockSaga) {
MockitoAnnotations.openMocks(this);
try {
SagaEntity sagaEntity = SAGA_DATA_MAPPER.toModel(String.valueOf(SagaEnum.CREATE_NEW_SCHOOL_SAGA), mockSaga);
return sagaService.createSagaRecordInDB(sagaEntity);
return this.sagaService.createSagaRecordInDB(sagaEntity);
} catch (Exception e) {
throw new SagaRuntimeException(e);
}
Expand Down

0 comments on commit 3fca623

Please sign in to comment.