Skip to content

Commit

Permalink
GRAD2-3060 (#721)
Browse files Browse the repository at this point in the history
* start of gdc messaging

(cherry picked from commit 30eb4e1)

* gdc messaging

(cherry picked from commit bd02fa6)

* use grad student service

(cherry picked from commit 1671dea)

* clean

(cherry picked from commit 541dbb8)

* extend base integration test

(cherry picked from commit a406871)

* required fields for grad student record payload

(cherry picked from commit 61bec8a)

* clean

(cherry picked from commit 1cbaddc)

* test

(cherry picked from commit 6e49a5f)

* add studentStatusCode to grad student record messaging

(cherry picked from commit 649c7ba)

* add student id and graduated status to grad stud messaging (#706)

* add student id and graduated status to grad stud messaging

* test parseGraduationStatus

(cherry picked from commit 38f1f56)

---------

Co-authored-by: alexmcdermid <[email protected]>
  • Loading branch information
cditcher and alexmcdermid authored Dec 10, 2024
1 parent 7283d63 commit 9fb18d0
Show file tree
Hide file tree
Showing 13 changed files with 304 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public enum Topics {
* GradStatus events topic.
*/
GRAD_STATUS_EVENT_TOPIC,
GRAD_STUDENT_API_FETCH_GRAD_STATUS_TOPIC
GRAD_STUDENT_API_FETCH_GRAD_STATUS_TOPIC,
GRAD_STUDENT_API_FETCH_GRAD_STUDENT_TOPIC,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package ca.bc.gov.educ.api.gradstudent.messaging.jetstream;

import ca.bc.gov.educ.api.gradstudent.constant.Topics;
import ca.bc.gov.educ.api.gradstudent.exception.EntityNotFoundException;
import ca.bc.gov.educ.api.gradstudent.model.dc.Event;
import ca.bc.gov.educ.api.gradstudent.model.dc.GradStudentRecordPayload;
import ca.bc.gov.educ.api.gradstudent.model.dto.messaging.GradStudentRecord;
import ca.bc.gov.educ.api.gradstudent.service.GradStudentService;
import ca.bc.gov.educ.api.gradstudent.util.EducGradStudentApiConstants;
import ca.bc.gov.educ.api.gradstudent.util.EducGradStudentApiUtils;
import ca.bc.gov.educ.api.gradstudent.util.JsonUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.nats.client.*;
import lombok.val;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.UUID;

@Component
public class FetchGradStudentRecordSubscriber implements MessageHandler {

private final Connection natsConnection;
private Dispatcher dispatcher;
private final GradStudentService gradStudentService;
public static final String RESPONDING_BACK_TO_NATS_ON_CHANNEL = "responding back to NATS on {} channel ";
public static final String PAYLOAD_LOG = "payload is :: {}";
private static final String TOPIC = Topics.GRAD_STUDENT_API_FETCH_GRAD_STUDENT_TOPIC.toString();
private static final Logger log = LoggerFactory.getLogger(FetchGradStudentRecordSubscriber.class);

@Autowired
public FetchGradStudentRecordSubscriber(final Connection natsConnection, GradStudentService gradStudentService, EducGradStudentApiConstants constants) {
this.natsConnection = natsConnection;
this.gradStudentService = gradStudentService;
}

@PostConstruct
public void subscribe() {
this.dispatcher = this.natsConnection.createDispatcher(this);
this.dispatcher.subscribe(TOPIC);
}

@Override
public void onMessage(Message message) {
val eventString = new String(message.getData());
log.debug("Received message: {}", eventString);
String response;

try {
Event event = JsonUtil.getJsonObjectFromString(Event.class, eventString);
log.info("received GET_STUDENT event :: {}", event.getSagaId());
log.trace(PAYLOAD_LOG, event.getEventPayload());
UUID studentId = JsonUtil.getJsonObjectFromString(UUID.class, event.getEventPayload());
GradStudentRecord studentRecord = gradStudentService.getGraduationStudentRecord(studentId);
response = getResponse(studentRecord);
log.info(RESPONDING_BACK_TO_NATS_ON_CHANNEL, message.getReplyTo() != null ? message.getReplyTo() : event.getReplyTo());
} catch (Exception e) {
response = getErrorResponse(e);
log.error("Error while processing GET_STUDENT event", e);
}
this.natsConnection.publish(message.getReplyTo(), response.getBytes());
}

private String getResponse(GradStudentRecord studentRecord) throws JsonProcessingException {
GradStudentRecordPayload gradStudentRecordPayload = GradStudentRecordPayload.builder()
.studentID(String.valueOf(studentRecord.getStudentID()))
.program(studentRecord.getProgram())
.programCompletionDate(studentRecord.getProgramCompletionDate() != null ? EducGradStudentApiUtils.formatDate(studentRecord.getProgramCompletionDate()) : null)
.schoolOfRecord(studentRecord.getSchoolOfRecord())
.studentStatusCode(studentRecord.getStudentStatusCode())
.graduated(studentRecord.getGraduated().toString())
.build();
return JsonUtil.getJsonStringFromObject(gradStudentRecordPayload);
}

private String getErrorResponse(Exception e) {
String ex = (e instanceof EntityNotFoundException) ? "not found" : "error";
GradStudentRecordPayload gradStudentRecordPayload = GradStudentRecordPayload.builder()
.exception(ex)
.build();
try {
return JsonUtil.getJsonStringFromObject(gradStudentRecordPayload);
} catch (JsonProcessingException exc) {
log.error("Error while serializing error response", exc);
return "{\"studentID\": \"\", \"program\": \"\", \"programCompletionDate\": \"\", \"schoolOfRecord\": \"\", \"studentStatusCode\": \"\", \"graduated\": \"\", \"exception\": \"JSON Parsing exception\"}";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ca.bc.gov.educ.api.gradstudent.model.dc;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class GradStudentRecordPayload {

private String studentID;
private String exception;
private String program;
private String programCompletionDate;
private String schoolOfRecord;
private String studentStatusCode;
private String graduated;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ca.bc.gov.educ.api.gradstudent.model.dto.messaging;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.Date;
import java.util.UUID;

@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class GradStudentRecord {

private UUID studentID;
private String program;
private Date programCompletionDate;
private String schoolOfRecord;
private String studentStatusCode;
private String studentProjectedGradData;
private Boolean graduated;

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package ca.bc.gov.educ.api.gradstudent.service;

import ca.bc.gov.educ.api.gradstudent.exception.EntityNotFoundException;
import ca.bc.gov.educ.api.gradstudent.model.dto.*;
import ca.bc.gov.educ.api.gradstudent.model.dto.messaging.GradStudentRecord;
import ca.bc.gov.educ.api.gradstudent.model.entity.GraduationStudentRecordEntity;
import ca.bc.gov.educ.api.gradstudent.model.entity.GraduationStudentRecordView;
import ca.bc.gov.educ.api.gradstudent.model.transformer.GraduationStatusTransformer;
import ca.bc.gov.educ.api.gradstudent.repository.GraduationStudentRecordRepository;
import ca.bc.gov.educ.api.gradstudent.util.EducGradStudentApiConstants;
import ca.bc.gov.educ.api.gradstudent.util.ThreadLocalStateUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.resilience4j.retry.annotation.Retry;
import jakarta.transaction.Transactional;
Expand Down Expand Up @@ -50,6 +54,7 @@ public class GradStudentService {
private static final String PAGE_NUMBER="pageNumber";
private static final String PAGE_SIZE="pageSize";
private static final String SEARCH_CRITERIA_LIST = "searchCriteriaList";
private static final String STD_NOT_FOUND_MSG = "Student with ID: %s not found";

final EducGradStudentApiConstants constants;
final WebClient webClient;
Expand Down Expand Up @@ -419,4 +424,32 @@ public List<UUID> getStudentIDsBySearchCriteriaOrAll(StudentSearchRequest search
}
return result;
}

/**
* Returns a condensed version of GraduationStudentRecord for GDC
* @param studentID
* @return
* @throws EntityNotFoundException
*/
public GradStudentRecord getGraduationStudentRecord(UUID studentID) {
GradStudentRecord response = graduationStatusRepository.findByStudentID(studentID, GradStudentRecord.class);
if (response != null) {
response.setGraduated(parseGraduationStatus(response.getStudentProjectedGradData()));
return response;
}
throw new EntityNotFoundException(String.format(STD_NOT_FOUND_MSG, studentID));
}

public Boolean parseGraduationStatus(String studentProjectedGradData) {
if (studentProjectedGradData == null || studentProjectedGradData.isEmpty()) {
return false;
}
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(studentProjectedGradData);
return jsonNode.get("graduated").asBoolean();
} catch (JsonProcessingException e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ca.bc.gov.educ.api.gradstudent.service;

import ca.bc.gov.educ.api.gradstudent.controller.BaseIntegrationTest;
import ca.bc.gov.educ.api.gradstudent.messaging.NatsConnection;
import ca.bc.gov.educ.api.gradstudent.messaging.jetstream.FetchGradStatusSubscriber;
import ca.bc.gov.educ.api.gradstudent.messaging.jetstream.Publisher;
Expand Down Expand Up @@ -34,8 +35,7 @@

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class CommonServiceTest {
public class CommonServiceTest extends BaseIntegrationTest {

@Autowired EducGradStudentApiConstants constants;
@Autowired CommonService commonService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ca.bc.gov.educ.api.gradstudent.constant.FieldName;
import ca.bc.gov.educ.api.gradstudent.constant.FieldType;
import ca.bc.gov.educ.api.gradstudent.constant.TraxEventType;
import ca.bc.gov.educ.api.gradstudent.controller.BaseIntegrationTest;
import ca.bc.gov.educ.api.gradstudent.messaging.NatsConnection;
import ca.bc.gov.educ.api.gradstudent.messaging.jetstream.FetchGradStatusSubscriber;
import ca.bc.gov.educ.api.gradstudent.messaging.jetstream.Publisher;
Expand Down Expand Up @@ -39,8 +40,7 @@

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class DataConversionServiceTest {
public class DataConversionServiceTest extends BaseIntegrationTest {
@Autowired
EducGradStudentApiConstants constants;
@Autowired
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ca.bc.gov.educ.api.gradstudent.service;

import ca.bc.gov.educ.api.gradstudent.controller.BaseIntegrationTest;
import ca.bc.gov.educ.api.gradstudent.messaging.NatsConnection;
import ca.bc.gov.educ.api.gradstudent.messaging.jetstream.FetchGradStatusSubscriber;
import ca.bc.gov.educ.api.gradstudent.messaging.jetstream.Publisher;
Expand Down Expand Up @@ -31,8 +32,7 @@

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class EdwSnapshotServiceTest {
public class EdwSnapshotServiceTest extends BaseIntegrationTest {

@Autowired
EdwSnapshotService edwSnapshotService;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package ca.bc.gov.educ.api.gradstudent.service;

import ca.bc.gov.educ.api.gradstudent.controller.BaseIntegrationTest;
import ca.bc.gov.educ.api.gradstudent.exception.EntityNotFoundException;
import ca.bc.gov.educ.api.gradstudent.messaging.NatsConnection;
import ca.bc.gov.educ.api.gradstudent.messaging.jetstream.FetchGradStatusSubscriber;
import ca.bc.gov.educ.api.gradstudent.messaging.jetstream.Publisher;
import ca.bc.gov.educ.api.gradstudent.messaging.jetstream.Subscriber;
import ca.bc.gov.educ.api.gradstudent.model.dto.*;
import ca.bc.gov.educ.api.gradstudent.model.dto.messaging.GradStudentRecord;
import ca.bc.gov.educ.api.gradstudent.model.entity.GraduationStudentRecordEntity;
import ca.bc.gov.educ.api.gradstudent.model.entity.GraduationStudentRecordView;
import ca.bc.gov.educ.api.gradstudent.model.transformer.GraduationStatusTransformer;
Expand Down Expand Up @@ -41,15 +44,15 @@
import java.util.function.Function;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.openMocks;

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class GradStudentServiceTest {
public class GradStudentServiceTest extends BaseIntegrationTest {

@Autowired
EducGradStudentApiConstants constants;
Expand Down Expand Up @@ -847,6 +850,114 @@ public void testGetStudentIDsBySearchCriterias() {
assertThat(results).isNotEmpty();
}

@Test
public void testGetGraduationStudentRecord_GivenValidProgramCompletionDate_ExpectTrue() throws EntityNotFoundException {
UUID studentID = UUID.randomUUID();
GraduationStudentRecordEntity graduationStudentRecordEntity = new GraduationStudentRecordEntity();
graduationStudentRecordEntity.setProgramCompletionDate(new java.util.Date());
when(graduationStatusRepository.findByStudentID(studentID, GradStudentRecord.class)).thenReturn(new GradStudentRecord(studentID, "2018-EN", new java.util.Date(), "schoolOfRecord", "studentStatusCode", "{\"nonGradReasons\":null,\"graduated\":true}", Boolean.TRUE));
GradStudentRecord result = gradStudentService.getGraduationStudentRecord(studentID);
assertNotNull(result);
}

@Test
public void testGetGraduationStudentRecord_givenNotFound_ExpectEntityNotFoundExcetpion() {
UUID studentID = UUID.randomUUID();
when(graduationStatusRepository.findByStudentID(studentID, GradStudentRecord.class)).thenReturn(null);
assertThrows(EntityNotFoundException.class, () -> {
gradStudentService.getGraduationStudentRecord(studentID);
});
}

@Test
public void testGetGraduationStudentRecord_GivenGraduatedTrue_ExpectGraduated() {
UUID studentID = UUID.randomUUID();
GradStudentRecord mockRecord = new GradStudentRecord(
studentID,
"2018-EN",
new java.util.Date(),
"schoolOfRecord",
"studentStatusCode",
"{\"nonGradReasons\":null,\"graduated\":true}",
true
);
when(graduationStatusRepository.findByStudentID(studentID, GradStudentRecord.class)).thenReturn(mockRecord);

GradStudentRecord result = gradStudentService.getGraduationStudentRecord(studentID);

assertNotNull(result);
assertTrue(result.getGraduated());
}

@Test
public void testGetGraduationStudentRecord_GivenGraduatedFalse_ExpectNotGraduated() {
UUID studentID = UUID.randomUUID();
GradStudentRecord mockRecord = new GradStudentRecord(
studentID,
"2018-EN",
new java.util.Date(),
"schoolOfRecord",
"studentStatusCode",
"{\"nonGradReasons\":[],\"graduated\":false}",
false
);
when(graduationStatusRepository.findByStudentID(studentID, GradStudentRecord.class)).thenReturn(mockRecord);

GradStudentRecord result = gradStudentService.getGraduationStudentRecord(studentID);

assertNotNull(result);
assertFalse(result.getGraduated());
}

@Test
public void testGetGraduationStudentRecord_GivenNullCLOBData_ExpectNotGraduated() {
UUID studentID = UUID.randomUUID();
GradStudentRecord mockRecord = new GradStudentRecord(
studentID,
"2018-EN",
new java.util.Date(),
"schoolOfRecord",
"studentStatusCode",
null,
false
);
when(graduationStatusRepository.findByStudentID(studentID, GradStudentRecord.class)).thenReturn(mockRecord);

GradStudentRecord result = gradStudentService.getGraduationStudentRecord(studentID);

assertNotNull(result);
assertFalse(result.getGraduated());
}

@Test
public void testGetGraduationStudentRecord_GivenRecordNotFound_ExpectEntityNotFoundException() {
UUID studentID = UUID.randomUUID();
when(graduationStatusRepository.findByStudentID(studentID, GradStudentRecord.class)).thenReturn(null);

assertThrows(EntityNotFoundException.class, () -> gradStudentService.getGraduationStudentRecord(studentID));
}

@Test
public void testParseGraduationStatus_GivenNullInput_ExpectFalse() {
String studentProjectedGradData = null;
Boolean result = gradStudentService.parseGraduationStatus(studentProjectedGradData);
assertFalse("Expected false for null input", result);
}

@Test
public void testParseGraduationStatus_GivenEmptyInput_ExpectFalse() {
String studentProjectedGradData = "";
Boolean result = gradStudentService.parseGraduationStatus(studentProjectedGradData);
assertFalse("Expected false for empty input", result);
}

@Test
public void testParseGraduationStatus_GivenMalformedJson_ExpectFalse() {
String malformedJson = "{invalid-json}";
Boolean result = gradStudentService.parseGraduationStatus(malformedJson);
assertFalse("Expected false for malformed JSON", result);
}

@SneakyThrows
protected Object createDataObjectFromJson(String jsonPath, Class<?> clazz) {
String json = readFile(jsonPath);
Expand Down
Loading

0 comments on commit 9fb18d0

Please sign in to comment.