From cfa0b16d93044d44e3fe37d0e4e8982f215d2674 Mon Sep 17 00:00:00 2001 From: Chris Ditcher Date: Thu, 8 Aug 2024 11:49:30 -0700 Subject: [PATCH 01/16] GRAD2-2648 (#341) * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * GRAD2-2637 - Cache institute api data into Redis * Commit to save * Ignoring application-local.yaml * Changed Event to EventEntity for clarity * Added EventHistoryEntity * Added EventHistoryRepository * Implemented add to history * Fixed unit test * Added more coverage. * Fixing maintainability * Added school updated and supporting unit tests. * Added school updated and supporting unit tests. * Added school created and supporting unit tests. * Added school moved and supporting unit tests. * Added update district and supporting tests * Added missing property * Correctly transforming to entity here. * Updated unit testing * Finalizing unit testing and move school. * Forgot to add these files. * Fixing Test Coverage. * Fixing duplication of code. --------- Co-authored-by: Kamal Mohammed Co-authored-by: chris.ditcher --- .gitignore | 3 + .../ChoreographEventHandler.java | 103 ++++++----- .../educ/api/trax/constant/EventOutcome.java | 2 +- .../educ/api/trax/constant/EventStatus.java | 2 +- .../gov/educ/api/trax/constant/EventType.java | 2 +- .../trax/controller/v2/SchoolController.java | 1 + .../api/trax/exception/BusinessError.java | 2 +- .../trax/messaging/jetstream/Publisher.java | 2 +- .../trax/model/dto/ChoreographedEvent.java | 6 +- .../trax/model/dto/institute/District.java | 5 + .../model/dto/institute/MoveSchoolData.java | 33 ++++ .../api/trax/model/dto/institute/School.java | 2 + .../model/dto/institute/SchoolDetail.java | 7 - .../entity/{Event.java => EventEntity.java} | 20 +-- .../trax/model/entity/EventHistoryEntity.java | 38 ++++ .../model/entity/TraxUpdatedPubEvent.java | 4 +- .../entity/institute/DistrictEntity.java | 1 + .../entity/institute/SchoolContactEntity.java | 2 +- .../api/trax/model/entity/v2/BaseEntity.java | 49 ++++++ .../repository/EventHistoryRepository.java | 12 ++ .../api/trax/repository/EventRepository.java | 10 +- .../scheduler/JetStreamEventScheduler.java | 6 +- .../AuthorityContactCreatedService.java | 8 +- .../AuthorityContactDeletedService.java | 8 +- .../AuthorityContactUpdatedService.java | 8 +- .../ChoreographedEventPersistenceService.java | 8 +- .../DistrictContactCreatedService.java | 8 +- .../DistrictContactDeletedService.java | 8 +- .../DistrictContactUpdatedService.java | 25 ++- .../trax/service/DistrictUpdatedService.java | 39 +++++ .../api/trax/service/EventBaseService.java | 27 ++- .../api/trax/service/EventCommonService.java | 8 +- .../service/EventHandlerDelegatorService.java | 2 +- .../educ/api/trax/service/EventService.java | 4 +- .../educ/api/trax/service/RESTService.java | 23 ++- .../service/SchoolContactCreatedService.java | 8 +- .../service/SchoolContactDeletedService.java | 8 +- .../service/SchoolContactUpdatedService.java | 23 ++- .../trax/service/SchoolCreatedService.java | 39 +++++ .../api/trax/service/SchoolMovedService.java | 41 +++++ .../trax/service/SchoolUpdatedService.java | 23 +++ .../service/institute/DistrictService.java | 13 ++ .../trax/service/institute/SchoolService.java | 24 +++ .../trax/util/EducGradTraxApiConstants.java | 2 + api/src/main/resources/application.yaml | 2 + ...reographedEventPersistenceServiceTest.java | 34 ++-- .../DistrictContactUpdatedServiceTest.java | 24 +++ .../service/DistrictUpdatedServiceTest.java | 52 ++++++ .../SchoolContactUpdatedServiceTest.java | 23 +++ .../service/SchoolCreatedServiceTest.java | 52 ++++++ .../trax/service/SchoolMovedServiceTest.java | 52 ++++++ .../service/SchoolUpdatedServiceTest.java | 52 ++++++ .../InstituteDistrictServiceTest.java | 42 ++++- .../intstituteSchoolServiceTestToo.java | 163 ++++++++++++++++++ .../gov/educ/api/trax/support/TestUtils.java | 78 ++++++++- .../api/trax/util/ReplicationTestUtils.java | 6 +- api/src/test/resources/application.yaml | 2 + 57 files changed, 1087 insertions(+), 164 deletions(-) create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/MoveSchoolData.java rename api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/{Event.java => EventEntity.java} (88%) create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictUpdatedService.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedService.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedService.java create mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictUpdatedServiceTest.java create mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedServiceTest.java create mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolMovedServiceTest.java create mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedServiceTest.java create mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/intstituteSchoolServiceTestToo.java diff --git a/.gitignore b/.gitignore index 1263624c..0d3181fb 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ build/ ### VS Code ### .vscode/ + +### local dev ### +application-local.yaml diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/choreographer/ChoreographEventHandler.java b/api/src/main/java/ca/bc/gov/educ/api/trax/choreographer/ChoreographEventHandler.java index 38a1bd3f..90e07eb8 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/choreographer/ChoreographEventHandler.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/choreographer/ChoreographEventHandler.java @@ -6,7 +6,10 @@ import ca.bc.gov.educ.api.trax.model.dto.DistrictContact; import ca.bc.gov.educ.api.trax.model.dto.GradStatusEventPayloadDTO; import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.dto.institute.District; +import ca.bc.gov.educ.api.trax.model.dto.institute.MoveSchoolData; +import ca.bc.gov.educ.api.trax.model.dto.institute.School; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import ca.bc.gov.educ.api.trax.repository.EventRepository; import ca.bc.gov.educ.api.trax.service.EventService; import ca.bc.gov.educ.api.trax.util.JsonUtil; @@ -48,74 +51,90 @@ public ChoreographEventHandler(final List eventServices, final Eve eventServices.forEach(eventService -> this.eventServiceMap.put(eventService.getEventType(), eventService)); } - public void handleEvent(@NonNull final Event event) { + public void handleEvent(@NonNull final EventEntity eventEntity) { //only one thread will process all the request. since RDB won't handle concurrent requests. this.eventExecutor.execute(() -> { try { - switch (EventType.valueOf(event.getEventType())) { + switch (EventType.valueOf(eventEntity.getEventType())) { case GRAD_STUDENT_GRADUATED -> { - log.debug("Processing GRAD_STUDENT_GRADUATED event record :: {} ", event); - val studentGraduated = JsonUtil.getJsonObjectFromString(GradStatusEventPayloadDTO.class, event.getEventPayload()); - this.eventServiceMap.get(GRAD_STUDENT_GRADUATED.toString()).processEvent(studentGraduated, event); + log.debug("Processing GRAD_STUDENT_GRADUATED eventEntity record :: {} ", eventEntity); + val studentGraduated = JsonUtil.getJsonObjectFromString(GradStatusEventPayloadDTO.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(GRAD_STUDENT_GRADUATED.toString()).processEvent(studentGraduated, eventEntity); } case GRAD_STUDENT_UPDATED -> { - log.debug("Processing GRAD_STUDENT_UPDATED event record :: {} ", event); - val studentUpdated = JsonUtil.getJsonObjectFromString(GradStatusEventPayloadDTO.class, event.getEventPayload()); - this.eventServiceMap.get(GRAD_STUDENT_UPDATED.toString()).processEvent(studentUpdated, event); + log.debug("Processing GRAD_STUDENT_UPDATED eventEntity record :: {} ", eventEntity); + val studentUpdated = JsonUtil.getJsonObjectFromString(GradStatusEventPayloadDTO.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(GRAD_STUDENT_UPDATED.toString()).processEvent(studentUpdated, eventEntity); } case GRAD_STUDENT_UNDO_COMPLETION -> { - log.debug("Processing GRAD_STUDENT_UNDO_COMPLETION event record :: {} ", event); - val studentUndoCompletion = JsonUtil.getJsonObjectFromString(GradStatusEventPayloadDTO.class, event.getEventPayload()); - this.eventServiceMap.get(GRAD_STUDENT_UNDO_COMPLETION.toString()).processEvent(studentUndoCompletion, event); + log.debug("Processing GRAD_STUDENT_UNDO_COMPLETION eventEntity record :: {} ", eventEntity); + val studentUndoCompletion = JsonUtil.getJsonObjectFromString(GradStatusEventPayloadDTO.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(GRAD_STUDENT_UNDO_COMPLETION.toString()).processEvent(studentUndoCompletion, eventEntity); } case CREATE_SCHOOL_CONTACT -> { - log.debug("Processing {} event record :: {} ", event.getEventType(), event); - val schoolContactCreated = JsonUtil.getJsonObjectFromString(SchoolContact.class, event.getEventPayload()); - this.eventServiceMap.get(CREATE_SCHOOL_CONTACT.toString()).processEvent(schoolContactCreated, event); + log.debug("Processing {} eventEntity record :: {} ", eventEntity.getEventType(), eventEntity); + val schoolContactCreated = JsonUtil.getJsonObjectFromString(SchoolContact.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(CREATE_SCHOOL_CONTACT.toString()).processEvent(schoolContactCreated, eventEntity); } case UPDATE_SCHOOL_CONTACT -> { - log.debug("Processing {} event record :: {} ", event.getEventType(), event); - val schoolContactUpdated = JsonUtil.getJsonObjectFromString(SchoolContact.class, event.getEventPayload()); - this.eventServiceMap.get(UPDATE_SCHOOL_CONTACT.toString()).processEvent(schoolContactUpdated, event); + log.debug("Processing {} eventEntity record :: {} ", eventEntity.getEventType(), eventEntity); + val schoolContactUpdated = JsonUtil.getJsonObjectFromString(SchoolContact.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(UPDATE_SCHOOL_CONTACT.toString()).processEvent(schoolContactUpdated, eventEntity); } case DELETE_SCHOOL_CONTACT -> { - log.debug("Processing {} event record :: {} ", event.getEventType(), event); - val schoolContactDeleted = JsonUtil.getJsonObjectFromString(SchoolContact.class, event.getEventPayload()); - this.eventServiceMap.get(DELETE_SCHOOL_CONTACT.toString()).processEvent(schoolContactDeleted, event); + log.debug("Processing {} eventEntity record :: {} ", eventEntity.getEventType(), eventEntity); + val schoolContactDeleted = JsonUtil.getJsonObjectFromString(SchoolContact.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(DELETE_SCHOOL_CONTACT.toString()).processEvent(schoolContactDeleted, eventEntity); } case CREATE_AUTHORITY_CONTACT -> { - log.debug("Processing {} event record :: {} ", event.getEventType(), event); - val authorityContactCreated = JsonUtil.getJsonObjectFromString(AuthorityContact.class, event.getEventPayload()); - this.eventServiceMap.get(CREATE_AUTHORITY_CONTACT.toString()).processEvent(authorityContactCreated, event); + log.debug("Processing {} eventEntity record :: {} ", eventEntity.getEventType(), eventEntity); + val authorityContactCreated = JsonUtil.getJsonObjectFromString(AuthorityContact.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(CREATE_AUTHORITY_CONTACT.toString()).processEvent(authorityContactCreated, eventEntity); } case UPDATE_AUTHORITY_CONTACT -> { - log.debug("Processing {} event record :: {} ", event.getEventType(), event); - val authorityContactUpdated = JsonUtil.getJsonObjectFromString(AuthorityContact.class, event.getEventPayload()); - this.eventServiceMap.get(UPDATE_AUTHORITY_CONTACT.toString()).processEvent(authorityContactUpdated, event); + log.debug("Processing {} eventEntity record :: {} ", eventEntity.getEventType(), eventEntity); + val authorityContactUpdated = JsonUtil.getJsonObjectFromString(AuthorityContact.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(UPDATE_AUTHORITY_CONTACT.toString()).processEvent(authorityContactUpdated, eventEntity); } case DELETE_AUTHORITY_CONTACT -> { - log.debug("Processing {} event record :: {} ", event.getEventType(), event); - val authorityContactDeleted = JsonUtil.getJsonObjectFromString(AuthorityContact.class, event.getEventPayload()); - this.eventServiceMap.get(DELETE_AUTHORITY_CONTACT.toString()).processEvent(authorityContactDeleted, event); + log.debug("Processing {} eventEntity record :: {} ", eventEntity.getEventType(), eventEntity); + val authorityContactDeleted = JsonUtil.getJsonObjectFromString(AuthorityContact.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(DELETE_AUTHORITY_CONTACT.toString()).processEvent(authorityContactDeleted, eventEntity); } case CREATE_DISTRICT_CONTACT -> { - log.debug("Processing {} event record :: {} ", event.getEventType(), event); - val districtContactCreated = JsonUtil.getJsonObjectFromString(DistrictContact.class, event.getEventPayload()); - this.eventServiceMap.get(CREATE_DISTRICT_CONTACT.toString()).processEvent(districtContactCreated, event); + log.debug("Processing {} eventEntity record :: {} ", eventEntity.getEventType(), eventEntity); + val districtContactCreated = JsonUtil.getJsonObjectFromString(DistrictContact.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(CREATE_DISTRICT_CONTACT.toString()).processEvent(districtContactCreated, eventEntity); } case UPDATE_DISTRICT_CONTACT -> { - log.debug("Processing {} event record :: {} ", event.getEventType(), event); - val districtContactUpdated = JsonUtil.getJsonObjectFromString(DistrictContact.class, event.getEventPayload()); - this.eventServiceMap.get(UPDATE_DISTRICT_CONTACT.toString()).processEvent(districtContactUpdated, event); + log.debug("Processing {} eventEntity record :: {} ", eventEntity.getEventType(), eventEntity); + val districtContactUpdated = JsonUtil.getJsonObjectFromString(DistrictContact.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(UPDATE_DISTRICT_CONTACT.toString()).processEvent(districtContactUpdated, eventEntity); } case DELETE_DISTRICT_CONTACT -> { - log.debug("Processing {} event record :: {} ", event.getEventType(), event); - val districtContactDeleted = JsonUtil.getJsonObjectFromString(DistrictContact.class, event.getEventPayload()); - this.eventServiceMap.get(DELETE_DISTRICT_CONTACT.toString()).processEvent(districtContactDeleted, event); + log.debug("Processing {} eventEntity record :: {} ", eventEntity.getEventType(), eventEntity); + val districtContactDeleted = JsonUtil.getJsonObjectFromString(DistrictContact.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(DELETE_DISTRICT_CONTACT.toString()).processEvent(districtContactDeleted, eventEntity); + } + case UPDATE_SCHOOL -> { + val schoolUpdated = JsonUtil.getJsonObjectFromString(School.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(UPDATE_SCHOOL.toString()).processEvent(schoolUpdated, eventEntity); + } + case CREATE_SCHOOL -> { + val schoolCreated = JsonUtil.getJsonObjectFromString(School.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(CREATE_SCHOOL.toString()).processEvent(schoolCreated, eventEntity); + } + case MOVE_SCHOOL -> { + val schoolMoved = JsonUtil.getJsonObjectFromString(MoveSchoolData.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(MOVE_SCHOOL.toString()).processEvent(schoolMoved, eventEntity); + } + case UPDATE_DISTRICT -> { + val districtUpdated = JsonUtil.getJsonObjectFromString(District.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(UPDATE_DISTRICT.toString()).processEvent(districtUpdated, eventEntity); } default -> { - log.warn("Silently ignoring event: {}", event); - this.eventRepository.findByEventId(event.getEventId()).ifPresent(existingEvent -> { + log.warn("Silently ignoring eventEntity: {}", eventEntity); + this.eventRepository.findByEventId(eventEntity.getEventId()).ifPresent(existingEvent -> { existingEvent.setEventStatus(EventStatus.PROCESSED.toString()); existingEvent.setUpdateDate(LocalDateTime.now()); this.eventRepository.save(existingEvent); @@ -124,7 +143,7 @@ public void handleEvent(@NonNull final Event event) { } } } catch (final Exception exception) { - log.error("Exception while processing event :: {}", event, exception); + log.error("Exception while processing eventEntity :: {}", eventEntity, exception); } }); diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventOutcome.java b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventOutcome.java index 9a3c2a3c..e9a079b4 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventOutcome.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventOutcome.java @@ -1,7 +1,7 @@ package ca.bc.gov.educ.api.trax.constant; /** - * The enum Event outcome. + * The enum EventEntity outcome. */ public enum EventOutcome { /** diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventStatus.java b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventStatus.java index 850a7a60..9d03f7f2 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventStatus.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventStatus.java @@ -1,7 +1,7 @@ package ca.bc.gov.educ.api.trax.constant; /** - * The enum Event status. + * The enum EventEntity status. */ public enum EventStatus { /** diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventType.java b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventType.java index 405b3554..0a4cda2e 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventType.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventType.java @@ -1,7 +1,7 @@ package ca.bc.gov.educ.api.trax.constant; /** - * The enum Event type. + * The enum EventEntity type. */ public enum EventType { /* =========================================================== diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java index 160f155a..c5c501f7 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java @@ -56,4 +56,5 @@ public List getAllSchoolDetails() { return schoolService.getSchoolDetailsFromRedisCache(); } + } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/BusinessError.java b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/BusinessError.java index 80bf54c1..8be24366 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/BusinessError.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/BusinessError.java @@ -3,7 +3,7 @@ import lombok.Getter; public enum BusinessError { - EVENT_ALREADY_PERSISTED("Event with event id :: $? , is already persisted in DB, a duplicate message from Jet Stream."); + EVENT_ALREADY_PERSISTED("EventEntity with event id :: $? , is already persisted in DB, a duplicate message from Jet Stream."); @Getter private final String code; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/messaging/jetstream/Publisher.java b/api/src/main/java/ca/bc/gov/educ/api/trax/messaging/jetstream/Publisher.java index f7ae45d4..8c3e6e37 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/messaging/jetstream/Publisher.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/messaging/jetstream/Publisher.java @@ -80,7 +80,7 @@ public void dispatchChoreographyEvent(final TraxUpdatedPubEvent traxUpdatedPubEv try { log.debug("Broadcasting replicationEvent :: {}", choreographedEvent); val pub = this.jetStream.publishAsync(TRAX_UPDATE_EVENT_TOPIC.name(), JsonUtil.getJsonBytesFromObject(choreographedEvent)); - pub.thenAcceptAsync(result -> log.debug("Event ID :: {} Published to JetStream :: {}", traxUpdatedPubEvent.getEventId(), result.getSeqno())); + pub.thenAcceptAsync(result -> log.debug("EventEntity ID :: {} Published to JetStream :: {}", traxUpdatedPubEvent.getEventId(), result.getSeqno())); } catch (IOException e) { log.error("exception while broadcasting message to JetStream", e); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ChoreographedEvent.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ChoreographedEvent.java index eb28e448..f9077546 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ChoreographedEvent.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ChoreographedEvent.java @@ -18,11 +18,11 @@ public class ChoreographedEvent { UUID eventID; /** - * The Event type. + * The EventEntity type. */ EventType eventType; /** - * The Event outcome. + * The EventEntity outcome. */ EventOutcome eventOutcome; /** @@ -30,7 +30,7 @@ public class ChoreographedEvent { */ String activityCode; /** - * The Event payload. + * The EventEntity payload. */ String eventPayload; // json string /** diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/District.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/District.java index d7700fa2..a7840b8f 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/District.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/District.java @@ -1,8 +1,10 @@ package ca.bc.gov.educ.api.trax.model.dto.institute; import ca.bc.gov.educ.api.trax.model.dto.BaseModel; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import org.springframework.stereotype.Component; import java.util.List; @@ -10,6 +12,8 @@ @Data @EqualsAndHashCode(callSuper = true) @Component("InstituteDistrict") +@NoArgsConstructor +@AllArgsConstructor public class District extends BaseModel { private String districtId; @@ -18,6 +22,7 @@ public class District extends BaseModel { private String phoneNumber; private String email; private String website; + private String displayName; private String districtRegionCode; private String districtStatusCode; private List contacts; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/MoveSchoolData.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/MoveSchoolData.java new file mode 100644 index 00000000..ff837ea7 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/MoveSchoolData.java @@ -0,0 +1,33 @@ +package ca.bc.gov.educ.api.trax.model.dto.institute; + +import ca.bc.gov.educ.api.trax.model.dto.BaseModel; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +import java.io.Serializable; + +@EqualsAndHashCode(callSuper = true) +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class MoveSchoolData extends BaseModel implements Serializable { + + /** + * The constant serialVersionUID. + */ + private static final long serialVersionUID = 1L; + + @NotNull(message = "toSchool cannot be null.") + @Valid + private School toSchool; + + @NotNull(message = "fromSchoolId cannot be null.") + private String fromSchoolId; + + @NotNull(message = "moveDate cannot be null.") + private String moveDate; +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/School.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/School.java index 55adc244..245d0eef 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/School.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/School.java @@ -1,6 +1,7 @@ package ca.bc.gov.educ.api.trax.model.dto.institute; import ca.bc.gov.educ.api.trax.model.dto.BaseModel; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; import lombok.EqualsAndHashCode; import org.springframework.stereotype.Component; @@ -8,6 +9,7 @@ @Data @EqualsAndHashCode(callSuper = true) @Component("InstituteSchool") +@JsonIgnoreProperties(ignoreUnknown = true) public class School extends BaseModel { private String schoolId; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/SchoolDetail.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/SchoolDetail.java index 4212fc69..9047504e 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/SchoolDetail.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/SchoolDetail.java @@ -2,11 +2,8 @@ import ca.bc.gov.educ.api.trax.model.dto.BaseModel; import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; -import ca.bc.gov.educ.api.trax.model.entity.institute.*; import lombok.Data; import lombok.EqualsAndHashCode; -import org.springframework.data.annotation.Id; -import org.springframework.data.redis.core.index.Indexed; import org.springframework.stereotype.Component; import java.util.List; @@ -35,10 +32,6 @@ public class SchoolDetail extends BaseModel { private String closedDate; private boolean canIssueTranscripts; private boolean canIssueCertificates; - private String createUser; - private String updateUser; - private String createDate; - private String updateDate; List contacts; List addresses; List notes; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/Event.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java similarity index 88% rename from api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/Event.java rename to api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java index b6a26ab1..b3fb0f53 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/Event.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java @@ -21,7 +21,7 @@ @Table(name = "REPLICATION_EVENT") @Data @DynamicUpdate -public class Event { +public class EventEntity { /** * The Create user. */ @@ -51,12 +51,12 @@ public class Event { @Column(name = "REPLICATION_EVENT_ID", unique = true, updatable = false, columnDefinition = "BINARY(16)") private UUID replicationEventId; /** - * The Event id. + * The EventEntity id. */ @Column(name = "EVENT_ID", unique = true, updatable = false, columnDefinition = "BINARY(16)") private UUID eventId; /** - * The Event payload. + * The EventEntity payload. */ @NotNull(message = "eventPayload cannot be null") @Lob @@ -64,19 +64,19 @@ public class Event { @ToString.Exclude private byte[] eventPayloadBytes; /** - * The Event status. + * The EventEntity status. */ @NotNull(message = "eventStatus cannot be null") @Column(name = "EVENT_STATUS") private String eventStatus; /** - * The Event type. + * The EventEntity type. */ @NotNull(message = "eventType cannot be null") @Column(name = "EVENT_TYPE") private String eventType; /** - * The Event outcome. + * The EventEntity outcome. */ @NotNull(message = "eventOutcome cannot be null.") @Column(name = "EVENT_OUTCOME") @@ -109,19 +109,19 @@ public void setEventPayload(final String eventPayload) { /** * The type Student event builder. */ - public static class EventBuilder { + public static class EventEntityBuilder { /** - * The Event payload bytes. + * The EventEntity payload bytes. */ byte[] eventPayloadBytes; /** - * Event payload student event . student event builder. + * EventEntity payload student event . student event builder. * * @param eventPayload the event payload * @return the student event . student event builder */ - public EventBuilder eventPayload(final String eventPayload) { + public EventEntityBuilder eventPayload(final String eventPayload) { this.eventPayloadBytes = eventPayload.getBytes(StandardCharsets.UTF_8); return this; } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java new file mode 100644 index 00000000..a02234c1 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java @@ -0,0 +1,38 @@ +package ca.bc.gov.educ.api.trax.model.entity; + +import ca.bc.gov.educ.api.trax.model.entity.v2.BaseEntity; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.GenericGenerator; + +import java.util.UUID; + +@Getter +@Setter +@Entity +@Table(name = "EVENT_HISTORY") +@DynamicUpdate +public class EventHistoryEntity extends BaseEntity { + + @Id + @GeneratedValue(generator = "UUID") + @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator", parameters = { + @org.hibernate.annotations.Parameter(name = "uuid_gen_strategy_class", value = "org.hibernate.id.uuid.CustomVersionOneStrategy")}) + @Column(name = "EVENT_HISTORY_ID", unique = true, updatable = false, columnDefinition = "BINARY(16)") + private UUID id; + + @NotNull + @OneToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "EVENT_ID", referencedColumnName = "REPLICATION_EVENT_ID", nullable = false) + private EventEntity event; + + @Size(max = 1) + @NotNull + @Column(name = "ACKNOWLEDGE_FLAG", nullable = false, length = 1) + private String acknowledgeFlag = "N"; + +} \ No newline at end of file diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/TraxUpdatedPubEvent.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/TraxUpdatedPubEvent.java index 10db141e..7da7bfac 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/TraxUpdatedPubEvent.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/TraxUpdatedPubEvent.java @@ -105,12 +105,12 @@ private static String nullSafeEventPayload(String eventPayload) { */ public static class TraxUpdatedPubEventBuilder { /** - * The Event payload bytes. + * The EventEntity payload bytes. */ byte[] eventPayloadBytes; /** - * Event payload grad status event . GradStatus event builder. + * EventEntity payload grad status event . GradStatus event builder. * * @param eventPayload the event payload * @return the student event . student event builder diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/DistrictEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/DistrictEntity.java index 482e9ce7..668c4236 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/DistrictEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/DistrictEntity.java @@ -23,6 +23,7 @@ public class DistrictEntity { private String phoneNumber; private String email; private String website; + private String displayName; private String districtRegionCode; private String districtStatusCode; private List contacts; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolContactEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolContactEntity.java index 48fd2720..3c2d0c8b 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolContactEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolContactEntity.java @@ -14,7 +14,7 @@ public class SchoolContactEntity { @Id - private String schooldistrictContactId; + private String schoolContactId; @Indexed private String schoolId; @Indexed diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java new file mode 100644 index 00000000..0e3400ae --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java @@ -0,0 +1,49 @@ +package ca.bc.gov.educ.api.trax.model.entity.v2; + +import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; +import ca.bc.gov.educ.api.trax.util.ThreadLocalStateUtil; +import jakarta.persistence.*; +import jakarta.validation.constraints.PastOrPresent; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import java.time.LocalDateTime; + +@Data +@MappedSuperclass +public class BaseEntity { + + @Column(name = "CREATE_USER", updatable = false) + String createUser; + + @Column(name = "CREATE_DATE", updatable = false) + @PastOrPresent + LocalDateTime createDate; + + @Column(name = "UPDATE_USER") + String updateUser; + + @Column(name = "UPDATE_DATE") + @PastOrPresent + LocalDateTime updateDate; + + @PrePersist + protected void onCreate() { + initUserInfo(); + this.createDate = LocalDateTime.now(); + this.updateDate = LocalDateTime.now(); + } + + @PreUpdate + protected void onPersist() { + initUserInfo(); + this.createDate = (this.createDate == null) ? LocalDateTime.now() : this.createDate; + this.updateDate = LocalDateTime.now(); + } + + private void initUserInfo() { + String user = ThreadLocalStateUtil.getCurrentUser(); + this.updateUser = (StringUtils.isBlank(user)) ? EducGradTraxApiConstants.DEFAULT_UPDATED_BY : user; + this.createUser = (StringUtils.isBlank(createUser) && StringUtils.isBlank(user)) ? EducGradTraxApiConstants.DEFAULT_CREATED_BY : user; + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java new file mode 100644 index 00000000..3dd616b2 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java @@ -0,0 +1,12 @@ +package ca.bc.gov.educ.api.trax.repository; + +import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.UUID; + +@Repository +public interface EventHistoryRepository extends JpaRepository { + + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventRepository.java b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventRepository.java index 9c3a7c33..9508d861 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventRepository.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventRepository.java @@ -1,6 +1,6 @@ package ca.bc.gov.educ.api.trax.repository; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -13,15 +13,15 @@ import java.util.UUID; @Repository -public interface EventRepository extends JpaRepository { +public interface EventRepository extends JpaRepository { - Optional findByEventId(UUID eventId); + Optional findByEventId(UUID eventId); - List findAllByEventStatusOrderByCreateDate(String eventStatus); + List findAllByEventStatusOrderByCreateDate(String eventStatus); @Transactional @Modifying - @Query("delete from Event where createDate <= :createDate") + @Query("delete from EventEntity where createDate <= :createDate") void deleteByCreateDateBefore(LocalDateTime createDate); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/scheduler/JetStreamEventScheduler.java b/api/src/main/java/ca/bc/gov/educ/api/trax/scheduler/JetStreamEventScheduler.java index 89d7f90e..18454088 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/scheduler/JetStreamEventScheduler.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/scheduler/JetStreamEventScheduler.java @@ -2,7 +2,7 @@ import ca.bc.gov.educ.api.trax.choreographer.ChoreographEventHandler; import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import ca.bc.gov.educ.api.trax.model.entity.TraxUpdatedPubEvent; import ca.bc.gov.educ.api.trax.repository.TraxUpdatedPubEventRepository; import ca.bc.gov.educ.api.trax.repository.EventRepository; @@ -22,7 +22,7 @@ public class JetStreamEventScheduler { /** - * The Event repository. + * The EventEntity repository. */ private final EventRepository eventRepository; /** @@ -62,7 +62,7 @@ public void findAndProcessEvents() { if (!results.isEmpty()) { var filteredList = results.stream().filter(el -> el.getUpdateDate().isBefore(LocalDateTime.now().minusMinutes(5))).toList(); int cnt = 0; - for (Event e : filteredList) { + for (EventEntity e : filteredList) { if (cnt++ >= constants.getGradToTraxProcessingThreshold()) { log.info(" ==> Reached the processing threshold of {}", constants.getGradToTraxProcessingThreshold()); break; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactCreatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactCreatedService.java index 6786d604..cc2b68b8 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactCreatedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactCreatedService.java @@ -2,7 +2,7 @@ import ca.bc.gov.educ.api.trax.constant.EventType; import ca.bc.gov.educ.api.trax.model.dto.AuthorityContact; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service @@ -10,10 +10,10 @@ public class AuthorityContactCreatedService extends EventBaseService { @Override - public void processEvent(final AuthorityContact districtContact, Event event) { + public void processEvent(final AuthorityContact districtContact, EventEntity eventEntity) { log.debug("Processing Authority Contact Created"); - // process the event here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(event); + // process the eventEntity here as per https://eccbc.atlassian.net/browse/GRAD2-2648 + this.updateEvent(eventEntity); } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactDeletedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactDeletedService.java index 80e45c63..7c6eba40 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactDeletedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactDeletedService.java @@ -2,7 +2,7 @@ import ca.bc.gov.educ.api.trax.constant.EventType; import ca.bc.gov.educ.api.trax.model.dto.AuthorityContact; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -11,10 +11,10 @@ public class AuthorityContactDeletedService extends EventBaseService { @Override - public void processEvent(final AuthorityContact districtContact, Event event) { + public void processEvent(final AuthorityContact districtContact, EventEntity eventEntity) { log.debug("Processing Authority Contact Deleted"); - // process the event here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(event); + // process the eventEntity here as per https://eccbc.atlassian.net/browse/GRAD2-2648 + this.updateEvent(eventEntity); } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactUpdatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactUpdatedService.java index 5495e37b..f6c22034 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactUpdatedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactUpdatedService.java @@ -2,7 +2,7 @@ import ca.bc.gov.educ.api.trax.constant.EventType; import ca.bc.gov.educ.api.trax.model.dto.AuthorityContact; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service @@ -10,10 +10,10 @@ public class AuthorityContactUpdatedService extends EventBaseService { @Override - public void processEvent(final AuthorityContact districtContact, Event event) { + public void processEvent(final AuthorityContact districtContact, EventEntity eventEntity) { log.debug("Processing Authority Contact Updated"); - // process the event here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(event); + // process the eventEntity here as per https://eccbc.atlassian.net/browse/GRAD2-2648 + this.updateEvent(eventEntity); } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/ChoreographedEventPersistenceService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/ChoreographedEventPersistenceService.java index 533bd86d..c0bebee2 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/ChoreographedEventPersistenceService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/ChoreographedEventPersistenceService.java @@ -3,7 +3,7 @@ import ca.bc.gov.educ.api.trax.exception.BusinessError; import ca.bc.gov.educ.api.trax.exception.BusinessException; import ca.bc.gov.educ.api.trax.model.dto.ChoreographedEvent; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import ca.bc.gov.educ.api.trax.repository.EventRepository; import ca.bc.gov.educ.api.trax.repository.TraxUpdatedPubEventRepository; import lombok.extern.slf4j.Slf4j; @@ -36,12 +36,12 @@ public ChoreographedEventPersistenceService( } @Transactional(propagation = Propagation.REQUIRES_NEW) - public Event persistEventToDB(final ChoreographedEvent choreographedEvent) throws BusinessException { + public EventEntity persistEventToDB(final ChoreographedEvent choreographedEvent) throws BusinessException { var eventOptional = eventRepository.findByEventId(choreographedEvent.getEventID()); if (eventOptional.isPresent()) { throw new BusinessException(BusinessError.EVENT_ALREADY_PERSISTED, choreographedEvent.getEventID().toString()); } - final Event event = Event.builder() + final EventEntity eventEntity = EventEntity.builder() .eventType(choreographedEvent.getEventType().toString()) .eventId(choreographedEvent.getEventID()) .eventOutcome(choreographedEvent.getEventOutcome().toString()) @@ -53,7 +53,7 @@ public Event persistEventToDB(final ChoreographedEvent choreographedEvent) throw .createDate(LocalDateTime.now()) .updateDate(LocalDateTime.now()) .build(); - return this.eventRepository.save(event); + return this.eventRepository.save(eventEntity); } /** diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedService.java index ee41531f..02b7d9a8 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedService.java @@ -2,7 +2,7 @@ import ca.bc.gov.educ.api.trax.constant.EventType; import ca.bc.gov.educ.api.trax.model.dto.DistrictContact; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -11,10 +11,10 @@ public class DistrictContactCreatedService extends EventBaseService { @Override - public void processEvent(final DistrictContact districtContact, Event event) { + public void processEvent(final DistrictContact districtContact, EventEntity eventEntity) { log.debug("Processing District Contact Created"); - // process the event here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(event); + // process the eventEntity here as per https://eccbc.atlassian.net/browse/GRAD2-2648 + this.updateEvent(eventEntity); } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedService.java index 04dfb5b2..7befed56 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedService.java @@ -2,7 +2,7 @@ import ca.bc.gov.educ.api.trax.constant.EventType; import ca.bc.gov.educ.api.trax.model.dto.DistrictContact; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -11,10 +11,10 @@ public class DistrictContactDeletedService extends EventBaseService { @Override - public void processEvent(final DistrictContact districtContact, Event event) { + public void processEvent(final DistrictContact districtContact, EventEntity eventEntity) { log.debug("Processing District Contact Deleted"); - // process the event here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(event); + // process the eventEntity here as per https://eccbc.atlassian.net/browse/GRAD2-2648 + this.updateEvent(eventEntity); } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedService.java index bdb80990..31f99013 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedService.java @@ -1,20 +1,37 @@ package ca.bc.gov.educ.api.trax.service; import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.exception.ServiceException; import ca.bc.gov.educ.api.trax.model.dto.DistrictContact; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; @Service @Slf4j public class DistrictContactUpdatedService extends EventBaseService { + private final DistrictService districtService; + + @Autowired + public DistrictContactUpdatedService(DistrictService districtService) { + super(); + this.districtService = districtService; + } + @Override - public void processEvent(final DistrictContact districtContact, Event event) { + public void processEvent(final DistrictContact districtContact, EventEntity eventEntity) { log.debug("Processing District Contact Deleted"); - // process the event here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(event); + try{ + districtService.updateDistrictCache(districtContact.getDistrictId()); + this.updateEventWithHistory(eventEntity); + } catch (ServiceException e) { + // do not mark eventEntity as processed + log.error(e.getMessage()); + } + } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictUpdatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictUpdatedService.java new file mode 100644 index 00000000..472cc5c7 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictUpdatedService.java @@ -0,0 +1,39 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.model.dto.institute.District; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class DistrictUpdatedService extends EventBaseService { + + DistrictService districtService; + + @Autowired + public DistrictUpdatedService(DistrictService districtService) { + this.districtService = districtService; + } + + @Override + public void processEvent(final District district, EventEntity eventEntity) { + log.debug("Processing District Updated"); + try{ + districtService.updateDistrictCache(district.getDistrictId()); + this.updateEventWithHistory(eventEntity); + } catch (ServiceException e) { + log.error(e.getMessage()); + } + } + + @Override + public String getEventType() { + return EventType.UPDATE_DISTRICT.toString(); + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventBaseService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventBaseService.java index aed619fa..dc56c98c 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventBaseService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventBaseService.java @@ -1,23 +1,44 @@ package ca.bc.gov.educ.api.trax.service; import ca.bc.gov.educ.api.trax.constant.EventStatus; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; +import ca.bc.gov.educ.api.trax.repository.EventHistoryRepository; import ca.bc.gov.educ.api.trax.repository.EventRepository; import org.springframework.beans.factory.annotation.Autowired; import java.time.LocalDateTime; +import java.util.Optional; public abstract class EventBaseService implements EventService { @Autowired protected EventRepository eventRepository; + @Autowired + protected EventHistoryRepository eventHistoryRepository; - protected void updateEvent(final Event event) { - this.eventRepository.findByEventId(event.getEventId()).ifPresent(existingEvent -> { + protected void updateEvent(final EventEntity eventEntity) { + this.eventRepository.findByEventId(eventEntity.getEventId()).ifPresent(existingEvent -> { existingEvent.setEventStatus(EventStatus.PROCESSED.toString()); existingEvent.setUpdateDate(LocalDateTime.now()); this.eventRepository.save(existingEvent); }); } + /** + * Adds the event to the EventHistory table. Implementing classes may want to + * use this method if they are interested in history tracking + * @param eventEntity the event entity + */ + protected void updateEventWithHistory(final EventEntity eventEntity) { + this.updateEvent(eventEntity); + Optional savedEvent = this.eventRepository.findByEventId(eventEntity.getEventId()); + if(savedEvent.isPresent()){ + EventHistoryEntity eventHistoryEntity = new EventHistoryEntity(); + eventHistoryEntity.setEvent(savedEvent.get()); + this.eventHistoryRepository.save(eventHistoryEntity); + } + + } + } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventCommonService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventCommonService.java index bfb40695..60248e46 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventCommonService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventCommonService.java @@ -2,7 +2,7 @@ import ca.bc.gov.educ.api.trax.constant.FieldType; import ca.bc.gov.educ.api.trax.model.dto.GradStatusEventPayloadDTO; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import ca.bc.gov.educ.api.trax.model.entity.TraxStudentEntity; import ca.bc.gov.educ.api.trax.repository.TraxStudentRepository; import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; @@ -49,7 +49,7 @@ public abstract class EventCommonService extends EventBaseService { private EducGradTraxApiConstants constants; @Override - public void processEvent(T request, Event event) { + public void processEvent(T request, EventEntity eventEntity) { GradStatusEventPayloadDTO gradStatusUpdate = (GradStatusEventPayloadDTO) request; val em = this.getEntityManager(); @@ -58,7 +58,7 @@ public void processEvent(T request, Event event) { try { process(existingStudent, gradStatusUpdate, em, tx, constants.isTraxUpdateEnabled()); - var existingEvent = this.eventRepository.findByEventId(event.getEventId()); + var existingEvent = this.eventRepository.findByEventId(eventEntity.getEventId()); existingEvent.ifPresent(eventRecord -> { eventRecord.setEventStatus(PROCESSED.toString()); eventRecord.setUpdateDate(LocalDateTime.now()); @@ -95,7 +95,7 @@ private void process(Optional existingStudent, GradStatusEven } } - // UpdateFieldsMap to keep which TRAX fields need to be updated by Event Type + // UpdateFieldsMap to keep which TRAX fields need to be updated by EventEntity Type private Map> setupUpdateFieldsMap() { Map> updateFieldsMap = new HashMap<>(); if (StringUtils.equalsIgnoreCase(getEventType(), "GRAD_STUDENT_GRADUATED")) { diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorService.java index 50ccb473..b2c9dea1 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorService.java @@ -20,7 +20,7 @@ public class EventHandlerDelegatorService { private final ChoreographEventHandler choreographer; /** - * Instantiates a new Event handler delegator service. + * Instantiates a new EventEntity handler delegator service. * * @param choreographedEventPersistenceService the choreographed event persistence service * @param choreographer the choreographer diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventService.java index eed4fe00..02572460 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventService.java @@ -1,10 +1,10 @@ package ca.bc.gov.educ.api.trax.service; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; public interface EventService { - void processEvent(T request, Event event); + void processEvent(T request, EventEntity eventEntity); String getEventType(); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/RESTService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/RESTService.java index 36101de3..b7d8c23f 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/RESTService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/RESTService.java @@ -5,12 +5,12 @@ import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; import ca.bc.gov.educ.api.trax.util.ThreadLocalStateUtil; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; @@ -19,13 +19,15 @@ @Service public class RESTService { - @Autowired + private WebClient webClient; - private static final String ERROR_5xx = "5xx error."; + private static final String SERVER_ERROR = "5xx error."; private static final String SERVICE_FAILED_ERROR = "Service failed to process after max retries."; - public RESTService() { + @Autowired + public RESTService(WebClient webClient) { + this.webClient = webClient; } /** @@ -51,7 +53,7 @@ public T get(String url, Class clazz, String accessToken) { .retrieve() // if 5xx errors, throw Service error .onStatus(HttpStatusCode::is5xxServerError, - clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, ERROR_5xx), clientResponse.statusCode().value()))) + clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, SERVER_ERROR), clientResponse.statusCode().value()))) .bodyToMono(clazz) // only does retry if initial error was 5xx as service may be temporarily down // 4xx errors will always happen if 404, 401, 403 etc, so does not retry @@ -80,7 +82,7 @@ public T get(String url, Class clazz, WebClient webClient) { .retrieve() // if 5xx errors, throw Service error .onStatus(HttpStatusCode::is5xxServerError, - clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, ERROR_5xx), clientResponse.statusCode().value()))) + clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, SERVER_ERROR), clientResponse.statusCode().value()))) .bodyToMono(clazz) // only does retry if initial error was 5xx as service may be temporarily down // 4xx errors will always happen if 404, 401, 403 etc, so does not retry @@ -92,7 +94,10 @@ public T get(String url, Class clazz, WebClient webClient) { .block(); } catch (Exception e) { // catches IOExceptions and the like - throw new ServiceException(getErrorMessage(url, e.getLocalizedMessage()), HttpStatus.SERVICE_UNAVAILABLE.value(), e); + throw new ServiceException( + getErrorMessage(url, e.getLocalizedMessage()), + (e instanceof WebClientResponseException) ? ((WebClientResponseException) e).getStatusCode().value() : HttpStatus.SERVICE_UNAVAILABLE.value(), + e); } return obj; } @@ -115,7 +120,7 @@ public T post(String url, Object body, Class clazz, String accessToken) { .body(BodyInserters.fromValue(body)) .retrieve() .onStatus(HttpStatusCode::is5xxServerError, - clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, ERROR_5xx), clientResponse.statusCode().value()))) + clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, SERVER_ERROR), clientResponse.statusCode().value()))) .bodyToMono(clazz) .retryWhen(Retry.backoff(3, Duration.ofSeconds(2)) .filter(ServiceException.class::isInstance) @@ -140,7 +145,7 @@ public T post(String url, Object body, Class clazz, WebClient webClient) .body(BodyInserters.fromValue(body)) .retrieve() .onStatus(HttpStatusCode::is5xxServerError, - clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, ERROR_5xx), clientResponse.statusCode().value()))) + clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, SERVER_ERROR), clientResponse.statusCode().value()))) .bodyToMono(clazz) .retryWhen(Retry.backoff(3, Duration.ofSeconds(2)) .filter(ServiceException.class::isInstance) diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedService.java index 2401fcb9..7a977360 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedService.java @@ -2,7 +2,7 @@ import ca.bc.gov.educ.api.trax.constant.EventType; import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -11,10 +11,10 @@ public class SchoolContactCreatedService extends EventBaseService { @Override - public void processEvent(final SchoolContact districtContact, Event event) { + public void processEvent(final SchoolContact schoolContact, EventEntity eventEntity) { log.debug("Processing School Contact Created"); - // process the event here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(event); + // process the eventEntity here as per https://eccbc.atlassian.net/browse/GRAD2-2648 + this.updateEvent(eventEntity); } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedService.java index 8ae2fbe4..3d5d8b07 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedService.java @@ -2,7 +2,7 @@ import ca.bc.gov.educ.api.trax.constant.EventType; import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -11,10 +11,10 @@ public class SchoolContactDeletedService extends EventBaseService { @Override - public void processEvent(final SchoolContact districtContact, Event event) { + public void processEvent(final SchoolContact districtContact, EventEntity eventEntity) { log.debug("Processing School Contact Deleted"); - // process the event here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(event); + // process the eventEntity here as per https://eccbc.atlassian.net/browse/GRAD2-2648 + this.updateEvent(eventEntity); } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedService.java index 12befcab..cbfe134c 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedService.java @@ -1,20 +1,35 @@ package ca.bc.gov.educ.api.trax.service; import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.exception.ServiceException; import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; @Service @Slf4j public class SchoolContactUpdatedService extends EventBaseService { + SchoolService schoolService; + + @Autowired + public SchoolContactUpdatedService(SchoolService schoolService) { + this.schoolService = schoolService; + } + @Override - public void processEvent(final SchoolContact districtContact, Event event) { + public void processEvent(final SchoolContact schoolContact, EventEntity eventEntity) { log.debug("Processing School Contact Updated"); - // process the event here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(event); + try{ + schoolService.updateSchoolCache(schoolContact.getSchoolId()); + this.updateEventWithHistory(eventEntity); + } catch (ServiceException e) { + // do not mark eventEntity as processed + log.error(e.getMessage()); + } } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedService.java new file mode 100644 index 00000000..a07e668a --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedService.java @@ -0,0 +1,39 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.model.dto.institute.School; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class SchoolCreatedService extends EventBaseService { + + SchoolService schoolService; + + @Autowired + public SchoolCreatedService(SchoolService schoolService) { + this.schoolService = schoolService; + } + + @Override + public void processEvent(final School school, EventEntity eventEntity) { + log.debug("Processing School Created"); + try{ + schoolService.updateSchoolCache(school.getSchoolId()); + this.updateEventWithHistory(eventEntity); + } catch (ServiceException e) { + log.error(e.getMessage()); + } + } + + @Override + public String getEventType() { + return EventType.CREATE_SCHOOL.toString(); + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java new file mode 100644 index 00000000..41094fc3 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java @@ -0,0 +1,41 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.model.dto.institute.MoveSchoolData; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Arrays; + +@Service +@Slf4j +public class SchoolMovedService extends EventBaseService { + + SchoolService schoolService; + + @Autowired + public SchoolMovedService(SchoolService schoolService) { + this.schoolService = schoolService; + } + + @Override + public void processEvent(final MoveSchoolData moveSchoolData, EventEntity eventEntity) { + log.debug("Processing School Moved"); + try{ + schoolService.updateSchoolCache(Arrays.asList(moveSchoolData.getFromSchoolId(), moveSchoolData.getToSchool().getSchoolId())); + this.updateEventWithHistory(eventEntity); + } catch (ServiceException e) { + log.error(e.getMessage()); + } + } + + @Override + public String getEventType() { + return EventType.MOVE_SCHOOL.toString(); + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedService.java new file mode 100644 index 00000000..33097e64 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedService.java @@ -0,0 +1,23 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class SchoolUpdatedService extends SchoolCreatedService { + + @Autowired + public SchoolUpdatedService(SchoolService schoolService) { + super(schoolService); + } + + @Override + public String getEventType() { + return EventType.UPDATE_SCHOOL.toString(); + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/DistrictService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/DistrictService.java index 246b0200..1d528dcd 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/DistrictService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/DistrictService.java @@ -1,6 +1,7 @@ package ca.bc.gov.educ.api.trax.service.institute; import ca.bc.gov.educ.api.trax.constant.CacheKey; +import ca.bc.gov.educ.api.trax.exception.ServiceException; import ca.bc.gov.educ.api.trax.model.dto.institute.District; import ca.bc.gov.educ.api.trax.model.entity.institute.DistrictEntity; import ca.bc.gov.educ.api.trax.model.transformer.institute.DistrictTransformer; @@ -62,4 +63,16 @@ public List getDistrictsFromRedisCache() { public void initializeDistrictCache(boolean force) { serviceHelper.initializeCache(force, CacheKey.DISTRICT_CACHE, this); } + + /** + * Updates the district details in the cache + * based on schoolId + * @param districtId the district id guid + */ + public void updateDistrictCache(String districtId) throws ServiceException { + log.debug(String.format("Updating district %s in cache.", districtId)); + District district = this.restService.get(String.format(constants.getGetDistrictFromInstituteApiUrl(), districtId), + District.class, webClient); + districtRedisRepository.save(this.districtTransformer.transformToEntity(district)); + } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java index 6d414f1c..e52b6cb3 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java @@ -1,6 +1,7 @@ package ca.bc.gov.educ.api.trax.service.institute; import ca.bc.gov.educ.api.trax.constant.CacheKey; +import ca.bc.gov.educ.api.trax.exception.ServiceException; import ca.bc.gov.educ.api.trax.model.dto.institute.School; import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolEntity; @@ -115,4 +116,27 @@ public void initializeSchoolDetailCache(boolean force) { serviceHelper.initializeCache(force, CacheKey.SCHOOL_DETAIL_CACHE, this); } + /** + * Updates the school and school details in the cache + * based on schoolId + * @param schoolId the school id guid + */ + public void updateSchoolCache(String schoolId) throws ServiceException { + // get details from institute + log.debug("Updating school %s in cache.", schoolId); + SchoolDetail schoolDetail = this.restService.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolId), + SchoolDetail.class, webClient); + schoolDetailRedisRepository.save(schoolDetailTransformer.transformToEntity(schoolDetail)); + } + + /** + * Updates the school and school details in the cache + * based on schoolId + * @param schoolIds the school id guids + */ + public void updateSchoolCache(List schoolIds) throws ServiceException { + for (String schoolId : schoolIds) { + updateSchoolCache(schoolId); + } + } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java index 7b78fcee..36491b76 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java @@ -166,6 +166,8 @@ public class EducGradTraxApiConstants { @Value("${endpoint.institute-api.get-all-districts.url}") private String allDistrictsFromInstituteApiUrl; + @Value("${endpoint.institute-api.get-district.url}") + private String getDistrictFromInstituteApiUrl; @Value("${endpoint.institute-api.get-all-school-category-codes.url}") private String allSchoolCategoryCodesFromInstituteApiUrl; diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml index 0565b42f..3424dc39 100644 --- a/api/src/main/resources/application.yaml +++ b/api/src/main/resources/application.yaml @@ -188,6 +188,8 @@ endpoint: url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/school/%s get-all-districts: url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/district + get-district: + url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/district/%s get-all-school-category-codes: url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/category-codes get-all-school-funding-group-codes: diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/ChoreographedEventPersistenceServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/ChoreographedEventPersistenceServiceTest.java index a64b83cc..7fc0a2af 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/ChoreographedEventPersistenceServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/ChoreographedEventPersistenceServiceTest.java @@ -7,7 +7,7 @@ import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; import ca.bc.gov.educ.api.trax.model.dto.ChoreographedEvent; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import ca.bc.gov.educ.api.trax.model.entity.TraxUpdatedPubEvent; import ca.bc.gov.educ.api.trax.repository.EventRepository; import ca.bc.gov.educ.api.trax.repository.TraxUpdatedPubEventRepository; @@ -83,14 +83,14 @@ public void testPersistEventToDB_givenTheExistingEvent() throws BusinessExceptio choreographedEvent.setEventType(EventType.GRAD_STUDENT_UPDATED); choreographedEvent.setEventOutcome(EventOutcome.GRAD_STATUS_UPDATED); - Event event = new Event(); - event.setEventType(EventType.GRAD_STUDENT_UPDATED.toString()); - event.setEventStatus(DB_COMMITTED.toString()); - event.setEventId(eventId); - event.setEventOutcome(EventOutcome.GRAD_STATUS_UPDATED.toString()); - event.setReplicationEventId(UUID.randomUUID()); + EventEntity eventEntity = new EventEntity(); + eventEntity.setEventType(EventType.GRAD_STUDENT_UPDATED.toString()); + eventEntity.setEventStatus(DB_COMMITTED.toString()); + eventEntity.setEventId(eventId); + eventEntity.setEventOutcome(EventOutcome.GRAD_STATUS_UPDATED.toString()); + eventEntity.setReplicationEventId(UUID.randomUUID()); - Mockito.when(eventRepository.findByEventId(eventId)).thenReturn(Optional.of(event)); + Mockito.when(eventRepository.findByEventId(eventId)).thenReturn(Optional.of(eventEntity)); choreographedEventPersistenceService.persistEventToDB(choreographedEvent); @@ -105,17 +105,17 @@ public void testPersistEventToDB_givenTheNewEvent() throws BusinessException { choreographedEvent.setEventID(eventId); choreographedEvent.setEventType(EventType.GRAD_STUDENT_UPDATED); choreographedEvent.setEventOutcome(EventOutcome.GRAD_STATUS_UPDATED); - choreographedEvent.setEventPayload("{ test: 'event'}"); + choreographedEvent.setEventPayload("{ test: 'eventEntity'}"); - Event event = new Event(); - event.setEventType(EventType.GRAD_STUDENT_UPDATED.toString()); - event.setEventStatus(DB_COMMITTED.toString()); - event.setEventId(eventId); - event.setEventOutcome(EventOutcome.GRAD_STATUS_UPDATED.toString()); - event.setReplicationEventId(UUID.randomUUID()); + EventEntity eventEntity = new EventEntity(); + eventEntity.setEventType(EventType.GRAD_STUDENT_UPDATED.toString()); + eventEntity.setEventStatus(DB_COMMITTED.toString()); + eventEntity.setEventId(eventId); + eventEntity.setEventOutcome(EventOutcome.GRAD_STATUS_UPDATED.toString()); + eventEntity.setReplicationEventId(UUID.randomUUID()); Mockito.when(eventRepository.findByEventId(eventId)).thenReturn(Optional.empty()); - Mockito.when(eventRepository.save(event)).thenReturn(event); + Mockito.when(eventRepository.save(eventEntity)).thenReturn(eventEntity); choreographedEventPersistenceService.persistEventToDB(choreographedEvent); @@ -123,7 +123,7 @@ public void testPersistEventToDB_givenTheNewEvent() throws BusinessException { } @Test - public void testUpdateEventStatus_givenTraxUpdatedEvent() throws BusinessException { + public void testUpdateEventStatus_givenTraxUpdatedEvent() { UUID eventId = UUID.randomUUID(); ChoreographedEvent choreographedEvent = new ChoreographedEvent(); diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedServiceTest.java index d91b65aa..937e3ad0 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedServiceTest.java @@ -1,17 +1,26 @@ package ca.bc.gov.educ.api.trax.service; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; import ca.bc.gov.educ.api.trax.support.TestUtils; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; public class DistrictContactUpdatedServiceTest extends BaseReplicationServiceTest { @Autowired private DistrictContactUpdatedService districtContactUpdatedService; + @MockBean + private DistrictService districtServiceMock; + @Test public void testProcessEvent_givenUPDATE_DISTRICT_CONTACT_Event_shouldProcessEvent() throws JsonProcessingException { final var request = TestUtils.createDistrictContact(); @@ -25,4 +34,19 @@ public void testProcessEvent_givenUPDATE_DISTRICT_CONTACT_Event_shouldProcessEve } } + @Test + public void testProcessEvent_givenUPDATE_DISTRICT_CONTACT_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { + final String ERROR_MSG = "Test Exception"; + doThrow(new ServiceException(ERROR_MSG)).when(districtServiceMock).updateDistrictCache(anyString()); + final var request = TestUtils.createDistrictContact(); + final var event = TestUtils.createEvent("UPDATE_SCHOOL_CONTACT", request, this.replicationTestUtils.getEventRepository()); + this.districtContactUpdatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); + } else { + fail("UPDATE_SCHOOL_CONTACT failed to process"); + } + } + } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictUpdatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictUpdatedServiceTest.java new file mode 100644 index 00000000..503d3af8 --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictUpdatedServiceTest.java @@ -0,0 +1,52 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; +import ca.bc.gov.educ.api.trax.support.TestUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; + +public class DistrictUpdatedServiceTest extends BaseReplicationServiceTest { + + @Autowired + private DistrictUpdatedService districtUpdatedService; + + @MockBean + private DistrictService districtServiceMock; + + @Test + public void testProcessEvent_givenUPDATE_DISTRICT_Event_shouldProcessEvent() throws JsonProcessingException { + final var request = TestUtils.createDistrict(); + final var event = TestUtils.createEvent(EventType.UPDATE_DISTRICT.toString(), request, this.replicationTestUtils.getEventRepository()); + this.districtUpdatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("PROCESSED", result.get().getEventStatus()); + } else { + fail("UPDATE_DISTRICT failed to process"); + } + } + + @Test + public void testProcessEvent_givenUPDATE_DISTRICT_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { + final String ERROR_MSG = "Test Exception"; + doThrow(new ServiceException(ERROR_MSG)).when(districtServiceMock).updateDistrictCache(anyString()); + final var request = TestUtils.createDistrict(); + final var event = TestUtils.createEvent(EventType.UPDATE_DISTRICT.toString(), request, this.replicationTestUtils.getEventRepository()); + this.districtUpdatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); + } else { + fail("UPDATE_DISTRICT failed to process"); + } + } +} diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedServiceTest.java index 7d44daba..d89dac5e 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedServiceTest.java @@ -1,18 +1,26 @@ package ca.bc.gov.educ.api.trax.service; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; import ca.bc.gov.educ.api.trax.support.TestUtils; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; public class SchoolContactUpdatedServiceTest extends BaseReplicationServiceTest { @Autowired private SchoolContactUpdatedService schoolContactUpdatedService; + @MockBean + private SchoolService schoolServiceMock; + @Test public void testProcessEvent_givenUPDATE_SCHOOL_CONTACT_Event_shouldProcessEvent() throws JsonProcessingException { final var request = TestUtils.createSchoolContact(); @@ -25,4 +33,19 @@ public void testProcessEvent_givenUPDATE_SCHOOL_CONTACT_Event_shouldProcessEvent fail("UPDATE_SCHOOL_CONTACT failed to process"); } } + + @Test + public void testProcessEvent_givenUPDATE_SCHOOL_CONTACT_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { + final String ERROR_MSG = "Test Exception"; + doThrow(new ServiceException(ERROR_MSG)).when(schoolServiceMock).updateSchoolCache(anyString()); + final var request = TestUtils.createSchoolContact(); + final var event = TestUtils.createEvent("UPDATE_SCHOOL_CONTACT", request, this.replicationTestUtils.getEventRepository()); + this.schoolContactUpdatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); + } else { + fail("UPDATE_SCHOOL_CONTACT failed to process"); + } + } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedServiceTest.java new file mode 100644 index 00000000..d8979264 --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedServiceTest.java @@ -0,0 +1,52 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; +import ca.bc.gov.educ.api.trax.support.TestUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; + +public class SchoolCreatedServiceTest extends BaseReplicationServiceTest { + + @Autowired + private SchoolCreatedService schoolCreatedService; + + @MockBean + private SchoolService schoolServiceMock; + + @Test + public void testProcessEvent_givenCREATE_SCHOOL_Event_shouldProcessEvent() throws JsonProcessingException { + final var request = TestUtils.createSchool(); + final var event = TestUtils.createEvent(EventType.CREATE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + this.schoolCreatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("PROCESSED", result.get().getEventStatus()); + } else { + fail("CREATE_SCHOOL failed to process"); + } + } + + @Test + public void testProcessEvent_givenCREATE_SCHOOL_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { + final String ERROR_MSG = "Test Exception"; + doThrow(new ServiceException(ERROR_MSG)).when(schoolServiceMock).updateSchoolCache(anyString()); + final var request = TestUtils.createSchool(); + final var event = TestUtils.createEvent(EventType.CREATE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + this.schoolCreatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); + } else { + fail("CREATE_SCHOOL failed to process"); + } + } +} diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolMovedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolMovedServiceTest.java new file mode 100644 index 00000000..bf76d956 --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolMovedServiceTest.java @@ -0,0 +1,52 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; +import ca.bc.gov.educ.api.trax.support.TestUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.doThrow; + +public class SchoolMovedServiceTest extends BaseReplicationServiceTest { + + @Autowired + private SchoolMovedService schoolMovedService; + + @MockBean + private SchoolService schoolServiceMock; + + @Test + public void testProcessEvent_givenMOVE_SCHOOL_Event_shouldProcessEvent() throws JsonProcessingException { + final var request = TestUtils.createMoveSchoolData(); + final var event = TestUtils.createEvent(EventType.MOVE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + this.schoolMovedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("PROCESSED", result.get().getEventStatus()); + } else { + fail("MOVE_SCHOOL failed to process"); + } + } + + @Test + public void testProcessEvent_givenMOVE_SCHOOL_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { + final String ERROR_MSG = "Test Exception"; + doThrow(new ServiceException(ERROR_MSG)).when(schoolServiceMock).updateSchoolCache(anyList()); + final var request = TestUtils.createMoveSchoolData(); + final var event = TestUtils.createEvent(EventType.MOVE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + this.schoolMovedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); + } else { + fail("MOVE_SCHOOL failed to process"); + } + } +} diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedServiceTest.java new file mode 100644 index 00000000..b6e76f90 --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedServiceTest.java @@ -0,0 +1,52 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; +import ca.bc.gov.educ.api.trax.support.TestUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; + +public class SchoolUpdatedServiceTest extends BaseReplicationServiceTest { + + @Autowired + private SchoolUpdatedService schoolUpdatedService; + + @MockBean + private SchoolService schoolServiceMock; + + @Test + public void testProcessEvent_givenUPDATE_SCHOOL_Event_shouldProcessEvent() throws JsonProcessingException { + final var request = TestUtils.createSchool(); + final var event = TestUtils.createEvent(EventType.UPDATE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + this.schoolUpdatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("PROCESSED", result.get().getEventStatus()); + } else { + fail("UPDATE_SCHOOL failed to process"); + } + } + + @Test + public void testProcessEvent_givenUPDATE_SCHOOL_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { + final String ERROR_MSG = "Test Exception"; + doThrow(new ServiceException(ERROR_MSG)).when(schoolServiceMock).updateSchoolCache(anyString()); + final var request = TestUtils.createSchool(); + final var event = TestUtils.createEvent(EventType.UPDATE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + this.schoolUpdatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); + } else { + fail("UPDATE_SCHOOL failed to process"); + } + } +} diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteDistrictServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteDistrictServiceTest.java index 6ad328f1..0f4afd93 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteDistrictServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteDistrictServiceTest.java @@ -11,13 +11,15 @@ import ca.bc.gov.educ.api.trax.model.entity.institute.DistrictEntity; import ca.bc.gov.educ.api.trax.model.transformer.institute.DistrictTransformer; import ca.bc.gov.educ.api.trax.repository.redis.DistrictRedisRepository; +import ca.bc.gov.educ.api.trax.service.RESTService; +import ca.bc.gov.educ.api.trax.support.TestUtils; import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; import ca.bc.gov.educ.api.trax.util.RestUtils; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.junit.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.exceptions.base.MockitoException; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; @@ -27,9 +29,7 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.core.ValueOperations; import org.springframework.http.HttpHeaders; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; @@ -39,12 +39,13 @@ import reactor.core.publisher.Mono; import redis.clients.jedis.JedisCluster; -import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.function.Consumer; +import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -62,6 +63,8 @@ public class InstituteDistrictServiceTest { @Autowired private EducGradTraxApiConstants constants; @Autowired + private DistrictTransformer districtTransformer; + @Autowired private DistrictService districtService; @MockBean private DistrictRedisRepository districtRedisRepository; @@ -73,6 +76,8 @@ public class InstituteDistrictServiceTest { @MockBean @Qualifier("default") WebClient webClientMock; + + @Mock private WebClient.RequestHeadersSpec requestHeadersSpecMock; @Mock @@ -87,8 +92,7 @@ public class InstituteDistrictServiceTest { private Mono> districtEntitiesMock; @Mock private List districtsMock; - @MockBean - private DistrictTransformer districtTransformerMock; + // NATS @MockBean @@ -101,6 +105,8 @@ public class InstituteDistrictServiceTest { private Subscriber subscriber; @MockBean private RestUtils restUtils; + @MockBean + private RESTService restServiceMock; @TestConfiguration static class TestConfigInstitute { @@ -249,4 +255,28 @@ public void whenInitializeDistrictCache_WithReadyAndTrue_ThenForceLoad() { .thenReturn(anyString()); districtService.initializeDistrictCache(true); } + + @Test + public void updateDistrictCache_givenValidDistrictId_shouldUpdateCache() { + // given + // set up initial district in redis mock + DistrictEntity districtEntity = createDistrictEntity(); + District district = districtTransformer.transformToDTO(districtEntity); + // when + // call updateDistrictCache with district id and mock a return from webclient + Mockito.when(this.restServiceMock.get(anyString(), + any(), any(WebClient.class))).thenReturn(district); + // then + Mockito.when(this.districtRedisRepository.save(districtEntity)).thenReturn(districtEntity); + Mockito.when(this.districtRedisRepository.findById(districtEntity.getDistrictId())).thenReturn(Optional.of(districtEntity)); + // mock return of district from redis cache and compare + districtService.updateDistrictCache(districtEntity.getDistrictId()); + assertTrue(this.districtRedisRepository.findById(districtEntity.getDistrictId()).isPresent()); + } + + private DistrictEntity createDistrictEntity() { + District d = TestUtils.createDistrict(); + DistrictEntity de = this.districtTransformer.transformToEntity(d); + return de; + } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/intstituteSchoolServiceTestToo.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/intstituteSchoolServiceTestToo.java new file mode 100644 index 00000000..56f437a5 --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/intstituteSchoolServiceTestToo.java @@ -0,0 +1,163 @@ +package ca.bc.gov.educ.api.trax.service.institute; + +import ca.bc.gov.educ.api.trax.messaging.NatsConnection; +import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; +import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; +import ca.bc.gov.educ.api.trax.model.dto.ResponseObj; +import ca.bc.gov.educ.api.trax.model.dto.institute.School; +import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; +import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolDetailEntity; +import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolEntity; +import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolDetailTransformer; +import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolTransformer; +import ca.bc.gov.educ.api.trax.repository.redis.SchoolDetailRedisRepository; +import ca.bc.gov.educ.api.trax.repository.redis.SchoolRedisRepository; +import ca.bc.gov.educ.api.trax.service.RESTService; +import ca.bc.gov.educ.api.trax.support.TestUtils; +import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; +import ca.bc.gov.educ.api.trax.util.RestUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import redis.clients.jedis.JedisCluster; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; + +@RunWith(SpringRunner.class) +@SpringBootTest +@ActiveProfiles("test") +@ExtendWith(MockitoExtension.class) +public class intstituteSchoolServiceTestToo { + + @Autowired + private EducGradTraxApiConstants constants; + @MockBean + private SchoolService schoolService; + @MockBean + private SchoolRedisRepository schoolRedisRepository; + @MockBean + private JedisConnectionFactory jedisConnectionFactoryMock; + @MockBean + private JedisCluster jedisClusterMock; + @MockBean + @Qualifier("default") + WebClient webClientMock; + @Mock + private WebClient.RequestHeadersSpec requestHeadersSpecMock; + @Mock + private WebClient.RequestHeadersUriSpec requestHeadersUriSpecMock; + @Mock + private WebClient.ResponseSpec responseSpecMock; + @Mock + private HttpHeaders httpHeadersMock; + @Mock + private ResponseObj responseObjectMock; + @Mock + private Mono> schoolEntitiesMock; + @Mock + private Mono> schoolDetailEntitiesMock; + @Mock + private List schoolsMock; + @Mock + private List schoolDetailsMock; + @MockBean + private RestUtils restUtils; + @MockBean + private SchoolTransformer schoolTransformerMock; + @Autowired + private SchoolDetailTransformer schoolDetailTransformer; + @MockBean + private RESTService restServiceMock; + @MockBean + private SchoolDetailRedisRepository schoolDetailRedisRepository; + // NATS + @MockBean + private NatsConnection natsConnection; + + @MockBean + private Publisher publisher; + + @MockBean + private Subscriber subscriber; + + @TestConfiguration + static class TestConfigInstitute { + @Bean + public ClientRegistrationRepository clientRegistrationRepository() { + return new ClientRegistrationRepository() { + @Override + public ClientRegistration findByRegistrationId(String registrationId) { + return null; + } + }; + } + } + + @Test + public void updateSchoolCache_givenValidShcoolId_shouldUpdate() { + SchoolDetail schoolDetail = TestUtils.createSchoolDetail(); + SchoolDetailEntity schoolDetailEntity = schoolDetailTransformer.transformToEntity(schoolDetail); + Optional schoolDetailEntityOptional = Optional.of(schoolDetailEntity); + when(this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolDetail.getSchoolId()), + SchoolDetail.class, this.webClientMock)).thenReturn(schoolDetail); + when(this.schoolDetailRedisRepository.findById(schoolDetail.getSchoolId())).thenReturn(schoolDetailEntityOptional); + doNothing().when(this.schoolService).updateSchoolCache(schoolDetail.getSchoolId()); + this.schoolService.updateSchoolCache(schoolDetail.getSchoolId()); + Optional response = this.schoolDetailRedisRepository.findById(schoolDetail.getSchoolId()); + if (response.isPresent()) { + Assert.assertEquals(response.get().getSchoolId(), schoolDetail.getSchoolId()); + } else { + Assert.fail(); + } + } + + @Test + public void updateSchoolCache_givenValidShcoolIdList_shouldUpdate() { + SchoolDetail schoolDetail1 = TestUtils.createSchoolDetail(); + SchoolDetail schoolDetail2 = TestUtils.createSchoolDetail(); + SchoolDetailEntity schoolDetailEntity1 = schoolDetailTransformer.transformToEntity(schoolDetail1); + SchoolDetailEntity schoolDetailEntity2 = schoolDetailTransformer.transformToEntity(schoolDetail2); + Optional schoolDetailEntityOptional1 = Optional.of(schoolDetailEntity1); + Optional schoolDetailEntityOptional2 = Optional.of(schoolDetailEntity2); + when(this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolDetail1.getSchoolId()), + SchoolDetail.class, this.webClientMock)).thenReturn(schoolDetail1); + when(this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolDetail2.getSchoolId()), + SchoolDetail.class, this.webClientMock)).thenReturn(schoolDetail2); + when(this.schoolDetailRedisRepository.findById(schoolDetail1.getSchoolId())).thenReturn(schoolDetailEntityOptional1); + when(this.schoolDetailRedisRepository.findById(schoolDetail2.getSchoolId())).thenReturn(schoolDetailEntityOptional2); + List schoolIds = Arrays.asList(schoolDetail1.getSchoolId(), schoolDetail2.getSchoolId()); + doNothing().when(this.schoolService).updateSchoolCache(schoolIds); + this.schoolService.updateSchoolCache(schoolIds); + Optional response1 = this.schoolDetailRedisRepository.findById(schoolDetail1.getSchoolId()); + Optional response2 = this.schoolDetailRedisRepository.findById(schoolDetail2.getSchoolId()); + if (response1.isPresent() && response2.isPresent()) { + Assert.assertTrue(response1.get().getSchoolId() == schoolDetail1.getSchoolId() && response2.get().getSchoolId() == schoolDetail2.getSchoolId()); + } else { + Assert.fail(); + } + + } + +} diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/support/TestUtils.java b/api/src/test/java/ca/bc/gov/educ/api/trax/support/TestUtils.java index e2a7b93e..cde06d6d 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/support/TestUtils.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/support/TestUtils.java @@ -4,7 +4,8 @@ import ca.bc.gov.educ.api.trax.model.dto.DistrictContact; import ca.bc.gov.educ.api.trax.model.dto.GradStatusEventPayloadDTO; import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; -import ca.bc.gov.educ.api.trax.model.entity.Event; +import ca.bc.gov.educ.api.trax.model.dto.institute.*; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import ca.bc.gov.educ.api.trax.model.entity.TraxStudentEntity; import ca.bc.gov.educ.api.trax.repository.EventRepository; import ca.bc.gov.educ.api.trax.util.JsonUtil; @@ -14,6 +15,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import java.util.UUID; import static ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants.DEFAULT_CREATED_BY; @@ -36,8 +38,8 @@ public static GradStatusEventPayloadDTO createGraduationStatus(boolean isGraduat return graduationStatus; } - public static Event createEvent(String eventType, Object payload, EventRepository eventRepository) throws JsonProcessingException { - var event = Event.builder() + public static EventEntity createEvent(String eventType, Object payload, EventRepository eventRepository) throws JsonProcessingException { + var event = EventEntity.builder() .eventType(eventType) .eventId(UUID.randomUUID()) .eventOutcome("DB_COMMITTED") @@ -93,6 +95,37 @@ public static SchoolContact createSchoolContact() { return contact; } + public static School createSchool() { + var school = new School(); + school.setSchoolId(UUID.randomUUID().toString()); + school.setDistrictId(UUID.randomUUID().toString()); + school.setMincode("07996006"); + school.setIndependentAuthorityId(UUID.randomUUID().toString()); + school.setSchoolNumber("96006"); + school.setFaxNumber("2507436200"); + school.setPhoneNumber("2507435516"); + school.setEmail("executiveoffice@shawnigan.ca"); + school.setWebsite(null); + school.setDisplayName("Shawnigan Lake"); + school.setDisplayNameNoSpecialChars("Shawnigan Lake"); + school.setSchoolReportingRequirementCode("REGULAR"); + school.setSchoolOrganizationCode("QUARTER"); + school.setSchoolCategoryCode("INDEPEND"); + school.setFacilityTypeCode("STANDARD"); + school.setOpenedDate("1989-09-01T00:00:00"); + school.setCanIssueCertificates(true); + school.setCanIssueTranscripts(true); + return school; + } + + public static MoveSchoolData createMoveSchoolData() { + var move = new MoveSchoolData(); + move.setToSchool(createSchool()); + move.setMoveDate(LocalDateTime.now().toString()); + move.setFromSchoolId(UUID.randomUUID().toString()); + return move; + } + public static DistrictContact createDistrictContact() { var contact = new DistrictContact(); contact.setDistrictId(UUID.randomUUID().toString()); @@ -182,4 +215,43 @@ public static TraxStudentEntity createTraxStudent(String program, String student } return traxStudent; } + + public static District createDistrict() { + District district = new District(); + district.setDistrictId(UUID.randomUUID().toString()); + district.setDistrictNumber("002"); + district.setFaxNumber("1233216547"); + district.setPhoneNumber("3216549874"); + district.setEmail("district@district.ca"); + district.setWebsite("www.district.ca"); + district.setDisplayName("Test Display Name"); + district.setDistrictRegionCode("NOT_APPLIC"); + district.setDistrictStatusCode("INACTIVE"); + return district; + } + + public static SchoolDetail createSchoolDetail(){ + String schoolId = UUID.randomUUID().toString(); + SchoolAddress schoolAddress = new SchoolAddress(); + schoolAddress.setSchoolId(schoolId); + schoolAddress.setAddressLine1("123 Fake St"); + schoolAddress.setCity("Vancouverland"); + schoolAddress.setCountryCode("CAN"); + schoolAddress.setPostal("VQV2L2"); + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId(schoolId); + schoolDetail.setSchoolNumber("96006"); + schoolDetail.setDistrictId(UUID.randomUUID().toString()); + schoolDetail.setAddresses(Arrays.asList(schoolAddress)); + schoolDetail.setCreateDate(LocalDateTime.now().toString()); + schoolDetail.setCanIssueCertificates(true); + schoolDetail.setDisplayName("Blah"); + schoolDetail.setCreateUser("Test"); + schoolDetail.setUpdateDate(LocalDateTime.now().toString()); + schoolDetail.setUpdateUser("Test"); + schoolDetail.setCreateDate(LocalDateTime.now().toString()); + schoolDetail.setCanIssueTranscripts(true); + schoolDetail.setDisplayNameNoSpecialChars("blah blah"); + return schoolDetail; + } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/util/ReplicationTestUtils.java b/api/src/test/java/ca/bc/gov/educ/api/trax/util/ReplicationTestUtils.java index a6cd346f..dac2cecb 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/util/ReplicationTestUtils.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/util/ReplicationTestUtils.java @@ -1,5 +1,6 @@ package ca.bc.gov.educ.api.trax.util; +import ca.bc.gov.educ.api.trax.repository.EventHistoryRepository; import ca.bc.gov.educ.api.trax.repository.EventRepository; import lombok.Getter; import org.springframework.beans.factory.annotation.Autowired; @@ -14,14 +15,17 @@ public class ReplicationTestUtils { private final EventRepository eventRepository; + private final EventHistoryRepository eventHistoryRepository; @Autowired - public ReplicationTestUtils(EventRepository eventRepository) { + public ReplicationTestUtils(EventRepository eventRepository, EventHistoryRepository eventHistoryRepository) { this.eventRepository = eventRepository; + this.eventHistoryRepository = eventHistoryRepository; } @Transactional(propagation = Propagation.REQUIRES_NEW) public void cleanDB() { + this.eventHistoryRepository.deleteAll(); this.eventRepository.deleteAll(); } } diff --git a/api/src/test/resources/application.yaml b/api/src/test/resources/application.yaml index 1e0bdd5e..9bffa622 100644 --- a/api/src/test/resources/application.yaml +++ b/api/src/test/resources/application.yaml @@ -130,6 +130,8 @@ endpoint: url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/school/%s get-all-districts: url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/district + get-district: + url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/district/%s get-all-school-category-codes: url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/category-codes get-all-school-funding-group-codes: From 32e8f2ca04c1e42aae24af244e3ba45c6fb38e3a Mon Sep 17 00:00:00 2001 From: Chris Ditcher Date: Mon, 12 Aug 2024 11:31:39 -0700 Subject: [PATCH 02/16] GRAD2-2920 Scheduled purge of old events and event history (#343) * Added cascade deletion of old events including history. * Added cron schedule every day at midnight * Added more test coverage. * Added more test coverage. * Updated exception to be thrown from repository layer. --------- Co-authored-by: chris.ditcher --- .../api/trax/model/entity/EventEntity.java | 3 + .../repository/EventHistoryRepository.java | 2 - .../api/trax/repository/EventRepository.java | 6 +- .../scheduler/PurgeOldRecordsScheduler.java | 25 +++++--- .../api/trax/service/EventHistoryService.java | 31 +++++++++ .../service/EventHistoryServiceMockTest.java | 32 ++++++++++ .../trax/service/EventHistoryServiceTest.java | 64 +++++++++++++++++++ .../gov/educ/api/trax/support/TestUtils.java | 20 +++++- tools/config/update-configmap.sh | 2 + 9 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java create mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceMockTest.java create mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java index b3fb0f53..ed6bd837 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java @@ -88,6 +88,9 @@ public class EventEntity { @Column(name = "ACTIVITY_CODE") private String activityCode; + @OneToOne(cascade = CascadeType.ALL, mappedBy = "event") + private EventHistoryEntity eventHistoryEntity; + /** * Gets event payload. * diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java index 3dd616b2..ea6cc59a 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java @@ -7,6 +7,4 @@ @Repository public interface EventHistoryRepository extends JpaRepository { - - } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventRepository.java b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventRepository.java index 9508d861..80d7822c 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventRepository.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventRepository.java @@ -2,8 +2,6 @@ import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -20,8 +18,6 @@ public interface EventRepository extends JpaRepository { List findAllByEventStatusOrderByCreateDate(String eventStatus); @Transactional - @Modifying - @Query("delete from EventEntity where createDate <= :createDate") - void deleteByCreateDateBefore(LocalDateTime createDate); + void deleteByCreateDateLessThan(LocalDateTime createDate); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/scheduler/PurgeOldRecordsScheduler.java b/api/src/main/java/ca/bc/gov/educ/api/trax/scheduler/PurgeOldRecordsScheduler.java index f494a5a0..d7453d7e 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/scheduler/PurgeOldRecordsScheduler.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/scheduler/PurgeOldRecordsScheduler.java @@ -1,12 +1,13 @@ package ca.bc.gov.educ.api.trax.scheduler; -import ca.bc.gov.educ.api.trax.repository.EventRepository; import ca.bc.gov.educ.api.trax.repository.TraxUpdatedPubEventRepository; +import ca.bc.gov.educ.api.trax.service.EventHistoryService; import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import net.javacrumbs.shedlock.core.LockAssert; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -16,14 +17,13 @@ @Slf4j public class PurgeOldRecordsScheduler { - private final EventRepository eventRepository; + private final EventHistoryService eventHistoryService; private final TraxUpdatedPubEventRepository traxUpdatedPubEventRepository; private final EducGradTraxApiConstants constants; - public PurgeOldRecordsScheduler(final EventRepository eventRepository, - final TraxUpdatedPubEventRepository traxUpdatedPubEventRepository, - final EducGradTraxApiConstants constants) { - this.eventRepository = eventRepository; + @Autowired + public PurgeOldRecordsScheduler(EventHistoryService eventHistoryService, TraxUpdatedPubEventRepository traxUpdatedPubEventRepository, EducGradTraxApiConstants constants) { + this.eventHistoryService = eventHistoryService; this.traxUpdatedPubEventRepository = traxUpdatedPubEventRepository; this.constants = constants; } @@ -33,10 +33,15 @@ public PurgeOldRecordsScheduler(final EventRepository eventRepository, lockAtLeastFor = "PT1H", lockAtMostFor = "PT1H") //midnight job so lock for an hour @Transactional public void purgeOldRecords() { - LockAssert.assertLocked(); - final LocalDateTime createDateToCompare = this.calculateCreateDateBasedOnStaleEventInDays(); - this.eventRepository.deleteByCreateDateBefore(createDateToCompare); - this.traxUpdatedPubEventRepository.deleteByCreateDateBefore(createDateToCompare); + try { + LockAssert.assertLocked(); + final LocalDateTime createDateToCompare = this.calculateCreateDateBasedOnStaleEventInDays(); + this.eventHistoryService.purgeOldEventAndEventHistoryRecords(createDateToCompare); + this.traxUpdatedPubEventRepository.deleteByCreateDateBefore(createDateToCompare); + } catch (Exception e) { + log.error(e.getMessage()); + } + } private LocalDateTime calculateCreateDateBasedOnStaleEventInDays() { diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java new file mode 100644 index 00000000..57eae2a1 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java @@ -0,0 +1,31 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.repository.EventRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Slf4j +@Service +public class EventHistoryService { + + private EventRepository eventRepository; + + @Autowired + public EventHistoryService(EventRepository eventRepository) { + this.eventRepository = eventRepository; + } + + public void purgeOldEventAndEventHistoryRecords(LocalDateTime sinceBefore) throws ServiceException { + try { + this.eventRepository.deleteByCreateDateLessThan(sinceBefore); + } catch (Exception e) { + throw new ServiceException(String.format("Exception encountered when attempting old Event History Purge: %s", e.getMessage())); + } + } + + +} diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceMockTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceMockTest.java new file mode 100644 index 00000000..b4bafdf9 --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceMockTest.java @@ -0,0 +1,32 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.repository.EventRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doThrow; + + +class EventHistoryServiceMockTest extends BaseReplicationServiceTest { + + @Autowired + EventHistoryService eventHistoryService; + + @MockBean + EventRepository eventRepository; + + @Test + void purgeOldEventAndEventHistoryRecords_givenExceptionThrown_shouldThrowException() { + final String ERROR_MSG = "Exception encountered"; + final LocalDateTime localDateTime = LocalDateTime.now(); + doThrow(new RuntimeException(ERROR_MSG)).when(eventRepository).deleteByCreateDateLessThan(localDateTime); + Exception exception = assertThrows(ServiceException.class, () -> eventHistoryService.purgeOldEventAndEventHistoryRecords(localDateTime)); + assertTrue(exception.getMessage().contains(ERROR_MSG)); + } +} \ No newline at end of file diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java new file mode 100644 index 00000000..424bc75f --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java @@ -0,0 +1,64 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; +import ca.bc.gov.educ.api.trax.repository.EventHistoryRepository; +import ca.bc.gov.educ.api.trax.repository.EventRepository; +import ca.bc.gov.educ.api.trax.support.TestUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.time.LocalDateTime; +import java.util.Optional; + +public class EventHistoryServiceTest extends BaseReplicationServiceTest { + @Autowired + private EventHistoryService eventHistoryService; + final LocalDateTime purgeTimeInDays = LocalDateTime.now().minusDays(2); + + @Test + public void testDeleteEvent_giventEventHistory_ShouldCascadeDelete() throws JsonProcessingException { + // set up + EventRepository eventRepository = this.replicationTestUtils.getEventRepository(); + EventHistoryRepository eventHistoryRepository = this.replicationTestUtils.getEventHistoryRepository(); + var event = TestUtils.createEvent("DELETE_DISTRICT_CONTACT", TestUtils.createDistrictContact(), LocalDateTime.now(), eventRepository); + var eventHistory = TestUtils.createEventHistory(event, LocalDateTime.now(), eventHistoryRepository); + eventRepository.deleteById(event.getReplicationEventId()); + Optional eventThatShouldBePurgedOptional = eventRepository.findById(event.getReplicationEventId()); + Optional eventHistoryThatShouldBePurgedAlso = eventHistoryRepository.findById(eventHistory.getId()); + Assert.assertTrue(eventHistoryThatShouldBePurgedAlso.isEmpty() && eventThatShouldBePurgedOptional.isEmpty()); + } + + @Test + public void purgeOldEventAndEventHistoryRecords_givenNoExceptionAndOldRecord_shouldPurgeRecords() throws JsonProcessingException { + // set up + EventRepository eventRepository = this.replicationTestUtils.getEventRepository(); + EventHistoryRepository eventHistoryRepository = this.replicationTestUtils.getEventHistoryRepository(); + var eventAge = LocalDateTime.now().minusDays(3); + var eventThatShouldBePurged = TestUtils.createEvent("DELETE_DISTRICT_CONTACT", TestUtils.createDistrictContact(), eventAge, eventRepository); + // set up event history for eventThatShouldBePurged + var eventHistory = TestUtils.createEventHistory(eventThatShouldBePurged, eventAge, eventHistoryRepository); + // call purge + eventHistoryService.purgeOldEventAndEventHistoryRecords(purgeTimeInDays); + // check repo and ensure that older record purged + Optional eventThatShouldBePurgedOptional = eventRepository.findById(eventThatShouldBePurged.getReplicationEventId()); + Optional eventHistoryThatShouldBePurgedAlso = eventHistoryRepository.findById(eventHistory.getId()); + Assert.assertTrue(eventHistoryThatShouldBePurgedAlso.isEmpty() && eventThatShouldBePurgedOptional.isEmpty()); + } + + @Test + public void purgeOldEventAndEventHistoryRecords_givenNoExceptionAndNewRecord_shouldNotPurgeRecords() throws JsonProcessingException { + EventRepository eventRepository = this.replicationTestUtils.getEventRepository(); + EventHistoryRepository eventHistoryRepository = this.replicationTestUtils.getEventHistoryRepository(); + var eventThatShouldNotBePurged = TestUtils.createEvent("DELETE_DISTRICT_CONTACT", TestUtils.createDistrictContact(), eventRepository); + var eventHistory = TestUtils.createEventHistory(eventThatShouldNotBePurged, LocalDateTime.now(), eventHistoryRepository); + // call purge + eventHistoryService.purgeOldEventAndEventHistoryRecords(purgeTimeInDays); + // check repo and ensure that new record is not purged + Optional eventThatShouldNotBePurgedOptional = eventRepository.findById(eventThatShouldNotBePurged.getReplicationEventId()); + Optional eventHistoryThatShouldNotBePurgedAlso = eventHistoryRepository.findById(eventHistory.getId()); + Assert.assertTrue(eventHistoryThatShouldNotBePurgedAlso.isPresent() && eventThatShouldNotBePurgedOptional.isPresent()); + } +} \ No newline at end of file diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/support/TestUtils.java b/api/src/test/java/ca/bc/gov/educ/api/trax/support/TestUtils.java index cde06d6d..a3bd8eec 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/support/TestUtils.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/support/TestUtils.java @@ -6,7 +6,9 @@ import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; import ca.bc.gov.educ.api.trax.model.dto.institute.*; import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; import ca.bc.gov.educ.api.trax.model.entity.TraxStudentEntity; +import ca.bc.gov.educ.api.trax.repository.EventHistoryRepository; import ca.bc.gov.educ.api.trax.repository.EventRepository; import ca.bc.gov.educ.api.trax.util.JsonUtil; import com.fasterxml.jackson.core.JsonProcessingException; @@ -39,6 +41,10 @@ public static GradStatusEventPayloadDTO createGraduationStatus(boolean isGraduat } public static EventEntity createEvent(String eventType, Object payload, EventRepository eventRepository) throws JsonProcessingException { + return createEvent(eventType, payload, LocalDateTime.now(), eventRepository); + } + + public static EventEntity createEvent(String eventType, Object payload, LocalDateTime createDate, EventRepository eventRepository) throws JsonProcessingException { var event = EventEntity.builder() .eventType(eventType) .eventId(UUID.randomUUID()) @@ -47,13 +53,25 @@ public static EventEntity createEvent(String eventType, Object payload, EventRep .eventStatus(DB_COMMITTED.toString()) .createUser(DEFAULT_CREATED_BY) .updateUser(DEFAULT_UPDATED_BY) - .createDate(LocalDateTime.now()) + .createDate(createDate) .updateDate(LocalDateTime.now()) .build(); eventRepository.save(event); return event; } + public static EventHistoryEntity createEventHistory(EventEntity event, LocalDateTime createdDate, EventHistoryRepository eventHistoryRepository) { + var eventHistory = new EventHistoryEntity(); + eventHistory.setEvent(event); + eventHistory.setAcknowledgeFlag("N"); + eventHistory.setCreateDate(createdDate); + eventHistory.setCreateUser("TEST"); + eventHistory.setUpdateDate(LocalDateTime.now()); + eventHistory.setUpdateUser("TEST"); + eventHistoryRepository.save(eventHistory); + return eventHistory; + } + public static AuthorityContact createAuthorityContact() { var auth = new AuthorityContact(); auth.setIndependentAuthorityId(UUID.randomUUID().toString()); diff --git a/tools/config/update-configmap.sh b/tools/config/update-configmap.sh index d86cabc9..855357a1 100644 --- a/tools/config/update-configmap.sh +++ b/tools/config/update-configmap.sh @@ -53,6 +53,8 @@ echo Creating config map "$APP_NAME"-config-map oc create -n "$GRAD_NAMESPACE"-"$envValue" configmap "$APP_NAME"-config-map \ --from-literal=APP_LOG_LEVEL="$APP_LOG_LEVEL" \ --from-literal=BASELINE_ON_MIGRATE="true" \ + --from-literal=CRON_SCHEDULED_PURGE_OLD_RECORDS: "0 0 0 * * *" \ + --from-literal=RECORDS_STALE_IN_DAYS: 365 \ --from-literal=CRON_SCHEDULED_GRAD_TO_TRAX_EVENTS="0 0/5 * * * *" \ --from-literal=CRON_SCHEDULED_GRAD_TO_TRAX_EVENTS_LOCK_AT_LEAST_FOR="PT1M" \ --from-literal=CRON_SCHEDULED_GRAD_TO_TRAX_EVENTS_LOCK_AT_MOST_FOR="PT5M" \ From 426e12f391e6f4ed53c6018a77d984b76a16b3e3 Mon Sep 17 00:00:00 2001 From: Chris Ditcher Date: Mon, 12 Aug 2024 15:44:39 -0700 Subject: [PATCH 03/16] GRAD2-2763 Add new activity codes for Institute (#344) * Added activity code checking. * Updated test coverage. * Increased test coverage. --------- Co-authored-by: chris.ditcher --- .../api/trax/constant/EventActivityCode.java | 8 ++ .../bc/gov/educ/api/trax/constant/Topics.java | 4 +- .../service/EventHandlerDelegatorService.java | 37 ++++++- .../EventHandlerDelegatorServiceTest.java | 96 +++++++++++++++++-- 4 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventActivityCode.java diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventActivityCode.java b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventActivityCode.java new file mode 100644 index 00000000..91bb5173 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/EventActivityCode.java @@ -0,0 +1,8 @@ +package ca.bc.gov.educ.api.trax.constant; + +/** + * Activity codes are for labeling types of events processed + */ +public enum EventActivityCode { + INSTITUTE_EVENT, PEN_EVENT, COREG_EVENT +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/constant/Topics.java b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/Topics.java index 93d6fa50..ca1a019d 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/constant/Topics.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/Topics.java @@ -12,6 +12,8 @@ public enum Topics { * TraxUpdate events topic. */ TRAX_UPDATE_EVENT_TOPIC, - INSTITUTE_EVENTS_TOPIC + INSTITUTE_EVENTS_TOPIC, + COREG_EVENTS_TOPIC, + PEN_EVENTS_TOPIC } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorService.java index b2c9dea1..eb7ddbae 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorService.java @@ -1,6 +1,8 @@ package ca.bc.gov.educ.api.trax.service; import ca.bc.gov.educ.api.trax.choreographer.ChoreographEventHandler; +import ca.bc.gov.educ.api.trax.constant.EventActivityCode; +import ca.bc.gov.educ.api.trax.constant.Topics; import ca.bc.gov.educ.api.trax.exception.BusinessException; import ca.bc.gov.educ.api.trax.model.dto.ChoreographedEvent; import io.nats.client.Message; @@ -10,7 +12,6 @@ import org.springframework.stereotype.Service; import java.io.IOException; - import static ca.bc.gov.educ.api.trax.constant.Topics.TRAX_UPDATE_EVENT_TOPIC; @Service @@ -41,7 +42,10 @@ public EventHandlerDelegatorService(final ChoreographedEventPersistenceService c * @param message the message * @throws IOException the io exception */ - public void handleChoreographyEvent(@NonNull final ChoreographedEvent choreographedEvent, final Message message) throws IOException { + public void handleChoreographyEvent(@NonNull ChoreographedEvent choreographedEvent, final Message message) throws IOException { + // some messages come in already with an activity code, some do not. + // set the activity code early in the process + setActivityCode(choreographedEvent, message); try { if (message.getSubject().equalsIgnoreCase(TRAX_UPDATE_EVENT_TOPIC.toString())) { this.choreographedEventPersistenceService.updateEventStatus(choreographedEvent); @@ -58,4 +62,33 @@ public void handleChoreographyEvent(@NonNull final ChoreographedEvent choreograp log.error("acknowledged to Jet Stream for exception..."); } } + + /** + * Applies the correct activity code to ChoreographedEvents. + * Add new activity codes here + * @param choreographedEvent the choreographed event object + * @param message message received from nats + */ + private void setActivityCode(@NonNull final ChoreographedEvent choreographedEvent, final Message message) { + Topics topics; + try { + topics = Topics.valueOf(message.getSubject()); + switch (topics) { + case INSTITUTE_EVENTS_TOPIC: + choreographedEvent.setActivityCode(EventActivityCode.INSTITUTE_EVENT.toString()); + break; + case COREG_EVENTS_TOPIC: + choreographedEvent.setActivityCode(EventActivityCode.COREG_EVENT.toString()); + break; + case PEN_EVENTS_TOPIC: + choreographedEvent.setActivityCode(EventActivityCode.PEN_EVENT.toString()); + break; + default: // do nothing + break; + } + } catch (Exception e) { + log.error("{} is not a valid topic", message.getSubject()); + } + + } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorServiceTest.java index 830b79fe..12d8ad72 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorServiceTest.java @@ -1,30 +1,108 @@ package ca.bc.gov.educ.api.trax.service; +import ca.bc.gov.educ.api.trax.choreographer.ChoreographEventHandler; +import ca.bc.gov.educ.api.trax.constant.EventActivityCode; +import ca.bc.gov.educ.api.trax.messaging.NatsConnection; +import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; +import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; import ca.bc.gov.educ.api.trax.model.dto.ChoreographedEvent; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import io.nats.client.Message; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import redis.clients.jedis.JedisCluster; + import java.io.IOException; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static ca.bc.gov.educ.api.trax.constant.Topics.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; -public class EventHandlerDelegatorServiceTest extends BaseReplicationServiceTest { +@SpringBootTest +@ExtendWith(OutputCaptureExtension.class) +class EventHandlerDelegatorServiceTest { - @MockBean + @SpyBean private EventHandlerDelegatorService eventHandlerDelegatorService; - @MockBean - private ChoreographedEvent choreographedEvent; - + private ChoreographedEventPersistenceService choreographedEventPersistenceService; + @MockBean + private ChoreographEventHandler choreographer; @MockBean private Message message; + @MockBean + private JedisConnectionFactory jedisConnectionFactoryMock; + @MockBean + private JedisCluster jedisClusterMock; + @MockBean + public Publisher publisher; + @MockBean + public Subscriber subscriber; + @MockBean + public NatsConnection natsConnection; + @MockBean + public ClientRegistrationRepository clientRegistrationRepository; + @MockBean + public OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository; + @MockBean + public OAuth2AuthorizedClientService oAuth2AuthorizedClientService; + private EventEntity eventEntity; + private ChoreographedEvent choreographedEvent; + + @BeforeEach + public void setUp() throws Exception { + choreographedEvent = new ChoreographedEvent(); + eventEntity = new EventEntity(); + eventEntity.setEventType("UPDATE_SCHOOL"); + when(message.getSubject()).thenReturn(INSTITUTE_EVENTS_TOPIC.toString()); + when(choreographedEventPersistenceService.persistEventToDB(any())).thenReturn(eventEntity); + } @Test - public void whenHandleChoreographyEvent_thenVerified() throws IOException { + void whenHandleChoreographyEvent_thenVerified() throws IOException { this.eventHandlerDelegatorService.handleChoreographyEvent(this.choreographedEvent, this.message); verify(eventHandlerDelegatorService, times(1)).handleChoreographyEvent(this.choreographedEvent, this.message); } + @Test + void testSetActivityCode_givenINSTITUTE_EVENT_shouldSetINSTITUTE_EVENTActivityCode() throws IOException { + this.eventHandlerDelegatorService.handleChoreographyEvent(this.choreographedEvent, this.message); + assertTrue(choreographedEvent.getActivityCode().equalsIgnoreCase(EventActivityCode.INSTITUTE_EVENT.toString())); + } + + @Test + void testSetActivityCode_givenPEN_EVENT_shouldSetPEN_EVENTActivityCode() throws IOException { + when(message.getSubject()).thenReturn(PEN_EVENTS_TOPIC.toString()); + this.eventHandlerDelegatorService.handleChoreographyEvent(this.choreographedEvent, this.message); + assertTrue(choreographedEvent.getActivityCode().equalsIgnoreCase(EventActivityCode.PEN_EVENT.toString())); + } + + @Test + void testSetActivityCode_givenCOREG_EVENT_shouldSetCOREG_EVENTActivityCode() throws IOException { + when(message.getSubject()).thenReturn(COREG_EVENTS_TOPIC.toString()); + this.eventHandlerDelegatorService.handleChoreographyEvent(this.choreographedEvent, this.message); + assertTrue(choreographedEvent.getActivityCode().equalsIgnoreCase(EventActivityCode.COREG_EVENT.toString())); + } + + @Test + void testSetActivityCode_givenInvalidCode_shouldLogError(CapturedOutput capturedOutput) throws IOException { + final String topic = "SILLY_EVENTS_TOPIC"; + final String errorMessage = String.format("%s is not a valid topic", topic); + when(message.getSubject()).thenReturn(topic); + this.eventHandlerDelegatorService.handleChoreographyEvent(this.choreographedEvent, this.message); + assertTrue(capturedOutput.getAll().contains(errorMessage)); + } + + } From ff1215b85b192ebb86fd4d89bcbb6d476ec62786 Mon Sep 17 00:00:00 2001 From: Chris Ditcher Date: Wed, 14 Aug 2024 12:55:16 -0700 Subject: [PATCH 04/16] GRAD2-2648 (#346) * Added update cache for school and district UD events. * Updated test cases for School and District CD * Added Create District functionality * Added Create District to event handler * Added thread sleep for not fully processed school moved events. * Changed exception handling * Reducing code duplication --------- Co-authored-by: chris.ditcher --- .../ChoreographEventHandler.java | 4 ++ .../DistrictContactCreatedService.java | 14 +++-- .../DistrictContactDeletedService.java | 14 +++-- .../DistrictContactEventBaseService.java | 32 ++++++++++++ .../trax/service/DistrictCreatedService.java | 39 ++++++++++++++ .../service/SchoolContactCreatedService.java | 14 +++-- .../service/SchoolContactDeletedService.java | 15 +++--- .../SchoolContactEventBaseService.java | 33 ++++++++++++ .../api/trax/service/SchoolMovedService.java | 4 ++ .../trax/service/institute/SchoolService.java | 3 +- .../DistrictContactCreatedServiceTest.java | 24 +++++++++ .../DistrictContactDeletedServiceTest.java | 24 +++++++++ .../DistrictContactUpdatedServiceTest.java | 4 +- .../service/DistrictCreatedServiceTest.java | 52 +++++++++++++++++++ .../SchoolContactCreatedServiceTest.java | 24 +++++++++ .../SchoolContactDeletedServiceTest.java | 24 +++++++++ 16 files changed, 289 insertions(+), 35 deletions(-) create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactEventBaseService.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictCreatedService.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactEventBaseService.java create mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictCreatedServiceTest.java diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/choreographer/ChoreographEventHandler.java b/api/src/main/java/ca/bc/gov/educ/api/trax/choreographer/ChoreographEventHandler.java index 90e07eb8..55740778 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/choreographer/ChoreographEventHandler.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/choreographer/ChoreographEventHandler.java @@ -124,6 +124,10 @@ public void handleEvent(@NonNull final EventEntity eventEntity) { val schoolCreated = JsonUtil.getJsonObjectFromString(School.class, eventEntity.getEventPayload()); this.eventServiceMap.get(CREATE_SCHOOL.toString()).processEvent(schoolCreated, eventEntity); } + case CREATE_DISTRICT -> { + val districtCreated = JsonUtil.getJsonObjectFromString(District.class, eventEntity.getEventPayload()); + this.eventServiceMap.get(CREATE_DISTRICT.toString()).processEvent(districtCreated, eventEntity); + } case MOVE_SCHOOL -> { val schoolMoved = JsonUtil.getJsonObjectFromString(MoveSchoolData.class, eventEntity.getEventPayload()); this.eventServiceMap.get(MOVE_SCHOOL.toString()).processEvent(schoolMoved, eventEntity); diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedService.java index 02b7d9a8..35f66f98 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedService.java @@ -1,20 +1,18 @@ package ca.bc.gov.educ.api.trax.service; import ca.bc.gov.educ.api.trax.constant.EventType; -import ca.bc.gov.educ.api.trax.model.dto.DistrictContact; -import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service @Slf4j -public class DistrictContactCreatedService extends EventBaseService { +public class DistrictContactCreatedService extends DistrictContactEventBaseService { - @Override - public void processEvent(final DistrictContact districtContact, EventEntity eventEntity) { - log.debug("Processing District Contact Created"); - // process the eventEntity here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(eventEntity); + @Autowired + public DistrictContactCreatedService(DistrictService districtService) { + super(districtService); } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedService.java index 7befed56..9cb44db5 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedService.java @@ -1,20 +1,18 @@ package ca.bc.gov.educ.api.trax.service; import ca.bc.gov.educ.api.trax.constant.EventType; -import ca.bc.gov.educ.api.trax.model.dto.DistrictContact; -import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service @Slf4j -public class DistrictContactDeletedService extends EventBaseService { +public class DistrictContactDeletedService extends DistrictContactEventBaseService { - @Override - public void processEvent(final DistrictContact districtContact, EventEntity eventEntity) { - log.debug("Processing District Contact Deleted"); - // process the eventEntity here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(eventEntity); + @Autowired + public DistrictContactDeletedService(DistrictService districtService) { + super(districtService); } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactEventBaseService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactEventBaseService.java new file mode 100644 index 00000000..3a8dbf32 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictContactEventBaseService.java @@ -0,0 +1,32 @@ +package ca.bc.gov.educ.api.trax.service; + + +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.model.dto.DistrictContact; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public abstract class DistrictContactEventBaseService extends EventBaseService { + + protected DistrictService districtService; + + protected DistrictContactEventBaseService(DistrictService districtService) { + this.districtService = districtService; + } + + @Override + public void processEvent(DistrictContact districtContact, EventEntity eventEntity) {log.debug("Processing {}", eventEntity.getEventType()); + try { + districtService.updateDistrictCache(districtContact.getDistrictId()); + this.updateEvent(eventEntity); + } catch (ServiceException e) { + // do not mark eventEntity as processed + log.error(e.getMessage()); + } + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictCreatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictCreatedService.java new file mode 100644 index 00000000..42280565 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/DistrictCreatedService.java @@ -0,0 +1,39 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.model.dto.institute.District; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class DistrictCreatedService extends EventBaseService { + + DistrictService districtService; + + @Autowired + public DistrictCreatedService(DistrictService districtService) { + this.districtService = districtService; + } + + @Override + public void processEvent(final District district, EventEntity eventEntity) { + log.debug("Processing District Created"); + try{ + districtService.updateDistrictCache(district.getDistrictId()); + this.updateEvent(eventEntity); + } catch (ServiceException e) { + log.error(e.getMessage()); + } + } + + @Override + public String getEventType() { + return EventType.CREATE_DISTRICT.toString(); + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedService.java index 7a977360..f3ab5832 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedService.java @@ -1,20 +1,18 @@ package ca.bc.gov.educ.api.trax.service; import ca.bc.gov.educ.api.trax.constant.EventType; -import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; -import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service @Slf4j -public class SchoolContactCreatedService extends EventBaseService { +public class SchoolContactCreatedService extends SchoolContactEventBaseService { - @Override - public void processEvent(final SchoolContact schoolContact, EventEntity eventEntity) { - log.debug("Processing School Contact Created"); - // process the eventEntity here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(eventEntity); + @Autowired + public SchoolContactCreatedService(SchoolService schoolService) { + super(schoolService); } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedService.java index 3d5d8b07..465a5ce6 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedService.java @@ -1,20 +1,19 @@ package ca.bc.gov.educ.api.trax.service; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; import ca.bc.gov.educ.api.trax.constant.EventType; -import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; -import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service @Slf4j -public class SchoolContactDeletedService extends EventBaseService { +public class SchoolContactDeletedService extends SchoolContactEventBaseService { - @Override - public void processEvent(final SchoolContact districtContact, EventEntity eventEntity) { - log.debug("Processing School Contact Deleted"); - // process the eventEntity here as per https://eccbc.atlassian.net/browse/GRAD2-2648 - this.updateEvent(eventEntity); + + @Autowired + public SchoolContactDeletedService(SchoolService schoolService) { + super(schoolService); } @Override diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactEventBaseService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactEventBaseService.java new file mode 100644 index 00000000..5b12ee70 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactEventBaseService.java @@ -0,0 +1,33 @@ +package ca.bc.gov.educ.api.trax.service; + + +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public abstract class SchoolContactEventBaseService extends EventBaseService { + + protected SchoolService schoolService; + + protected SchoolContactEventBaseService(SchoolService schoolService) { + this.schoolService = schoolService; + } + + @Override + public void processEvent(SchoolContact schoolContact, EventEntity eventEntity) { + log.debug("Processing {}", eventEntity.getEventType()); + try { + schoolService.updateSchoolCache(schoolContact.getSchoolId()); + this.updateEvent(eventEntity); + } catch (ServiceException e) { + // do not mark eventEntity as processed + log.error(e.getMessage()); + } + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java index 41094fc3..a338dbca 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java @@ -26,10 +26,14 @@ public SchoolMovedService(SchoolService schoolService) { public void processEvent(final MoveSchoolData moveSchoolData, EventEntity eventEntity) { log.debug("Processing School Moved"); try{ + // school move sometimes not fully processed, sleep 1 second + Thread.sleep(1000); schoolService.updateSchoolCache(Arrays.asList(moveSchoolData.getFromSchoolId(), moveSchoolData.getToSchool().getSchoolId())); this.updateEventWithHistory(eventEntity); } catch (ServiceException e) { log.error(e.getMessage()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java index e52b6cb3..bc2d8197 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java @@ -123,9 +123,10 @@ public void initializeSchoolDetailCache(boolean force) { */ public void updateSchoolCache(String schoolId) throws ServiceException { // get details from institute - log.debug("Updating school %s in cache.", schoolId); + log.debug("Updating school {} in cache.", schoolId); SchoolDetail schoolDetail = this.restService.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolId), SchoolDetail.class, webClient); + log.debug("Retrieved school: {} from Institute API", schoolDetail.getSchoolId()); schoolDetailRedisRepository.save(schoolDetailTransformer.transformToEntity(schoolDetail)); } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedServiceTest.java index 675a2b06..b49db9a5 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactCreatedServiceTest.java @@ -1,17 +1,26 @@ package ca.bc.gov.educ.api.trax.service; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; import ca.bc.gov.educ.api.trax.support.TestUtils; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; public class DistrictContactCreatedServiceTest extends BaseReplicationServiceTest { @Autowired private DistrictContactCreatedService districtContactCreatedService; + @MockBean + private DistrictService districtServiceMock; + @Test public void testProcessEvent_givenCREATE_DISTRICT_CONTACT_Event_shouldProcessEvent() throws JsonProcessingException { final var request = TestUtils.createDistrictContact(); @@ -25,4 +34,19 @@ public void testProcessEvent_givenCREATE_DISTRICT_CONTACT_Event_shouldProcessEve } } + @Test + public void testProcessEvent_givenCREATE_DISTRICT_CONTACT_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { + final String ERROR_MSG = "Test Exception"; + doThrow(new ServiceException(ERROR_MSG)).when(districtServiceMock).updateDistrictCache(anyString()); + final var request = TestUtils.createDistrictContact(); + final var event = TestUtils.createEvent("CREATE_DISTRICT_CONTACT", request, this.replicationTestUtils.getEventRepository()); + this.districtContactCreatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); + } else { + fail("CREATE_DISTRICT_CONTACT failed to process"); + } + } + } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedServiceTest.java index 14f187f6..108132e8 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactDeletedServiceTest.java @@ -1,17 +1,26 @@ package ca.bc.gov.educ.api.trax.service; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; import ca.bc.gov.educ.api.trax.support.TestUtils; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; public class DistrictContactDeletedServiceTest extends BaseReplicationServiceTest { @Autowired private DistrictContactDeletedService districtContactDeletedService; + @MockBean + private DistrictService districtServiceMock; + @Test public void testProcessEvent_givenDELETE_DISTRICT_CONTACT_Event_shouldProcessEvent() throws JsonProcessingException { final var request = TestUtils.createDistrictContact(); @@ -25,4 +34,19 @@ public void testProcessEvent_givenDELETE_DISTRICT_CONTACT_Event_shouldProcessEve } } + @Test + public void testProcessEvent_givenDELETE_DISTRICT_CONTACT_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { + final String ERROR_MSG = "Test Exception"; + doThrow(new ServiceException(ERROR_MSG)).when(districtServiceMock).updateDistrictCache(anyString()); + final var request = TestUtils.createDistrictContact(); + final var event = TestUtils.createEvent("DELETE_DISTRICT_CONTACT", request, this.replicationTestUtils.getEventRepository()); + this.districtContactDeletedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); + } else { + fail("DELETE_DISTRICT_CONTACT failed to process"); + } + } + } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedServiceTest.java index 937e3ad0..c2fceef0 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictContactUpdatedServiceTest.java @@ -39,13 +39,13 @@ public void testProcessEvent_givenUPDATE_DISTRICT_CONTACT_Event_ServiceUnavailab final String ERROR_MSG = "Test Exception"; doThrow(new ServiceException(ERROR_MSG)).when(districtServiceMock).updateDistrictCache(anyString()); final var request = TestUtils.createDistrictContact(); - final var event = TestUtils.createEvent("UPDATE_SCHOOL_CONTACT", request, this.replicationTestUtils.getEventRepository()); + final var event = TestUtils.createEvent("UPDATE_DISTRICT_CONTACT", request, this.replicationTestUtils.getEventRepository()); this.districtContactUpdatedService.processEvent(request, event); var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); if(result.isPresent()){ Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); } else { - fail("UPDATE_SCHOOL_CONTACT failed to process"); + fail("UPDATE_DISTRICT_CONTACT failed to process"); } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictCreatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictCreatedServiceTest.java new file mode 100644 index 00000000..b671a37a --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/DistrictCreatedServiceTest.java @@ -0,0 +1,52 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; +import ca.bc.gov.educ.api.trax.support.TestUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; + +public class DistrictCreatedServiceTest extends BaseReplicationServiceTest { + + @Autowired + private DistrictCreatedService districtCreatedService; + + @MockBean + private DistrictService districtServiceMock; + + @Test + public void testProcessEvent_givenCREATE_DISTRICT_Event_shouldProcessEvent() throws JsonProcessingException { + final var request = TestUtils.createDistrict(); + final var event = TestUtils.createEvent(EventType.CREATE_DISTRICT.toString(), request, this.replicationTestUtils.getEventRepository()); + this.districtCreatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("PROCESSED", result.get().getEventStatus()); + } else { + fail("CREATE_DISTRICT failed to process"); + } + } + + @Test + public void testProcessEvent_givenCREATE_DISTRICT_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { + final String ERROR_MSG = "Test Exception"; + doThrow(new ServiceException(ERROR_MSG)).when(districtServiceMock).updateDistrictCache(anyString()); + final var request = TestUtils.createDistrict(); + final var event = TestUtils.createEvent(EventType.CREATE_DISTRICT.toString(), request, this.replicationTestUtils.getEventRepository()); + this.districtCreatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); + } else { + fail("CREATE_DISTRICT failed to process"); + } + } +} diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedServiceTest.java index e5ef5b88..bde45b9b 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactCreatedServiceTest.java @@ -1,17 +1,26 @@ package ca.bc.gov.educ.api.trax.service; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; import ca.bc.gov.educ.api.trax.support.TestUtils; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; public class SchoolContactCreatedServiceTest extends BaseReplicationServiceTest { @Autowired private SchoolContactCreatedService schoolContactCreatedService; + @MockBean + private SchoolService schoolServiceMock; + @Test public void testProcessEvent_givenCREATE_SCHOOL_CONTACT_Event_shouldProcessEvent() throws JsonProcessingException { final var request = TestUtils.createSchoolContact(); @@ -25,4 +34,19 @@ public void testProcessEvent_givenCREATE_SCHOOL_CONTACT_Event_shouldProcessEvent } } + @Test + public void testProcessEvent_givenCREATE_SCHOOL_CONTACT_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { + final String ERROR_MSG = "Test Exception"; + doThrow(new ServiceException(ERROR_MSG)).when(schoolServiceMock).updateSchoolCache(anyString()); + final var request = TestUtils.createSchoolContact(); + final var event = TestUtils.createEvent("CREATE_SCHOOL_CONTACT", request, this.replicationTestUtils.getEventRepository()); + this.schoolContactCreatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); + } else { + fail("CREATE_SCHOOL_CONTACT failed to process"); + } + } + } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedServiceTest.java index f00dbb34..0542f2c6 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactDeletedServiceTest.java @@ -1,17 +1,26 @@ package ca.bc.gov.educ.api.trax.service; +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; import ca.bc.gov.educ.api.trax.support.TestUtils; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; public class SchoolContactDeletedServiceTest extends BaseReplicationServiceTest { @Autowired private SchoolContactDeletedService schoolContactDeletedService; + @MockBean + private SchoolService schoolServiceMock; + @Test public void testProcessEvent_givenDELETE_SCHOOL_CONTACT_Event_shouldProcessEvent() throws JsonProcessingException { final var request = TestUtils.createSchoolContact(); @@ -25,4 +34,19 @@ public void testProcessEvent_givenDELETE_SCHOOL_CONTACT_Event_shouldProcessEvent } } + @Test + public void testProcessEvent_givenDELETE_SCHOOL_CONTACT_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { + final String ERROR_MSG = "Test Exception"; + doThrow(new ServiceException(ERROR_MSG)).when(schoolServiceMock).updateSchoolCache(anyString()); + final var request = TestUtils.createSchoolContact(); + final var event = TestUtils.createEvent("DELETE_SCHOOL_CONTACT", request, this.replicationTestUtils.getEventRepository()); + this.schoolContactDeletedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); + if(result.isPresent()){ + Assert.assertEquals("DB_COMMITTED", result.get().getEventStatus()); + } else { + fail("DELETE_SCHOOL_CONTACT failed to process"); + } + } + } From 49e23f5d1412f9853cd00b6686e9df01f1448e91 Mon Sep 17 00:00:00 2001 From: "chris.ditcher" Date: Wed, 14 Aug 2024 15:24:30 -0700 Subject: [PATCH 05/16] Removed sleep. Not necessary after testing. --- .../ca/bc/gov/educ/api/trax/service/SchoolMovedService.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java index a338dbca..41094fc3 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java @@ -26,14 +26,10 @@ public SchoolMovedService(SchoolService schoolService) { public void processEvent(final MoveSchoolData moveSchoolData, EventEntity eventEntity) { log.debug("Processing School Moved"); try{ - // school move sometimes not fully processed, sleep 1 second - Thread.sleep(1000); schoolService.updateSchoolCache(Arrays.asList(moveSchoolData.getFromSchoolId(), moveSchoolData.getToSchool().getSchoolId())); this.updateEventWithHistory(eventEntity); } catch (ServiceException e) { log.error(e.getMessage()); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); } } From d6efa14514057506ec317bc83cb2a8430b8bf33a Mon Sep 17 00:00:00 2001 From: Chris Ditcher Date: Tue, 20 Aug 2024 10:54:01 -0700 Subject: [PATCH 06/16] GRAD2-2869 (#347) * Added an extra type hierarchy for school events for filtering on transcript eligibility * Added some unit tests to support history filtering on school created events * Added testing for school updated * Added testing and code for move school filtering * Added rules for UPDATE_SCHOOL_CONTACT * Removed old update event with history method. * Removed redundant method from abstract class. --------- Co-authored-by: chris.ditcher --- .../repository/EventHistoryRepository.java | 5 +++ .../AuthorityContactCreatedService.java | 2 +- .../AuthorityContactDeletedService.java | 2 +- .../AuthorityContactUpdatedService.java | 2 +- .../DistrictContactEventBaseService.java | 2 +- .../DistrictContactUpdatedService.java | 2 +- .../trax/service/DistrictCreatedService.java | 2 +- .../trax/service/DistrictUpdatedService.java | 2 +- .../api/trax/service/EventBaseService.java | 24 ++++---------- .../SchoolContactEventBaseService.java | 2 +- .../service/SchoolContactUpdatedService.java | 12 +++---- .../trax/service/SchoolCreatedService.java | 8 ++--- .../trax/service/SchoolEventBaseService.java | 27 ++++++++++++++++ .../api/trax/service/SchoolMovedService.java | 13 +++++--- .../trax/service/institute/SchoolService.java | 8 +++++ .../SchoolContactUpdatedServiceTest.java | 30 +++++++++++++++++- .../service/SchoolCreatedServiceTest.java | 19 ++++++++++++ .../trax/service/SchoolMovedServiceTest.java | 31 +++++++++++++++++++ .../service/SchoolUpdatedServiceTest.java | 19 ++++++++++++ 19 files changed, 169 insertions(+), 43 deletions(-) create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolEventBaseService.java diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java index ea6cc59a..fe011d1d 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java @@ -3,8 +3,13 @@ import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; + +import java.util.Optional; import java.util.UUID; @Repository public interface EventHistoryRepository extends JpaRepository { + + Optional findByEvent_ReplicationEventId(UUID replicationEventId); + } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactCreatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactCreatedService.java index cc2b68b8..9d9a366e 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactCreatedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/AuthorityContactCreatedService.java @@ -13,7 +13,7 @@ public class AuthorityContactCreatedService extends EventBaseService implements EventService { @@ -17,28 +16,17 @@ public abstract class EventBaseService implements EventService { @Autowired protected EventHistoryRepository eventHistoryRepository; - protected void updateEvent(final EventEntity eventEntity) { + protected void updateEvent(final EventEntity eventEntity, boolean includeHistory) { this.eventRepository.findByEventId(eventEntity.getEventId()).ifPresent(existingEvent -> { existingEvent.setEventStatus(EventStatus.PROCESSED.toString()); existingEvent.setUpdateDate(LocalDateTime.now()); this.eventRepository.save(existingEvent); + if(includeHistory){ + EventHistoryEntity eventHistoryEntity = new EventHistoryEntity(); + eventHistoryEntity.setEvent(existingEvent); + this.eventHistoryRepository.save(eventHistoryEntity); + } }); } - /** - * Adds the event to the EventHistory table. Implementing classes may want to - * use this method if they are interested in history tracking - * @param eventEntity the event entity - */ - protected void updateEventWithHistory(final EventEntity eventEntity) { - this.updateEvent(eventEntity); - Optional savedEvent = this.eventRepository.findByEventId(eventEntity.getEventId()); - if(savedEvent.isPresent()){ - EventHistoryEntity eventHistoryEntity = new EventHistoryEntity(); - eventHistoryEntity.setEvent(savedEvent.get()); - this.eventHistoryRepository.save(eventHistoryEntity); - } - - } - } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactEventBaseService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactEventBaseService.java index 5b12ee70..cb1e3e7f 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactEventBaseService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactEventBaseService.java @@ -23,7 +23,7 @@ public void processEvent(SchoolContact schoolContact, EventEntity eventEntity) { log.debug("Processing {}", eventEntity.getEventType()); try { schoolService.updateSchoolCache(schoolContact.getSchoolId()); - this.updateEvent(eventEntity); + this.updateEvent(eventEntity, false); } catch (ServiceException e) { // do not mark eventEntity as processed log.error(e.getMessage()); diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedService.java index cbfe134c..5f2bea7a 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedService.java @@ -3,6 +3,7 @@ import ca.bc.gov.educ.api.trax.constant.EventType; import ca.bc.gov.educ.api.trax.exception.ServiceException; import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; +import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -11,21 +12,20 @@ @Service @Slf4j -public class SchoolContactUpdatedService extends EventBaseService { - - SchoolService schoolService; +public class SchoolContactUpdatedService extends SchoolEventBaseService { @Autowired public SchoolContactUpdatedService(SchoolService schoolService) { - this.schoolService = schoolService; + super(schoolService); } @Override public void processEvent(final SchoolContact schoolContact, EventEntity eventEntity) { log.debug("Processing School Contact Updated"); try{ - schoolService.updateSchoolCache(schoolContact.getSchoolId()); - this.updateEventWithHistory(eventEntity); + SchoolDetail schoolDetail = this.schoolService.getSchoolDetailByIdFromInstituteApi(schoolContact.getSchoolId()); + schoolService.updateSchoolCache(schoolDetail); + this.updateEvent(eventEntity, schoolDetail.isCanIssueTranscripts()); } catch (ServiceException e) { // do not mark eventEntity as processed log.error(e.getMessage()); diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedService.java index a07e668a..888b9262 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedService.java @@ -11,13 +11,11 @@ @Service @Slf4j -public class SchoolCreatedService extends EventBaseService { - - SchoolService schoolService; +public class SchoolCreatedService extends SchoolEventBaseService { @Autowired public SchoolCreatedService(SchoolService schoolService) { - this.schoolService = schoolService; + super(schoolService); } @Override @@ -25,7 +23,7 @@ public void processEvent(final School school, EventEntity eventEntity) { log.debug("Processing School Created"); try{ schoolService.updateSchoolCache(school.getSchoolId()); - this.updateEventWithHistory(eventEntity); + this.updateEvent(eventEntity, this.shouldCreateHistory(school)); } catch (ServiceException e) { log.error(e.getMessage()); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolEventBaseService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolEventBaseService.java new file mode 100644 index 00000000..d9a04254 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolEventBaseService.java @@ -0,0 +1,27 @@ +package ca.bc.gov.educ.api.trax.service; + +import ca.bc.gov.educ.api.trax.model.dto.institute.School; +import ca.bc.gov.educ.api.trax.service.institute.SchoolService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public abstract class SchoolEventBaseService extends EventBaseService { + + protected SchoolService schoolService; + + protected SchoolEventBaseService(SchoolService schoolService) { + this.schoolService = schoolService; + } + + /** + * Acts as a filter. Add rules here for if a school event should be added to history table + * @param school a school object + * @return true if the school object passes eligibility for history table + */ + protected boolean shouldCreateHistory(School school) { + // currently only schools that can issue transcripts qualify + return school.isCanIssueTranscripts(); + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java index 41094fc3..3890ba81 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/SchoolMovedService.java @@ -3,6 +3,7 @@ import ca.bc.gov.educ.api.trax.constant.EventType; import ca.bc.gov.educ.api.trax.exception.ServiceException; import ca.bc.gov.educ.api.trax.model.dto.institute.MoveSchoolData; +import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import ca.bc.gov.educ.api.trax.service.institute.SchoolService; import lombok.extern.slf4j.Slf4j; @@ -13,13 +14,11 @@ @Service @Slf4j -public class SchoolMovedService extends EventBaseService { - - SchoolService schoolService; +public class SchoolMovedService extends SchoolEventBaseService { @Autowired public SchoolMovedService(SchoolService schoolService) { - this.schoolService = schoolService; + super(schoolService); } @Override @@ -27,7 +26,11 @@ public void processEvent(final MoveSchoolData moveSchoolData, EventEntity eventE log.debug("Processing School Moved"); try{ schoolService.updateSchoolCache(Arrays.asList(moveSchoolData.getFromSchoolId(), moveSchoolData.getToSchool().getSchoolId())); - this.updateEventWithHistory(eventEntity); + // have to check event history eligibility on from and to schools for move. + // if one can issue transcripts, set history eligibility + SchoolDetail schoolDetail = this.schoolService.getSchoolDetailByIdFromInstituteApi(moveSchoolData.getFromSchoolId()); + boolean shouldCreateHistory = (schoolDetail.isCanIssueTranscripts() || this.shouldCreateHistory(moveSchoolData.getToSchool())); + this.updateEvent(eventEntity, shouldCreateHistory); } catch (ServiceException e) { log.error(e.getMessage()); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java index bc2d8197..44ff99e2 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java @@ -130,6 +130,14 @@ public void updateSchoolCache(String schoolId) throws ServiceException { schoolDetailRedisRepository.save(schoolDetailTransformer.transformToEntity(schoolDetail)); } + /** + * Updates the school and school details in the cache + * @param schoolDetail the school detail object + */ + public void updateSchoolCache(SchoolDetail schoolDetail) throws ServiceException { + schoolDetailRedisRepository.save(schoolDetailTransformer.transformToEntity(schoolDetail)); + } + /** * Updates the school and school details in the cache * based on schoolId diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedServiceTest.java index d89dac5e..1cb68d50 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolContactUpdatedServiceTest.java @@ -1,5 +1,6 @@ package ca.bc.gov.educ.api.trax.service; +import ca.bc.gov.educ.api.trax.constant.EventType; import ca.bc.gov.educ.api.trax.exception.ServiceException; import ca.bc.gov.educ.api.trax.service.institute.SchoolService; import ca.bc.gov.educ.api.trax.support.TestUtils; @@ -12,6 +13,7 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; public class SchoolContactUpdatedServiceTest extends BaseReplicationServiceTest { @@ -25,6 +27,9 @@ public class SchoolContactUpdatedServiceTest extends BaseReplicationServiceTest public void testProcessEvent_givenUPDATE_SCHOOL_CONTACT_Event_shouldProcessEvent() throws JsonProcessingException { final var request = TestUtils.createSchoolContact(); final var event = TestUtils.createEvent("UPDATE_SCHOOL_CONTACT", request, this.replicationTestUtils.getEventRepository()); + var schoolDetails = TestUtils.createSchoolDetail(); + schoolDetails.setSchoolId(request.getSchoolId()); + when(schoolServiceMock.getSchoolDetailByIdFromInstituteApi(request.getSchoolId())).thenReturn(schoolDetails); this.schoolContactUpdatedService.processEvent(request, event); var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); if(result.isPresent()){ @@ -37,9 +42,9 @@ public void testProcessEvent_givenUPDATE_SCHOOL_CONTACT_Event_shouldProcessEvent @Test public void testProcessEvent_givenUPDATE_SCHOOL_CONTACT_Event_ServiceUnavailable_triggerError() throws JsonProcessingException { final String ERROR_MSG = "Test Exception"; - doThrow(new ServiceException(ERROR_MSG)).when(schoolServiceMock).updateSchoolCache(anyString()); final var request = TestUtils.createSchoolContact(); final var event = TestUtils.createEvent("UPDATE_SCHOOL_CONTACT", request, this.replicationTestUtils.getEventRepository()); + doThrow(new ServiceException(ERROR_MSG)).when(schoolServiceMock).getSchoolDetailByIdFromInstituteApi(anyString()); this.schoolContactUpdatedService.processEvent(request, event); var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); if(result.isPresent()){ @@ -48,4 +53,27 @@ public void testProcessEvent_givenUPDATE_SCHOOL_CONTACT_Event_ServiceUnavailable fail("UPDATE_SCHOOL_CONTACT failed to process"); } } + + @Test + public void testProcessEvent_givenUPDATE_SCHOOL_CONTACT_EventWithPassingHistoryCriteria_shouldStoreInHistoryTable() throws JsonProcessingException { + final var request = TestUtils.createSchoolContact(); + final var event = TestUtils.createEvent(EventType.CREATE_SCHOOL_CONTACT.toString(), request, this.replicationTestUtils.getEventRepository()); + var schoolDetails = TestUtils.createSchoolDetail(); + when(schoolServiceMock.getSchoolDetailByIdFromInstituteApi(request.getSchoolId())).thenReturn(schoolDetails); + this.schoolContactUpdatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventHistoryRepository().findByEvent_ReplicationEventId(event.getReplicationEventId()); + Assert.assertTrue(result.isPresent()); + } + + @Test + public void testProcessEvent_givenUPDATE_SCHOOL_CONTACT_EventWithFailingHistoryCriteria_shouldNotStoreInHistoryTable() throws JsonProcessingException { + final var request = TestUtils.createSchoolContact(); + final var event = TestUtils.createEvent(EventType.CREATE_SCHOOL_CONTACT.toString(), request, this.replicationTestUtils.getEventRepository()); + var schoolDetails = TestUtils.createSchoolDetail(); + schoolDetails.setCanIssueTranscripts(false); + when(schoolServiceMock.getSchoolDetailByIdFromInstituteApi(request.getSchoolId())).thenReturn(schoolDetails); + this.schoolContactUpdatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventHistoryRepository().findByEvent_ReplicationEventId(event.getReplicationEventId()); + Assert.assertFalse(result.isPresent()); + } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedServiceTest.java index d8979264..7c83c6fc 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolCreatedServiceTest.java @@ -49,4 +49,23 @@ public void testProcessEvent_givenCREATE_SCHOOL_Event_ServiceUnavailable_trigger fail("CREATE_SCHOOL failed to process"); } } + + @Test + public void testProcessEvent_givenCREATE_SCHOOL_EventWithPassingHistoryCriteria_shouldStoreInHistoryTable() throws JsonProcessingException { + final var request = TestUtils.createSchool(); + final var event = TestUtils.createEvent(EventType.CREATE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + this.schoolCreatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventHistoryRepository().findByEvent_ReplicationEventId(event.getReplicationEventId()); + Assert.assertTrue(result.isPresent()); + } + + @Test + public void testProcessEvent_givenCREATE_SCHOOL_EventWithFailingHistoryCriteria_shouldNotStoreInHistoryTable() throws JsonProcessingException { + final var request = TestUtils.createSchool(); + request.setCanIssueTranscripts(false); + final var event = TestUtils.createEvent(EventType.CREATE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + this.schoolCreatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventHistoryRepository().findByEvent_ReplicationEventId(event.getReplicationEventId()); + Assert.assertFalse(result.isPresent()); + } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolMovedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolMovedServiceTest.java index bf76d956..63491130 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolMovedServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolMovedServiceTest.java @@ -13,6 +13,7 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; public class SchoolMovedServiceTest extends BaseReplicationServiceTest { @@ -26,6 +27,9 @@ public class SchoolMovedServiceTest extends BaseReplicationServiceTest { public void testProcessEvent_givenMOVE_SCHOOL_Event_shouldProcessEvent() throws JsonProcessingException { final var request = TestUtils.createMoveSchoolData(); final var event = TestUtils.createEvent(EventType.MOVE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + var schoolDetail = TestUtils.createSchoolDetail(); + schoolDetail.setSchoolId(request.getFromSchoolId()); + when(schoolServiceMock.getSchoolDetailByIdFromInstituteApi(request.getFromSchoolId())).thenReturn(schoolDetail); this.schoolMovedService.processEvent(request, event); var result = this.replicationTestUtils.getEventRepository().findById(event.getReplicationEventId()); if(result.isPresent()){ @@ -49,4 +53,31 @@ public void testProcessEvent_givenMOVE_SCHOOL_Event_ServiceUnavailable_triggerEr fail("MOVE_SCHOOL failed to process"); } } + + @Test + public void testProcessEvent_givenMOVE_SCHOOL_EventWithPassingHistoryCriteria_shouldStoreInHistoryTable() throws JsonProcessingException { + final var request = TestUtils.createMoveSchoolData(); + final var event = TestUtils.createEvent(EventType.MOVE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + var schoolDetail = TestUtils.createSchoolDetail(); + schoolDetail.setSchoolId(request.getFromSchoolId()); + schoolDetail.setCanIssueTranscripts(false); + when(schoolServiceMock.getSchoolDetailByIdFromInstituteApi(request.getFromSchoolId())).thenReturn(schoolDetail); + this.schoolMovedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventHistoryRepository().findByEvent_ReplicationEventId(event.getReplicationEventId()); + Assert.assertTrue(result.isPresent()); + } + + @Test + public void testProcessEvent_givenMOVE_SCHOOL_EventWithFailingHistoryCriteria_shouldNotStoreInHistoryTable() throws JsonProcessingException { + final var request = TestUtils.createMoveSchoolData(); + request.getToSchool().setCanIssueTranscripts(false); + final var event = TestUtils.createEvent(EventType.MOVE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + var schoolDetail = TestUtils.createSchoolDetail(); + schoolDetail.setSchoolId(request.getFromSchoolId()); + schoolDetail.setCanIssueTranscripts(false); + when(schoolServiceMock.getSchoolDetailByIdFromInstituteApi(request.getFromSchoolId())).thenReturn(schoolDetail); + this.schoolMovedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventHistoryRepository().findByEvent_ReplicationEventId(event.getReplicationEventId()); + Assert.assertFalse(result.isPresent()); + } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedServiceTest.java index b6e76f90..13409c57 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/SchoolUpdatedServiceTest.java @@ -49,4 +49,23 @@ public void testProcessEvent_givenUPDATE_SCHOOL_Event_ServiceUnavailable_trigger fail("UPDATE_SCHOOL failed to process"); } } + + @Test + public void testProcessEvent_givenUPDATE_SCHOOL_EventWithPassingHistoryCriteria_shouldStoreInHistoryTable() throws JsonProcessingException { + final var request = TestUtils.createSchool(); + final var event = TestUtils.createEvent(EventType.UPDATE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + this.schoolUpdatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventHistoryRepository().findByEvent_ReplicationEventId(event.getReplicationEventId()); + Assert.assertTrue(result.isPresent()); + } + + @Test + public void testProcessEvent_givenUPDATE_SCHOOL_EventWithFailingHistoryCriteria_shouldNotStoreInHistoryTable() throws JsonProcessingException { + final var request = TestUtils.createSchool(); + request.setCanIssueTranscripts(false); + final var event = TestUtils.createEvent(EventType.UPDATE_SCHOOL.toString(), request, this.replicationTestUtils.getEventRepository()); + this.schoolUpdatedService.processEvent(request, event); + var result = this.replicationTestUtils.getEventHistoryRepository().findByEvent_ReplicationEventId(event.getReplicationEventId()); + Assert.assertFalse(result.isPresent()); + } } From 3a187cb1b0323cdc0a75dde9b0b49ac75c67b5bd Mon Sep 17 00:00:00 2001 From: "chris.ditcher" Date: Tue, 20 Aug 2024 11:20:08 -0700 Subject: [PATCH 07/16] Updated gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0d3181fb..7ded25d7 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,4 @@ build/ .vscode/ ### local dev ### -application-local.yaml +**/application-local.yaml From 3fadcce903e6992b544c5eb20c35f1620027c113 Mon Sep 17 00:00:00 2001 From: Kamal Mohammed Date: Fri, 23 Aug 2024 08:53:43 -0600 Subject: [PATCH 08/16] GRAD2-2638 - Institute - Create V2 endpoints for School data (#342) * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data * GRAD2-2638 - Institute - Create V2 endpoints for School data --------- Co-authored-by: Mohammed --- .../api/trax/controller/CodeController.java | 1 - .../trax/controller/DistrictController.java | 3 +- .../api/trax/controller/EdwController.java | 2 - .../api/trax/controller/PsiController.java | 2 - .../api/trax/controller/SchoolController.java | 4 +- .../trax/controller/TraxCommonController.java | 2 - .../controller/v2/DistrictController.java | 64 ++++ .../trax/controller/v2/SchoolController.java | 51 ++- .../trax/model/dto/institute/District.java | 2 +- .../model/dto/institute/DistrictAddress.java | 2 +- .../model/dto/institute/DistrictContact.java | 2 +- .../api/trax/model/dto/institute/Grade.java | 2 +- .../dto/institute/NeighborhoodLearning.java | 2 +- .../api/trax/model/dto/institute/Note.java | 2 +- .../api/trax/model/dto/institute/School.java | 2 +- .../model/dto/institute/SchoolAddress.java | 2 +- .../institute/DistrictTransformer.java | 1 - .../redis/DistrictRedisRepository.java | 2 + .../redis/SchoolDetailRedisRepository.java | 7 +- .../redis/SchoolRedisRepository.java | 2 + .../service/institute/DistrictService.java | 29 ++ .../trax/service/institute/SchoolService.java | 29 +- .../trax/util/EducGradTraxApiConstants.java | 2 +- .../controller/DistrictControllerTest.java | 65 +++- .../trax/controller/SchoolControllerTest.java | 95 ++++- .../institute/InstituteCodeServiceTest.java | 8 - .../InstituteDistrictServiceTest.java | 268 ++++++++++++-- .../institute/InstituteSchoolServiceTest.java | 345 ++++++++++++++++-- api/src/test/resources/application.yaml | 16 + 29 files changed, 912 insertions(+), 102 deletions(-) create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/DistrictController.java diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/CodeController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/CodeController.java index 4b7e2670..ee8c4410 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/CodeController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/CodeController.java @@ -4,7 +4,6 @@ import ca.bc.gov.educ.api.trax.model.dto.GradProvince; import ca.bc.gov.educ.api.trax.service.CodeService; import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; -import ca.bc.gov.educ.api.trax.util.GradValidation; import ca.bc.gov.educ.api.trax.util.PermissionsConstants; import ca.bc.gov.educ.api.trax.util.ResponseHelper; import io.swagger.v3.oas.annotations.OpenAPIDefinition; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/DistrictController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/DistrictController.java index 4fe63774..a3425cd0 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/DistrictController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/DistrictController.java @@ -3,7 +3,6 @@ import ca.bc.gov.educ.api.trax.model.dto.District; import ca.bc.gov.educ.api.trax.service.DistrictService; import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; -import ca.bc.gov.educ.api.trax.util.GradValidation; import ca.bc.gov.educ.api.trax.util.PermissionsConstants; import ca.bc.gov.educ.api.trax.util.ResponseHelper; import io.swagger.v3.oas.annotations.OpenAPIDefinition; @@ -48,7 +47,7 @@ public ResponseEntity getDistrictDetails(@PathVariable String distCode @GetMapping(EducGradTraxApiConstants.GRAD_DISTRICT_URL_MAPPING_V1 + EducGradTraxApiConstants.GET_DISTRICTS_BY_SCHOOL_CATEGORY_MAPPING) @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) - @Operation(summary = "Check school existence by Mincode", description = "Check school existence by Mincode", tags = { "School" }) + @Operation(summary = "Get Districts by SchoolCategory", description = "Get Districts by SchoolCategory", tags = { "School" }) @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "204", description = "NO CONTENT")}) public ResponseEntity> getDistrictBySchoolCategory(@RequestParam(required = false) String schoolCategory) { diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/EdwController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/EdwController.java index 30552bf1..03293ca9 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/EdwController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/EdwController.java @@ -13,8 +13,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/PsiController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/PsiController.java index 699a331d..59ece699 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/PsiController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/PsiController.java @@ -13,8 +13,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/SchoolController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/SchoolController.java index 477036c6..7c6bc2af 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/SchoolController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/SchoolController.java @@ -78,7 +78,7 @@ public ResponseEntity> getAllCommonSchool() { @GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_URL_MAPPING_V1 + EducGradTraxApiConstants.GET_COMMON_SCHOOL_BY_CODE_MAPPING) @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) - @Operation(summary = "Find a Common School by Mincode", description = "Get a Common School by Mincode", tags = { "School" }) + @Operation(summary = "Find a Common School by Mincode", description = "Find a Common School by Mincode", tags = { "School" }) @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "NOT FOUND")}) public ResponseEntity getCommonSchool(@PathVariable String minCode) { @@ -114,7 +114,7 @@ public ResponseEntity checkSchoolExists(@PathVariable String minCode) { @GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_URL_MAPPING_V1 + EducGradTraxApiConstants.GET_SCHOOLS_BY_SCHOOL_CATEGORY_MAPPING) @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) - @Operation(summary = "Check school existence by Mincode", description = "Check school existence by Mincode", tags = { "School" }) + @Operation(summary = "Get schools by SchoolCategory", description = "Get schools by SchoolCategory", tags = { "School" }) @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "204", description = "NO CONTENT")}) public ResponseEntity> getSchoolsBySchoolCategory(@RequestParam(required = false) String schoolCategory, @RequestHeader(name="Authorization") String accessToken) { diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/TraxCommonController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/TraxCommonController.java index 0361a99b..d6e9466b 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/TraxCommonController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/TraxCommonController.java @@ -9,8 +9,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/DistrictController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/DistrictController.java new file mode 100644 index 00000000..06ee7d2a --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/DistrictController.java @@ -0,0 +1,64 @@ +package ca.bc.gov.educ.api.trax.controller.v2; + +import ca.bc.gov.educ.api.trax.model.dto.institute.District; +import ca.bc.gov.educ.api.trax.service.institute.DistrictService; +import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; +import ca.bc.gov.educ.api.trax.util.GradValidation; +import ca.bc.gov.educ.api.trax.util.PermissionsConstants; +import ca.bc.gov.educ.api.trax.util.ResponseHelper; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@CrossOrigin +@RestController("districtControllerV2") +@Slf4j +@OpenAPIDefinition(info = @Info(title = "API for School Data.", description = "This Read API is for Reading school data.", version = "2"), + security = {@SecurityRequirement(name = "OAUTH2", scopes = {"READ_GRAD_SCHOOL_DATA"})}) +public class DistrictController { + + DistrictService districtService; + GradValidation validation; + ResponseHelper response; + + @Autowired + public DistrictController(DistrictService districtService, GradValidation validation, ResponseHelper response) { + this.districtService = districtService; + this.validation = validation; + this.response = response; + } + + @GetMapping(EducGradTraxApiConstants.GRAD_DISTRICT_URL_MAPPING_V2 + EducGradTraxApiConstants.GET_DISTRICT_BY_DISTNO_MAPPING) + @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) + @Operation(summary = "Find a District by District Number V2", description = "Get District by District Number V2", tags = { "District" }) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) + public ResponseEntity getDistrictDetailsByDistNo(@PathVariable String distNo) { + if(distNo.length() <=3) { + District distResponse = districtService.getDistrictByDistNoFromRedisCache(distNo); + if (distResponse != null) { + return response.GET(distResponse); + } + } + return null; + } + + @GetMapping(EducGradTraxApiConstants.GRAD_DISTRICT_URL_MAPPING_V2 + EducGradTraxApiConstants.GET_DISTRICTS_BY_SCHOOL_CATEGORY_MAPPING) + @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) + @Operation(summary = "Get District by school category code V2", description = "Get District by school category code V2", tags = { "School" }) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "204", description = "NO CONTENT")}) + public ResponseEntity> getDistrictsBySchoolCategoryCode(@RequestParam(required = false) String schoolCategoryCode) { + return response.GET(districtService.getDistrictsBySchoolCategoryCode(schoolCategoryCode)); + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java index c5c501f7..5b6a9c3b 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java @@ -15,13 +15,14 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; @CrossOrigin -@RestController("SchoolControllerV2") +@RestController("schoolControllerV2") @Slf4j @OpenAPIDefinition(info = @Info(title = "API for School Data.", description = "This Read API is for Reading school data from Redis Cache.", version = "2"), security = {@SecurityRequirement(name = "OAUTH2", scopes = {"READ_GRAD_SCHOOL_DATA"})}) @@ -47,6 +48,21 @@ public List getAllSchools() { return schoolService.getSchoolsFromRedisCache(); } + @GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_URL_MAPPING_V2 + EducGradTraxApiConstants.GET_SCHOOL_BY_CODE_MAPPING) + @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) + @Operation(summary = "Find a School by Mincode from cache", description = "Get a School by Mincode from cache", tags = { "School" }) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "204", description = "NO CONTENT")}) + public ResponseEntity getSchoolByMincode(@PathVariable String minCode) { + log.debug("getSchoolByMincode : "); + School schoolResponse = schoolService.getSchoolByMincodeFromRedisCache(minCode); + if(schoolResponse != null) { + return response.GET(schoolResponse); + }else { + return response.NOT_FOUND(); + } + } + @GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_DETAIL_URL_MAPPING_V2) @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) @Operation(summary = "Find All School details from Cache", description = "Get All School details from Cache", tags = { "School" }) @@ -56,5 +72,38 @@ public List getAllSchoolDetails() { return schoolService.getSchoolDetailsFromRedisCache(); } + @GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_URL_MAPPING_V2 + EducGradTraxApiConstants.CHECK_SCHOOL_BY_CODE_MAPPING) + @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) + @Operation(summary = "Check school existence by Mincode V2", description = "Check school existence by Mincode V2", tags = { "School" }) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "204", description = "NO CONTENT")}) + public Boolean checkIfSchoolExists(@PathVariable String minCode) { + return schoolService.checkIfSchoolExists(minCode); + } + + @GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_URL_MAPPING_V2 + EducGradTraxApiConstants.GET_SCHOOLS_BY_SCHOOL_CATEGORY_MAPPING) + @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) + @Operation(summary = "Get Schools by School Category Code V2", description = "Get Schools by School Category Code V2", tags = { "School" }) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "204", description = "NO CONTENT")}) + public ResponseEntity> getSchoolsBySchoolCategory(@RequestParam(required = false) String schoolCategoryCode) { + return response.GET(schoolService.getSchoolDetailsBySchoolCategoryCode(schoolCategoryCode)); + } + + @GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_DETAIL_URL_MAPPING_V2 + EducGradTraxApiConstants.GET_SCHOOL_BY_CODE_MAPPING) + @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) + @Operation(summary = "Find School Details by Mincode from cache", description = "Get School Details by Mincode from cache", tags = { "School" }) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "204", description = "NO CONTENT")}) + public ResponseEntity getSchoolDetailsByMincode(@PathVariable String minCode) { + log.debug("getSchoolDetails : "); + SchoolDetail schoolDetailResponse = schoolService.getSchoolDetailByMincodeFromRedisCache(minCode); + if(schoolDetailResponse != null) { + return response.GET(schoolDetailResponse); + }else { + return response.NOT_FOUND(); + } + } + } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/District.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/District.java index a7840b8f..e320bdd1 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/District.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/District.java @@ -11,7 +11,7 @@ @Data @EqualsAndHashCode(callSuper = true) -@Component("InstituteDistrict") +@Component("instituteDistrict") @NoArgsConstructor @AllArgsConstructor public class District extends BaseModel { diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/DistrictAddress.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/DistrictAddress.java index c350e60f..0663337d 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/DistrictAddress.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/DistrictAddress.java @@ -7,7 +7,7 @@ @Data @EqualsAndHashCode(callSuper = true) -@Component("DistrictAddress") +@Component("districtAddress") public class DistrictAddress extends BaseModel { private String districtAddressId; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/DistrictContact.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/DistrictContact.java index 7e6e5330..c74ed9b6 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/DistrictContact.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/DistrictContact.java @@ -12,7 +12,7 @@ @Data @EqualsAndHashCode(callSuper = true) -@Component("DistrictContact") +@Component("districtContact") public class DistrictContact extends BaseModel { private String districtId; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/Grade.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/Grade.java index b7b3e8dd..897cbece 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/Grade.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/Grade.java @@ -7,7 +7,7 @@ @Data @EqualsAndHashCode(callSuper = true) -@Component("Grade") +@Component("grade") public class Grade extends BaseModel { private String schoolGradeId; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/NeighborhoodLearning.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/NeighborhoodLearning.java index e14df402..11350fff 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/NeighborhoodLearning.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/NeighborhoodLearning.java @@ -7,7 +7,7 @@ @Data @EqualsAndHashCode(callSuper = true) -@Component("NeighborhoodLearning") +@Component("neighborhoodLearning") public class NeighborhoodLearning extends BaseModel { private String neighborhoodLearningId; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/Note.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/Note.java index 19960202..53a9b76d 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/Note.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/Note.java @@ -7,7 +7,7 @@ @Data @EqualsAndHashCode(callSuper = true) -@Component("Note") +@Component("note") public class Note extends BaseModel { private String noteId; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/School.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/School.java index 245d0eef..86d3cdda 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/School.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/School.java @@ -8,7 +8,7 @@ @Data @EqualsAndHashCode(callSuper = true) -@Component("InstituteSchool") +@Component("instituteSchool") @JsonIgnoreProperties(ignoreUnknown = true) public class School extends BaseModel { diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/SchoolAddress.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/SchoolAddress.java index ae01421b..a47feadd 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/SchoolAddress.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/SchoolAddress.java @@ -7,7 +7,7 @@ @Data @EqualsAndHashCode(callSuper = true) -@Component("SchoolAddress") +@Component("schoolAddress") public class SchoolAddress extends BaseModel { private String SchoolAddressId; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/DistrictTransformer.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/DistrictTransformer.java index f3f49056..2aa3f811 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/DistrictTransformer.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/DistrictTransformer.java @@ -2,7 +2,6 @@ import ca.bc.gov.educ.api.trax.model.dto.institute.District; import ca.bc.gov.educ.api.trax.model.entity.institute.DistrictEntity; -import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolDetailEntity; import org.modelmapper.ModelMapper; import org.modelmapper.TypeToken; import org.springframework.beans.factory.annotation.Autowired; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/DistrictRedisRepository.java b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/DistrictRedisRepository.java index b0a9d1e4..5b42704c 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/DistrictRedisRepository.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/DistrictRedisRepository.java @@ -7,4 +7,6 @@ @Repository public interface DistrictRedisRepository extends CrudRepository { String HASH_KEY = "District"; + + DistrictEntity findByDistrictNumber(String districtNumber); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/SchoolDetailRedisRepository.java b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/SchoolDetailRedisRepository.java index 9bc3cc86..caa1c721 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/SchoolDetailRedisRepository.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/SchoolDetailRedisRepository.java @@ -1,11 +1,16 @@ package ca.bc.gov.educ.api.trax.repository.redis; import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolDetailEntity; -import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolEntity; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface SchoolDetailRedisRepository extends CrudRepository { String HASH_KEY = "SchoolDetail"; + + List findBySchoolCategoryCode(String schoolCategoryCode); + + SchoolDetailEntity findByMincode(String mincode); } \ No newline at end of file diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/SchoolRedisRepository.java b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/SchoolRedisRepository.java index 4df623d3..0e21d486 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/SchoolRedisRepository.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/SchoolRedisRepository.java @@ -7,4 +7,6 @@ @Repository public interface SchoolRedisRepository extends CrudRepository { String HASH_KEY = "School"; + + SchoolEntity findByMincode(String mincode); } \ No newline at end of file diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/DistrictService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/DistrictService.java index 1d528dcd..dc4586ac 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/DistrictService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/DistrictService.java @@ -3,6 +3,7 @@ import ca.bc.gov.educ.api.trax.constant.CacheKey; import ca.bc.gov.educ.api.trax.exception.ServiceException; import ca.bc.gov.educ.api.trax.model.dto.institute.District; +import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; import ca.bc.gov.educ.api.trax.model.entity.institute.DistrictEntity; import ca.bc.gov.educ.api.trax.model.transformer.institute.DistrictTransformer; import ca.bc.gov.educ.api.trax.repository.redis.DistrictRedisRepository; @@ -15,6 +16,7 @@ import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; +import java.util.ArrayList; import java.util.List; @Slf4j @@ -33,6 +35,8 @@ public class DistrictService { @Autowired ServiceHelper serviceHelper; @Autowired + SchoolService schoolService; + @Autowired RESTService restService; public List getDistrictsFromInstituteApi() { @@ -64,6 +68,31 @@ public void initializeDistrictCache(boolean force) { serviceHelper.initializeCache(force, CacheKey.DISTRICT_CACHE, this); } + public District getDistrictByDistNoFromRedisCache(String districtNumber) { + log.debug("**** Getting district by district no. from Redis Cache."); + return districtTransformer.transformToDTO(districtRedisRepository.findByDistrictNumber(districtNumber)); + } + + public District getDistrictByIdFromRedisCache(String districtId) { + log.debug("**** Getting district by ID from Redis Cache."); + return districtTransformer.transformToDTO(districtRedisRepository.findById(districtId)); + } + + public List getDistrictsBySchoolCategoryCode(String schoolCategoryCode) { + List schoolDetails; + + if (schoolCategoryCode.isBlank() || schoolCategoryCode.isEmpty()) + schoolDetails = schoolService.getSchoolDetailsFromRedisCache(); + else + schoolDetails = schoolService.getSchoolDetailsBySchoolCategoryCode(schoolCategoryCode); + + List districts = new ArrayList<>(); + for (SchoolDetail schoolDetail : schoolDetails) { + districts.add(getDistrictByIdFromRedisCache(schoolDetail.getDistrictId())); + } + return districts; + } + /** * Updates the district details in the cache * based on schoolId diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java index 44ff99e2..5d775f73 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java @@ -69,6 +69,16 @@ public List getSchoolsFromRedisCache() { return schoolTransformer.transformToDTO(schoolRedisRepository.findAll()); } + public School getSchoolByMincodeFromRedisCache(String mincode) { + log.debug("Get School by Mincode from Redis Cache"); + return schoolTransformer.transformToDTO(schoolRedisRepository.findByMincode(mincode)); + } + + public boolean checkIfSchoolExists(String minCode) { + SchoolEntity schoolEntity = schoolRedisRepository.findByMincode(minCode); + return schoolEntity != null; + } + public void initializeSchoolCache(boolean force) { serviceHelper.initializeCache(force, CacheKey.SCHOOL_CACHE, this); } @@ -90,13 +100,9 @@ public SchoolDetail getSchoolDetailByIdFromInstituteApi(String schoolId) { public List getSchoolDetailsFromInstituteApi() { List schools = getSchoolsFromRedisCache(); - List schoolDetails = new ArrayList(); - + List schoolDetails = new ArrayList<>(); for (School s : schools) { - SchoolDetail sd = new SchoolDetail(); - - sd = getSchoolDetailByIdFromInstituteApi(s.getSchoolId()); - schoolDetails.add(sd); + schoolDetails.add(getSchoolDetailByIdFromInstituteApi(s.getSchoolId())); } return schoolDetails; } @@ -112,10 +118,21 @@ public List getSchoolDetailsFromRedisCache() { return schoolDetailTransformer.transformToDTO(schoolDetailRedisRepository.findAll()); } + public SchoolDetail getSchoolDetailByMincodeFromRedisCache(String mincode) { + log.debug("**** Getting school Details By Mincode from Redis Cache."); + return schoolDetailTransformer.transformToDTO(schoolDetailRedisRepository.findByMincode(mincode)); + } + public void initializeSchoolDetailCache(boolean force) { serviceHelper.initializeCache(force, CacheKey.SCHOOL_DETAIL_CACHE, this); } + public List getSchoolDetailsBySchoolCategoryCode(String schoolCategoryCode) { + + return schoolDetailTransformer.transformToDTO( + schoolDetailRedisRepository.findBySchoolCategoryCode(schoolCategoryCode)); + } + /** * Updates the school and school details in the cache * based on schoolId diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java index 36491b76..3dd06d0c 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java @@ -67,7 +67,7 @@ public class EducGradTraxApiConstants { public static final String GET_STUDENT_PSI_BY_CODE_MAPPING = "/student"; - public static final String GET_DISTRICT_BY_DISTNO_MAPPING = "/{distCode}"; + public static final String GET_DISTRICT_BY_DISTNO_MAPPING = "/{distNo}"; public static final String GET_DISTRICTS_BY_SCHOOL_CATEGORY_MAPPING = "/schoolCategories"; public static final String GET_SCHOOLS_BY_SCHOOL_CATEGORY_MAPPING = "/schoolCategories"; diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/controller/DistrictControllerTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/controller/DistrictControllerTest.java index d2338a94..8926ed5f 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/controller/DistrictControllerTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/controller/DistrictControllerTest.java @@ -1,9 +1,11 @@ package ca.bc.gov.educ.api.trax.controller; import ca.bc.gov.educ.api.trax.model.dto.District; +import ca.bc.gov.educ.api.trax.model.transformer.institute.DistrictTransformer; import ca.bc.gov.educ.api.trax.service.DistrictService; import ca.bc.gov.educ.api.trax.util.ResponseHelper; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -11,9 +13,13 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.ResponseEntity; +import java.util.ArrayList; import java.util.List; +import static org.mockito.Mockito.never; + @RunWith(MockitoJUnitRunner.class) @ExtendWith(MockitoExtension.class) @SuppressWarnings("rawtypes") @@ -21,6 +27,8 @@ public class DistrictControllerTest { @Mock private DistrictService districtService; + @Mock + private ca.bc.gov.educ.api.trax.service.institute.DistrictService districtServiceV2; @Mock ResponseHelper responseHelper; @@ -28,6 +36,11 @@ public class DistrictControllerTest { @InjectMocks private DistrictController districtController; + @InjectMocks + private ca.bc.gov.educ.api.trax.controller.v2.DistrictController districtControllerV2; + @Mock + DistrictTransformer districtTransformer; + @Test public void testGetSchoolDetails() { final District district = new District(); @@ -35,9 +48,10 @@ public void testGetSchoolDetails() { district.setDistrictName("Test School"); Mockito.when(districtService.getDistrictDetails("123")).thenReturn(district); - districtController.getDistrictDetails("123"); + Mockito.when(responseHelper.GET(district)).thenReturn(ResponseEntity.ok().body(district)); + ResponseEntity result = districtController.getDistrictDetails("123"); Mockito.verify(districtService).getDistrictDetails("123"); - + Assertions.assertEquals(district, (District) result.getBody()); } @Test @@ -49,6 +63,53 @@ public void testGetDistrictBySchoolCategoryCode() { Mockito.when(districtService.getDistrictBySchoolCategory("123")).thenReturn(List.of(district)); districtController.getDistrictBySchoolCategory("123"); Mockito.verify(districtService).getDistrictBySchoolCategory("123"); + } + + @Test + public void whenGetDistrictDetailsByDistNo_ReturnsDistrict() { + String distNo = "123"; + ca.bc.gov.educ.api.trax.model.dto.institute.District district = new ca.bc.gov.educ.api.trax.model.dto.institute.District(); + district.setDistrictId("123456"); + district.setDistrictNumber("123"); + district.setDistrictRegionCode("BC"); + + Mockito.when(districtServiceV2.getDistrictByDistNoFromRedisCache(distNo)).thenReturn(district); + Mockito.when(responseHelper.GET(district)).thenReturn(ResponseEntity.ok().body(district)); + ResponseEntity result = districtControllerV2.getDistrictDetailsByDistNo(distNo); + Mockito.verify(districtServiceV2).getDistrictByDistNoFromRedisCache(distNo); + Assertions.assertEquals(district, (ca.bc.gov.educ.api.trax.model.dto.institute.District) result.getBody()); + } + + @Test + public void whenGetDistrictDetailsByDistNo_ReturnNULL() { + String distNo = "1234"; + ca.bc.gov.educ.api.trax.model.dto.institute.District district = new ca.bc.gov.educ.api.trax.model.dto.institute.District(); + district.setDistrictId("123456"); + district.setDistrictNumber("12"); + district.setDistrictRegionCode("BC"); + + districtControllerV2.getDistrictDetailsByDistNo(distNo); + Mockito.verify(districtServiceV2, never()).getDistrictByDistNoFromRedisCache(distNo); + Assertions.assertEquals(null, districtControllerV2.getDistrictDetailsByDistNo(distNo)); + } + + @Test + public void whenGetDistrictsBySchoolCategoryCode_ReturnListOfDistricts() { + String schoolCategoryCode = "123"; + final List districts = new ArrayList<>(); + ca.bc.gov.educ.api.trax.model.dto.institute.District district = new ca.bc.gov.educ.api.trax.model.dto.institute.District(); + district.setDistrictId("123456"); + district.setDistrictNumber("123"); + district.setDistrictRegionCode("BC"); + districts.add(district); + district = new ca.bc.gov.educ.api.trax.model.dto.institute.District(); + district.setDistrictId("789012"); + district.setDistrictNumber("456"); + district.setDistrictRegionCode("BC"); + districts.add(district); + Mockito.when(districtServiceV2.getDistrictsBySchoolCategoryCode(schoolCategoryCode)).thenReturn(districts); + districtControllerV2.getDistrictsBySchoolCategoryCode(schoolCategoryCode); + Mockito.verify(districtServiceV2).getDistrictsBySchoolCategoryCode(schoolCategoryCode); } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/controller/SchoolControllerTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/controller/SchoolControllerTest.java index 0ad1211a..f21c28aa 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/controller/SchoolControllerTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/controller/SchoolControllerTest.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.function.Consumer; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -107,10 +108,21 @@ public void testGetSchoolsBySchoolCategoryCode() { } @Test - public void testCheckSchoolExists() { - Mockito.when(schoolService.existsSchool("1234567")).thenReturn(true); - schoolController.checkSchoolExists("1234567"); - Mockito.verify(schoolService).existsSchool("1234567"); + public void testCheckSchoolExists_expectTrue() { + String mincode = "1234567"; + Mockito.when(schoolServiceV2.checkIfSchoolExists(mincode)).thenReturn(true); + schoolControllerV2.checkIfSchoolExists(mincode); + Mockito.verify(schoolServiceV2).checkIfSchoolExists(mincode); + assertEquals(true, schoolServiceV2.checkIfSchoolExists(mincode)); + } + + @Test + public void testCheckSchoolExists_expectFalse() { + String mincode = "1234567"; + Mockito.when(schoolServiceV2.checkIfSchoolExists(mincode)).thenReturn(false); + schoolControllerV2.checkIfSchoolExists(mincode); + Mockito.verify(schoolServiceV2).checkIfSchoolExists(mincode); + assertEquals(false, schoolServiceV2.checkIfSchoolExists(mincode)); } @Test @@ -169,6 +181,33 @@ public void whenGetAllSchools_ReturnsListOfSchools() { Mockito.verify(schoolServiceV2).getSchoolsFromRedisCache(); } + @Test + public void whenGetSchoolByMincode_ReturnsSchool() { + String mincode = "12345678"; + ca.bc.gov.educ.api.trax.model.dto.institute.School school = new ca.bc.gov.educ.api.trax.model.dto.institute.School(); + school.setSchoolId("1234567"); + school.setDistrictId("9876543"); + school.setMincode(mincode); + + Mockito.when(schoolServiceV2.getSchoolByMincodeFromRedisCache(mincode)).thenReturn(school); + schoolControllerV2.getSchoolByMincode(mincode); + Mockito.verify(schoolServiceV2).getSchoolByMincodeFromRedisCache(mincode); + } + + @Test + public void whenGetSchoolByMincode_Return_NOT_FOUND() { + String mincode = "12345678"; + ca.bc.gov.educ.api.trax.model.dto.institute.School school = new ca.bc.gov.educ.api.trax.model.dto.institute.School(); + school.setSchoolId("1234567"); + school.setDistrictId("9876543"); + school.setMincode(mincode); + + Mockito.when(schoolServiceV2.getSchoolByMincodeFromRedisCache(mincode)).thenReturn(null); + schoolControllerV2.getSchoolByMincode(mincode); + Mockito.verify(schoolServiceV2).getSchoolByMincodeFromRedisCache(mincode); + assertEquals(responseHelper.NOT_FOUND(), schoolControllerV2.getSchoolByMincode(mincode)); + } + @Test public void whenGetAllSchoolDetails_ReturnsListOfSchoolDetails() { final List schoolDetails = new ArrayList<>(); @@ -182,7 +221,53 @@ public void whenGetAllSchoolDetails_ReturnsListOfSchoolDetails() { schoolDetails.add(schoolDetail); Mockito.when(schoolServiceV2.getSchoolDetailsFromRedisCache()).thenReturn(schoolDetails); - schoolControllerV2.getAllSchoolDetails(); + List sd = schoolControllerV2.getAllSchoolDetails(); Mockito.verify(schoolServiceV2).getSchoolDetailsFromRedisCache(); + assertEquals(schoolDetails, sd); + } + + @Test + public void whenGetSchoolsBySchoolCategory_ReturnListOfSchoolDetails() { + String schoolCategoryCode = "SCHL_CATG"; + final List schoolDetails = new ArrayList<>(); + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("1234567"); + schoolDetail.setDistrictId("9876543"); + schoolDetails.add(schoolDetail); + schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("1234567"); + schoolDetail.setDistrictId("9876543"); + schoolDetails.add(schoolDetail); + + Mockito.when(schoolServiceV2.getSchoolDetailsBySchoolCategoryCode(schoolCategoryCode)).thenReturn(schoolDetails); + schoolControllerV2.getSchoolsBySchoolCategory(schoolCategoryCode); + Mockito.verify(schoolServiceV2).getSchoolDetailsBySchoolCategoryCode(schoolCategoryCode); + } + + @Test + public void whenGetSchoolDetailsByMincode_ReturnsSchoolDetail() { + String mincode = "12345678"; + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("1234567"); + schoolDetail.setDistrictId("9876543"); + schoolDetail.setMincode(mincode); + + Mockito.when(schoolServiceV2.getSchoolDetailByMincodeFromRedisCache(mincode)).thenReturn(schoolDetail); + schoolControllerV2.getSchoolDetailsByMincode(mincode); + Mockito.verify(schoolServiceV2).getSchoolDetailByMincodeFromRedisCache(mincode); + } + + @Test + public void whenGetSchoolDetailsByMincode_Return_NOT_FOUND() { + String mincode = "12345678"; + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("1234567"); + schoolDetail.setDistrictId("9876543"); + schoolDetail.setMincode(mincode); + + Mockito.when(schoolServiceV2.getSchoolDetailByMincodeFromRedisCache(mincode)).thenReturn(null); + schoolControllerV2.getSchoolDetailsByMincode(mincode); + Mockito.verify(schoolServiceV2).getSchoolDetailByMincodeFromRedisCache(mincode); + assertEquals(responseHelper.NOT_FOUND(), schoolControllerV2.getSchoolDetailsByMincode(mincode)); } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteCodeServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteCodeServiceTest.java index 63f0ac50..c76c20f3 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteCodeServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteCodeServiceTest.java @@ -6,7 +6,6 @@ import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; import ca.bc.gov.educ.api.trax.model.dto.ResponseObj; -import ca.bc.gov.educ.api.trax.model.dto.institute.District; import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolCategoryCode; import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolFundingGroupCode; import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolCategoryCodeEntity; @@ -153,7 +152,6 @@ public void whenGetSchoolCategoryCodesFromInstituteApi_returnsListOfSchoolCatego .thenReturn(schoolCategoryCodes); List result = codeService.getSchoolCategoryCodesFromInstituteApi(); - //assertThat(result).hasSize(1); } @Test @@ -187,8 +185,6 @@ public void whenGetSchoolFundingGroupCodesFromInstituteApi_returnsListOfSchoolFu .thenReturn(schoolFundingGroupCodes); List result = codeService.getSchoolFundingGroupCodesFromInstituteApi(); - //assertThat(result).hasSize(1); - } @Test @@ -221,7 +217,6 @@ public void whenGetSchoolCategoryCodesFromRedisCache_GetSchoolCategoryCodes() { scce.setLabel("SCC2-label"); scces.add(scce); when(schoolCategoryCodeRedisRepository.findAll()).thenReturn(scces); - //assertTrue(codeService.getSchoolCategoryCodesFromRedisCache().size() == 2); } @Test @@ -306,7 +301,6 @@ public void whenInitializeSchoolCategoryCodeCache_WithLoadingAndTrue_ThenForceLo doNothing().when(codeServicemock).loadSchoolCategoryCodesIntoRedisCache(sccs); codeService.initializeSchoolCategoryCodeCache(true); - //verify(codeServicemock).loadSchoolCategoryCodesIntoRedisCache(sccs); } @Test @@ -330,7 +324,6 @@ public void whenGetSchoolFundingGroupCodesFromRedisCache_GetSchoolFundingGroupCo sfgce.setLabel("SFGC2-label"); sfgces.add(sfgce); when(schoolFundingGroupCodeRedisRepository.findAll()).thenReturn(sfgces); - //assertTrue(codeService.getSchoolCategoryCodesFromRedisCache().size() == 2); } @Test @@ -413,7 +406,6 @@ public void whenInitializeSchoolFundingGroupCodeCache_WithLoadingAndTrue_ThenFor doNothing().when(codeServicemock).loadSchoolFundingGroupCodesIntoRedisCache(sfgcs); codeService.initializeSchoolFundingGroupCodeCache(true); - //verify(codeServicemock).loadSchoolCategoryCodesIntoRedisCache(sccs); } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteDistrictServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteDistrictServiceTest.java index 0f4afd93..72f5b766 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteDistrictServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteDistrictServiceTest.java @@ -7,10 +7,15 @@ import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; import ca.bc.gov.educ.api.trax.model.dto.ResponseObj; import ca.bc.gov.educ.api.trax.model.dto.institute.District; +import ca.bc.gov.educ.api.trax.model.dto.institute.DistrictContact; +import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; import ca.bc.gov.educ.api.trax.model.entity.institute.DistrictContactEntity; import ca.bc.gov.educ.api.trax.model.entity.institute.DistrictEntity; +import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolDetailEntity; import ca.bc.gov.educ.api.trax.model.transformer.institute.DistrictTransformer; +import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolDetailTransformer; import ca.bc.gov.educ.api.trax.repository.redis.DistrictRedisRepository; +import ca.bc.gov.educ.api.trax.repository.redis.SchoolDetailRedisRepository; import ca.bc.gov.educ.api.trax.service.RESTService; import ca.bc.gov.educ.api.trax.support.TestUtils; import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; @@ -39,14 +44,12 @@ import reactor.core.publisher.Mono; import redis.clients.jedis.JedisCluster; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.function.Consumer; import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; @@ -66,9 +69,13 @@ public class InstituteDistrictServiceTest { private DistrictTransformer districtTransformer; @Autowired private DistrictService districtService; + @Autowired + private SchoolService schoolService; @MockBean private DistrictRedisRepository districtRedisRepository; @MockBean + private SchoolDetailRedisRepository schoolDetailRedisRepository; + @MockBean private JedisConnectionFactory jedisConnectionFactoryMock; @MockBean private JedisCluster jedisClusterMock; @@ -76,6 +83,9 @@ public class InstituteDistrictServiceTest { @MockBean @Qualifier("default") WebClient webClientMock; + @MockBean + @Qualifier("instituteWebClient") + private WebClient instWebClient; @Mock @@ -92,6 +102,10 @@ public class InstituteDistrictServiceTest { private Mono> districtEntitiesMock; @Mock private List districtsMock; + @MockBean + private DistrictTransformer districtTransformerMock; + @MockBean + private SchoolDetailTransformer schoolDetailTransformer; // NATS @@ -106,6 +120,8 @@ public class InstituteDistrictServiceTest { @MockBean private RestUtils restUtils; @MockBean + private ServiceHelper serviceHelper; + @MockBean private RESTService restServiceMock; @TestConfiguration @@ -123,34 +139,31 @@ public ClientRegistration findByRegistrationId(String registrationId) { @Test public void whenGetDistrictsFromInstituteApi_returnsListOfDistricts() { - List districts = new ArrayList<>(); - DistrictEntity district = new DistrictEntity(); - + List districtEntities = new ArrayList<>(); + DistrictEntity districtEntity = new DistrictEntity(); + districtEntity.setDistrictId("ID"); + districtEntity.setDistrictNumber("1234"); + districtEntity.setDistrictStatusCode("SC"); + districtEntity.setDistrictRegionCode("RC"); + districtEntity.setContacts(Arrays.asList(new DistrictContactEntity(), new DistrictContactEntity())); + districtEntities.add(districtEntity); + + List districts = new ArrayList<>(); + District district = new District(); district.setDistrictId("ID"); district.setDistrictNumber("1234"); district.setDistrictStatusCode("SC"); district.setDistrictRegionCode("RC"); - district.setContacts(Arrays.asList(new DistrictContactEntity(), new DistrictContactEntity())); - + district.setContacts(Arrays.asList(new DistrictContact(), new DistrictContact())); districts.add(district); - when(this.restUtils.getTokenResponseObject(anyString(), anyString())) - .thenReturn(responseObjectMock); - when(this.responseObjectMock.getAccess_token()) - .thenReturn("accessToken"); - when(webClientMock.get()) - .thenReturn(requestHeadersUriSpecMock); - when(requestHeadersUriSpecMock.uri(anyString())) - .thenReturn(requestHeadersSpecMock); - when(requestHeadersSpecMock.headers(any(Consumer.class))) - .thenReturn(requestHeadersSpecMock); - when(requestHeadersSpecMock.retrieve()) - .thenReturn(responseSpecMock); - when(this.responseSpecMock.bodyToMono(new ParameterizedTypeReference>(){})) - .thenReturn(districtEntitiesMock); - when(this.districtEntitiesMock.block()).thenReturn(districts); + when(restServiceMock.get(constants.getAllDistrictsFromInstituteApiUrl(), List.class, instWebClient)) + .thenReturn(districtEntities); + when(districtTransformerMock.transformToDTO(districtEntities)) + .thenReturn(districts); List result = districtService.getDistrictsFromInstituteApi(); + assertEquals(districts, result); } @Test @@ -162,6 +175,49 @@ public void whenLoadDistrictsIntoRedisCache_DoesNotThrow() { assertDoesNotThrow(() -> districtService.loadDistrictsIntoRedisCache(districts)); } + @Test + public void whenGetDistrictsFromRedisCache_ReturnDistricts() { + List districts = new ArrayList<>(); + District district = new District(); + district.setDistrictId("ID"); + district.setDistrictNumber("1234"); + district.setDistrictStatusCode("SC"); + district.setDistrictRegionCode("RC"); + district.setContacts(Arrays.asList(new DistrictContact(), new DistrictContact())); + districts.add(district); + + district = new District(); + district.setDistrictId("ID"); + district.setDistrictNumber("1234"); + district.setDistrictStatusCode("SC"); + district.setDistrictRegionCode("RC"); + district.setContacts(Arrays.asList(new DistrictContact(), new DistrictContact())); + districts.add(district); + + List districtEntities = new ArrayList<>(); + DistrictEntity districtEntity = new DistrictEntity(); + districtEntity.setDistrictId("ID"); + districtEntity.setDistrictNumber("1234"); + districtEntity.setDistrictStatusCode("SC"); + districtEntity.setDistrictRegionCode("RC"); + districtEntity.setContacts(Arrays.asList(new DistrictContactEntity(), new DistrictContactEntity())); + districtEntities.add(districtEntity); + + districtEntity = new DistrictEntity(); + districtEntity.setDistrictId("ID"); + districtEntity.setDistrictNumber("1234"); + districtEntity.setDistrictStatusCode("SC"); + districtEntity.setDistrictRegionCode("RC"); + districtEntity.setContacts(Arrays.asList(new DistrictContactEntity(), new DistrictContactEntity())); + districtEntities.add(districtEntity); + + when(this.districtRedisRepository.findAll()) + .thenReturn(districtEntities); + when(this.districtTransformerMock.transformToDTO(districtEntities)) + .thenReturn(districts); + assertEquals(districts, districtService.getDistrictsFromRedisCache()); + } + @Test public void whenInitializeDistrictCache_WithLoadingAndFalse_DoNotForceLoad() { when(jedisClusterMock.get(CacheKey.DISTRICT_CACHE.name())) @@ -180,7 +236,6 @@ public void whenInitializeDistrictCache_WithReadyAndFalse_DoNotForceLoad() { @Test public void whenInitializeDistrictCache_WithLoadingAndTrue_ThenForceLoad() { - DistrictEntity de = new DistrictEntity(); List des = new ArrayList(); de.setDistrictId("123"); @@ -201,7 +256,6 @@ public void whenInitializeDistrictCache_WithLoadingAndTrue_ThenForceLoad() { d.setDistrictNumber("012"); ds.add(d); - when(webClientMock.get()) .thenReturn(requestHeadersUriSpecMock); when(requestHeadersUriSpecMock.uri(anyString())) @@ -233,7 +287,53 @@ public void whenInitializeDistrictCache_WithLoadingAndTrue_ThenForceLoad() { doNothing().when(districtServiceMock).loadDistrictsIntoRedisCache(ds); districtService.initializeDistrictCache(true); - //verify(codeServicemock).loadSchoolCategoryCodesIntoRedisCache(sccs); + } + + @Test + public void whenGetDistrictByDistNoFromRedisCache_ReturnDistrict() { + String distNo = "123"; + District district = new District(); + district.setDistrictId("ID"); + district.setDistrictNumber("123"); + district.setDistrictStatusCode("SC"); + district.setDistrictRegionCode("RC"); + district.setContacts(Arrays.asList(new DistrictContact(), new DistrictContact())); + + DistrictEntity districtEntity = new DistrictEntity(); + districtEntity.setDistrictId("ID"); + districtEntity.setDistrictNumber("456"); + districtEntity.setDistrictStatusCode("SC"); + districtEntity.setDistrictRegionCode("RC"); + districtEntity.setContacts(Arrays.asList(new DistrictContactEntity(), new DistrictContactEntity())); + + when(this.districtRedisRepository.findByDistrictNumber(distNo)) + .thenReturn(districtEntity); + when(this.districtTransformerMock.transformToDTO(districtEntity)) + .thenReturn(district); + assertEquals(district, districtService.getDistrictByDistNoFromRedisCache(distNo)); + } + + @Test + public void whenGetDistrictByIdFromRedisCache_ReturnDistrict() { + District district = new District(); + district.setDistrictId("ID"); + district.setDistrictNumber("1234"); + district.setDistrictStatusCode("SC"); + district.setDistrictRegionCode("RC"); + district.setContacts(Arrays.asList(new DistrictContact(), new DistrictContact())); + + DistrictEntity districtEntity = new DistrictEntity(); + districtEntity.setDistrictId("ID"); + districtEntity.setDistrictNumber("1234"); + districtEntity.setDistrictStatusCode("SC"); + districtEntity.setDistrictRegionCode("RC"); + districtEntity.setContacts(Arrays.asList(new DistrictContactEntity(), new DistrictContactEntity())); + + when(this.districtRedisRepository.findById("ID")) + .thenReturn(Optional.of(districtEntity)); + when(this.districtTransformerMock.transformToDTO(Optional.of(districtEntity))) + .thenReturn(district); + assertEquals(district, districtService.getDistrictByIdFromRedisCache("ID")); } @Test @@ -252,16 +352,121 @@ public void whenInitializeDistrictCache_WithReadyAndTrue_ThenForceLoad() { when(jedisClusterMock.get(CacheKey.DISTRICT_CACHE.name())) .thenReturn(String.valueOf(CacheStatus.READY)); when(jedisClusterMock.set(CacheKey.DISTRICT_CACHE.name(), CacheStatus.READY.name())) - .thenReturn(anyString()); + .thenReturn("OK"); districtService.initializeDistrictCache(true); + verify(serviceHelper).initializeCache(true, CacheKey.DISTRICT_CACHE, districtService); + } + + @Test + public void whenGetDistrictsBySchoolCategoryCode_ReturnDistricts() { + String schoolCategoryCode = "ABC"; + List districts = new ArrayList<>(); + District district = new District(); + district.setDistrictId("ID"); + district.setDistrictNumber("1234"); + district.setDistrictStatusCode("SC"); + district.setDistrictRegionCode("RC"); + district.setContacts(Arrays.asList(new DistrictContact(), new DistrictContact())); + districts.add(district); + + district = new District(); + district.setDistrictId("ID"); + district.setDistrictNumber("1234"); + district.setDistrictStatusCode("SC"); + district.setDistrictRegionCode("RC"); + district.setContacts(Arrays.asList(new DistrictContact(), new DistrictContact())); + districts.add(district); + + List districtEntities = new ArrayList<>(); + DistrictEntity districtEntity = new DistrictEntity(); + districtEntity.setDistrictId("ID"); + districtEntity.setDistrictNumber("1234"); + districtEntity.setDistrictStatusCode("SC"); + districtEntity.setDistrictRegionCode("RC"); + districtEntity.setContacts(Arrays.asList(new DistrictContactEntity(), new DistrictContactEntity())); + districtEntities.add(districtEntity); + + districtEntity = new DistrictEntity(); + districtEntity.setDistrictId("ID"); + districtEntity.setDistrictNumber("1234"); + districtEntity.setDistrictStatusCode("SC"); + districtEntity.setDistrictRegionCode("RC"); + districtEntity.setContacts(Arrays.asList(new DistrictContactEntity(), new DistrictContactEntity())); + districtEntities.add(districtEntity); + + List schoolDetails = new ArrayList<>(); + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("ID"); + schoolDetail.setDistrictId("DistID"); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); + schoolDetails.add(schoolDetail); + + schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("ID"); + schoolDetail.setDistrictId("DistID"); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); + schoolDetails.add(schoolDetail); + + List schoolDetailEntities = new ArrayList<>(); + SchoolDetailEntity schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId("ID"); + schoolDetailEntity.setDistrictId("DistID"); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + schoolDetailEntities.add(schoolDetailEntity); + + schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId("ID"); + schoolDetailEntity.setDistrictId("DistID"); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + schoolDetailEntities.add(schoolDetailEntity); + + + when(this.schoolDetailRedisRepository.findBySchoolCategoryCode(schoolCategoryCode)) + .thenReturn(schoolDetailEntities); + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntities)) + .thenReturn(schoolDetails); + when(this.districtService.getDistrictByIdFromRedisCache("ID")) + .thenReturn(district); + when(this.districtTransformerMock.transformToDTO(districtEntities)) + .thenReturn(districts); + when(this.schoolService.getSchoolDetailsBySchoolCategoryCode(schoolCategoryCode)) + .thenReturn(schoolDetails); + assertEquals(districts, districtService.getDistrictsBySchoolCategoryCode(schoolCategoryCode)); } @Test public void updateDistrictCache_givenValidDistrictId_shouldUpdateCache() { // given // set up initial district in redis mock - DistrictEntity districtEntity = createDistrictEntity(); - District district = districtTransformer.transformToDTO(districtEntity); + District district = new District(); + district.setDistrictId(UUID.randomUUID().toString()); + district.setDistrictNumber("002"); + district.setFaxNumber("1233216547"); + district.setPhoneNumber("3216549874"); + district.setEmail("district@district.ca"); + district.setWebsite("www.district.ca"); + district.setDisplayName("Test Display Name"); + district.setDistrictRegionCode("NOT_APPLIC"); + district.setDistrictStatusCode("INACTIVE"); + + DistrictEntity districtEntity = new DistrictEntity(); + districtEntity.setDistrictId(UUID.randomUUID().toString()); + districtEntity.setDistrictNumber("002"); + districtEntity.setFaxNumber("1233216547"); + districtEntity.setPhoneNumber("3216549874"); + districtEntity.setEmail("district@district.ca"); + districtEntity.setWebsite("www.district.ca"); + districtEntity.setDisplayName("Test Display Name"); + districtEntity.setDistrictRegionCode("NOT_APPLIC"); + districtEntity.setDistrictStatusCode("INACTIVE"); // when // call updateDistrictCache with district id and mock a return from webclient Mockito.when(this.restServiceMock.get(anyString(), @@ -276,7 +481,6 @@ public void updateDistrictCache_givenValidDistrictId_shouldUpdateCache() { private DistrictEntity createDistrictEntity() { District d = TestUtils.createDistrict(); - DistrictEntity de = this.districtTransformer.transformToEntity(d); - return de; + return districtTransformer.transformToEntity(d); } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteSchoolServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteSchoolServiceTest.java index ed7f10d1..e1720176 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteSchoolServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/InstituteSchoolServiceTest.java @@ -1,22 +1,22 @@ package ca.bc.gov.educ.api.trax.service.institute; +import ca.bc.gov.educ.api.trax.constant.CacheKey; import ca.bc.gov.educ.api.trax.messaging.NatsConnection; import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; import ca.bc.gov.educ.api.trax.model.dto.ResponseObj; -import ca.bc.gov.educ.api.trax.model.dto.institute.District; import ca.bc.gov.educ.api.trax.model.dto.institute.School; import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; import ca.bc.gov.educ.api.trax.model.entity.institute.*; -import ca.bc.gov.educ.api.trax.model.transformer.institute.DistrictTransformer; import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolDetailTransformer; import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolTransformer; -import ca.bc.gov.educ.api.trax.repository.GradCountryRepository; -import ca.bc.gov.educ.api.trax.repository.GradProvinceRepository; +import ca.bc.gov.educ.api.trax.repository.redis.SchoolDetailRedisRepository; import ca.bc.gov.educ.api.trax.repository.redis.SchoolRedisRepository; +import ca.bc.gov.educ.api.trax.service.RESTService; import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; import ca.bc.gov.educ.api.trax.util.RestUtils; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -44,8 +44,10 @@ import java.util.function.Consumer; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; @@ -61,14 +63,21 @@ public class InstituteSchoolServiceTest { @Autowired private SchoolService schoolService; @MockBean + private ServiceHelper serviceHelperMock; + @MockBean private SchoolRedisRepository schoolRedisRepository; @MockBean + private SchoolDetailRedisRepository schoolDetailRedisRepository; + @MockBean private JedisConnectionFactory jedisConnectionFactoryMock; @MockBean private JedisCluster jedisClusterMock; @MockBean @Qualifier("default") WebClient webClientMock; + @MockBean + @Qualifier("instituteWebClient") + private WebClient instWebClient; @Mock private WebClient.RequestHeadersSpec requestHeadersSpecMock; @Mock @@ -90,9 +99,15 @@ public class InstituteSchoolServiceTest { @MockBean private RestUtils restUtils; @MockBean + private RESTService restServiceMock; + @MockBean private SchoolTransformer schoolTransformerMock; + @Autowired + private SchoolTransformer schoolTransformer; @MockBean private SchoolDetailTransformer schoolDetailTransformerMock; + @Autowired + private SchoolDetailTransformer schoolDetailTransformer; // NATS @MockBean @@ -149,7 +164,6 @@ public void whenGetSchoolsFromInstituteApi_returnsListOfSchools() { when(this.schoolTransformerMock.transformToDTO(schools)) .thenReturn(schoolsMock); - List result = schoolService.getSchoolsFromInstituteApi(); } @@ -163,37 +177,316 @@ public void whenLoadSchoolsIntoRedisCache_DoesNotThrow() { } @Test - public void whenGetSchoolDetailssFromInstituteApi_returnsListOfSchoolDetails() { - List schoolDetails = new ArrayList<>(); - SchoolDetailEntity schoolDetail = new SchoolDetailEntity(); + public void whenGetSchoolsFromRedisCache_ReturnSchools() { + String mincode = "12345678"; + List schools = new ArrayList<>(); + School school = new School(); + school.setSchoolId("ID"); + school.setDistrictId("DistID"); + school.setSchoolNumber("12345"); + school.setMincode(mincode); + school.setSchoolCategoryCode("SCC"); + school.setEmail("abc@xyz.ca"); + schools.add(school); + + school = new School(); + school.setSchoolId("ID"); + school.setDistrictId("DistID"); + school.setSchoolNumber("12345"); + school.setMincode(mincode); + school.setSchoolCategoryCode("SCC"); + school.setEmail("abc@xyz.ca"); + schools.add(school); + + List schoolEntities = new ArrayList<>(); + SchoolEntity schoolEntity = new SchoolEntity(); + schoolEntity.setSchoolId("ID"); + schoolEntity.setDistrictId("DistID"); + schoolEntity.setSchoolNumber("12345"); + schoolEntity.setMincode(mincode); + schoolEntity.setSchoolCategoryCode("SCC"); + schoolEntity.setEmail("abc@xyz.ca"); + schoolEntities.add(schoolEntity); + + schoolEntity = new SchoolEntity(); + schoolEntity.setSchoolId("ID"); + schoolEntity.setDistrictId("DistID"); + schoolEntity.setSchoolNumber("12345"); + schoolEntity.setMincode(mincode); + schoolEntity.setSchoolCategoryCode("SCC"); + schoolEntity.setEmail("abc@xyz.ca"); + schoolEntities.add(schoolEntity); + + when(this.schoolRedisRepository.findAll()) + .thenReturn(schoolEntities); + when(this.schoolTransformer.transformToDTO(schoolEntities)) + .thenReturn(schools); + assertEquals(schools, schoolService.getSchoolsFromRedisCache()); + } + + @Test + public void whenGetSchoolByMincodeFromRedisCache_ReturnSchool() { + String mincode = "12345678"; + School school = new School(); + school.setSchoolId("ID"); + school.setDistrictId("DistID"); + school.setSchoolNumber("12345"); + school.setMincode(mincode); + school.setSchoolCategoryCode("SCC"); + school.setEmail("abc@xyz.ca"); + + SchoolEntity schoolEntity = new SchoolEntity(); + schoolEntity.setSchoolId("ID"); + schoolEntity.setDistrictId("DistID"); + schoolEntity.setSchoolNumber("12345"); + schoolEntity.setMincode(mincode); + schoolEntity.setSchoolCategoryCode("SCC"); + schoolEntity.setEmail("abc@xyz.ca"); + + when(this.schoolRedisRepository.findByMincode(mincode)) + .thenReturn(schoolEntity); + when(this.schoolTransformer.transformToDTO(schoolEntity)) + .thenReturn(school); + assertEquals(school, schoolService.getSchoolByMincodeFromRedisCache(mincode)); + } + + @Test + public void whenCheckIfSchoolExists_returnTrue() { + String minCode = "12345678"; + SchoolEntity schoolEntity = new SchoolEntity(); + schoolEntity.setSchoolId("ID"); + schoolEntity.setDistrictId("DistID"); + schoolEntity.setSchoolNumber("12345"); + schoolEntity.setMincode(minCode); + schoolEntity.setSchoolCategoryCode("SCC"); + schoolEntity.setEmail("abc@xyz.ca"); + + when(schoolRedisRepository.findByMincode(minCode)).thenReturn(schoolEntity); + assertEquals(true, schoolService.checkIfSchoolExists(minCode)); + } + + @Test + public void whenCheckIfSchoolExists_returnFalse() { + String minCode = "12345678"; + when(schoolRedisRepository.findByMincode(minCode)).thenReturn(null); + assertEquals(false, schoolService.checkIfSchoolExists(minCode)); + } + + @Test + public void whenInitializeSchoolCache_DoNothing() { + doNothing().when(serviceHelperMock).initializeCache(false, CacheKey.SCHOOL_CACHE, serviceHelperMock); + Assertions.assertDoesNotThrow(() -> schoolService.initializeSchoolCache(false)); + } + + @Test + public void whenGetSchoolDetailsFromInstituteApi_returnsListOfSchoolDetails() { + List schools = new ArrayList<>(); + School school = new School(); + school.setSchoolId("1"); + school.setMincode("234"); + schools.add(school); + school = new School(); + school.setSchoolId("2"); + school.setMincode("345"); + schools.add(school); + + List schoolDetails = new ArrayList<>(); + SchoolDetail schoolDetail1 = new SchoolDetail(); + schoolDetail1.setSchoolId("1"); + schoolDetail1.setDistrictId("DistID"); + schoolDetail1.setSchoolNumber("12345"); + schoolDetail1.setSchoolCategoryCode("SCC"); + schoolDetail1.setEmail("abc@xyz.ca"); + schoolDetails.add(schoolDetail1); + SchoolDetail schoolDetail2 = new SchoolDetail(); + schoolDetail2.setSchoolId("2"); + schoolDetail2.setDistrictId("DistID"); + schoolDetail2.setSchoolNumber("12345"); + schoolDetail2.setSchoolCategoryCode("SCC"); + schoolDetail2.setEmail("abc@xyz.ca"); + schoolDetails.add(schoolDetail2); + + SchoolDetailEntity schoolDetailEntity1 = new SchoolDetailEntity(); + schoolDetailEntity1.setSchoolId("1"); + schoolDetailEntity1.setDistrictId("DistID"); + schoolDetailEntity1.setSchoolNumber("12345"); + schoolDetailEntity1.setSchoolCategoryCode("SCC"); + schoolDetailEntity1.setEmail("abc@xyz.ca"); + SchoolDetailEntity schoolDetailEntity2 = new SchoolDetailEntity(); + schoolDetailEntity1.setSchoolId("2"); + schoolDetailEntity1.setDistrictId("DistID"); + schoolDetailEntity1.setSchoolNumber("12345"); + schoolDetailEntity1.setSchoolCategoryCode("SCC"); + schoolDetailEntity1.setEmail("abc@xyz.ca"); + + when(this.schoolService.getSchoolsFromRedisCache()).thenReturn(schools); + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntity1)).thenReturn(schoolDetail1); + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntity2)).thenReturn(schoolDetail2); + when(this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), "1"), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity1); + when(this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), "2"), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity2); + + List result = schoolService.getSchoolDetailsFromInstituteApi(); + assertEquals(schoolDetails, result); + } + + @Test + public void whenGetSchoolDetailByIdFromInstituteApi_ReturnSchoolDetail() { + String schoolId = "1"; + + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("1"); + schoolDetail.setDistrictId("DistID"); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); + + SchoolDetailEntity schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId("1"); + schoolDetailEntity.setDistrictId("DistID"); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntity)).thenReturn(schoolDetail); + when(this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), "1"), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity); + + SchoolDetail result = schoolService.getSchoolDetailByIdFromInstituteApi(schoolId); + assertEquals(schoolDetail, result); + } + + @Test + public void whenLoadSchoolDetailsIntoRedisCache_DoesNotThrow() { + List schoolDetailEntities = Arrays.asList(new SchoolDetailEntity()); + List schoolDetails = Arrays.asList(new SchoolDetail()); + when(this.schoolDetailRedisRepository.saveAll(schoolDetailEntities)) + .thenReturn(schoolDetailEntities); + assertDoesNotThrow(() -> schoolService.loadSchoolDetailsIntoRedisCache(schoolDetails)); + } + @Test + public void whenInitializeSchoolDetailCache_DoNothing() { + doNothing().when(serviceHelperMock).initializeCache(false, CacheKey.SCHOOL_DETAIL_CACHE, serviceHelperMock); + Assertions.assertDoesNotThrow(() -> schoolService.initializeSchoolDetailCache(false)); + } + + @Test + public void whenGetSchoolDetailsFromRedisCache_ReturnSchoolDetails() { + String mincode = "12345678"; + List schoolDetails = new ArrayList<>(); + SchoolDetail schoolDetail = new SchoolDetail(); schoolDetail.setSchoolId("ID"); schoolDetail.setDistrictId("DistID"); schoolDetail.setSchoolNumber("12345"); + schoolDetail.setMincode(mincode); schoolDetail.setSchoolCategoryCode("SCC"); schoolDetail.setEmail("abc@xyz.ca"); + schoolDetails.add(schoolDetail); + schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("ID"); + schoolDetail.setDistrictId("DistID"); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setMincode(mincode); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); schoolDetails.add(schoolDetail); - when(this.restUtils.getTokenResponseObject(anyString(), anyString())) - .thenReturn(responseObjectMock); - when(this.responseObjectMock.getAccess_token()) - .thenReturn("accessToken"); - when(webClientMock.get()) - .thenReturn(requestHeadersUriSpecMock); - when(requestHeadersUriSpecMock.uri(anyString())) - .thenReturn(requestHeadersSpecMock); - when(requestHeadersSpecMock.headers(any(Consumer.class))) - .thenReturn(requestHeadersSpecMock); - when(requestHeadersSpecMock.retrieve()) - .thenReturn(responseSpecMock); - when(this.responseSpecMock.bodyToMono(new ParameterizedTypeReference>(){})) - .thenReturn(schoolDetailEntitiesMock); - when(this.schoolDetailEntitiesMock.block()).thenReturn(schoolDetails); + List schoolDetailEntities = new ArrayList<>(); + SchoolDetailEntity schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId("ID"); + schoolDetailEntity.setDistrictId("DistID"); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setMincode(mincode); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + schoolDetailEntities.add(schoolDetailEntity); - when(this.schoolDetailTransformerMock.transformToDTO(schoolDetails)) - .thenReturn(schoolDetailsMock); + schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId("ID"); + schoolDetailEntity.setDistrictId("DistID"); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setMincode(mincode); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + schoolDetailEntities.add(schoolDetailEntity); - List result = schoolService.getSchoolDetailsFromInstituteApi(); + when(this.schoolDetailRedisRepository.findAll()) + .thenReturn(schoolDetailEntities); + when(this.schoolDetailTransformerMock.transformToDTO(schoolDetailEntities)) + .thenReturn(schoolDetails); + assertEquals(schoolDetails, schoolService.getSchoolDetailsFromRedisCache()); + } + + @Test + public void whenGetSchoolDetailByMincodeFromRedisCache_ReturnSchoolDetail() { + String mincode = "12345678"; + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("ID"); + schoolDetail.setDistrictId("DistID"); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setMincode(mincode); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); + + SchoolDetailEntity schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId("ID"); + schoolDetailEntity.setDistrictId("DistID"); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setMincode(mincode); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + + when(this.schoolDetailRedisRepository.findByMincode(mincode)) + .thenReturn(schoolDetailEntity); + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntity)) + .thenReturn(schoolDetail); + assertEquals(schoolDetail, schoolService.getSchoolDetailByMincodeFromRedisCache(mincode)); + } + + @Test + public void whenGetSchoolDetailBySchoolCategoryCode_ReturnSchoolDetail() { + String schoolCategoryCode = "ABC"; + List schoolDetails = new ArrayList<>(); + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("ID"); + schoolDetail.setDistrictId("DistID"); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); + schoolDetails.add(schoolDetail); + + schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("ID"); + schoolDetail.setDistrictId("DistID"); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); + schoolDetails.add(schoolDetail); + + List schoolDetailEntities = new ArrayList<>(); + SchoolDetailEntity schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId("ID"); + schoolDetailEntity.setDistrictId("DistID"); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + schoolDetailEntities.add(schoolDetailEntity); + + schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId("ID"); + schoolDetailEntity.setDistrictId("DistID"); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + schoolDetailEntities.add(schoolDetailEntity); + + when(this.schoolDetailRedisRepository.findBySchoolCategoryCode(schoolCategoryCode)) + .thenReturn(schoolDetailEntities); + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntities)) + .thenReturn(schoolDetails); + assertEquals(schoolDetails, schoolService.getSchoolDetailsBySchoolCategoryCode(schoolCategoryCode)); } } diff --git a/api/src/test/resources/application.yaml b/api/src/test/resources/application.yaml index 9bffa622..b1c45be8 100644 --- a/api/src/test/resources/application.yaml +++ b/api/src/test/resources/application.yaml @@ -33,6 +33,22 @@ spring: jwt: issuer-uri: https://soam-dev.apps.silver.devops.gov.bc.ca/auth/realms/master jwk-set-uri: https://soam-dev.apps.silver.devops.gov.bc.ca/auth/realms/master/protocol/openid-connect/certs + client: + registration: + traxclient: + client-id: trax-client-name + client-secret: trax-client-secret + authorization-grant-type: client_credentials + institute-web-client: + client-id: edx-client-name + client-secret: edx-client-secret + provider: + traxclient: + issuer-uri: https://soam-dev.apps.silver.devops.gov.bc.ca/auth/realms/master + token-uri: https://soam-dev.apps.silver.devops.gov.bc.ca/auth/realms/master/protocol/openid-connect/token + institute-web-client: + issuer-uri: https://soam-dev.apps.silver.devops.gov.bc.ca/auth/realms/master + token-uri: https://soam-dev.apps.silver.devops.gov.bc.ca/auth/realms/master/protocol/openid-connect/token sql: init: data-locations: classpath:schema.sql From 7caa57930651d13b512d14f3da8c633f41c91e0f Mon Sep 17 00:00:00 2001 From: Chris Ditcher Date: Fri, 23 Aug 2024 13:47:41 -0700 Subject: [PATCH 09/16] Update update-configmap.sh (cherry picked from commit a95653388e1e173a569cf81e53768fea0efcf810) --- tools/config/update-configmap.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/config/update-configmap.sh b/tools/config/update-configmap.sh index 855357a1..702127fb 100644 --- a/tools/config/update-configmap.sh +++ b/tools/config/update-configmap.sh @@ -23,6 +23,7 @@ FLB_CONFIG="[SERVICE] Exclude_Path *.gz,*.zip Parser docker Mem_Buf_Limit 20MB + Buffer_Max_Size 1MB [FILTER] Name record_modifier Match * From 6a6bea15596279e2a977e7f4899faafc74852909 Mon Sep 17 00:00:00 2001 From: Chris Ditcher Date: Tue, 10 Sep 2024 08:27:31 -0700 Subject: [PATCH 10/16] GRAD2-2768 (#348) * Added an extra type hierarchy for school events for filtering on transcript eligibility * Added some unit tests to support history filtering on school created events * Added testing for school updated * Added testing and code for move school filtering * Added rules for UPDATE_SCHOOL_CONTACT * Removed old update event with history method. * Updated gitignore * initial commit * Added endpoint for searching event history. * Added openapi specs * Added a mapper * Added json ignore to prevent infinite recursion * Changed to Event, also updated pom to include mapstruct annotation processing * Still buggered mapper * Success with mapper * Updated test file and pom * Updated exclusion for mapper * Updated exclusion for filter * Adding unit testing for service * Increasing test coverage. * Increasing test coverage. * Including for test * Including mapper tests * Added update endpoint and service method * Added tests and validation for EventHistory * Finished integration testing * Excluding filter * Added validation response formatter and increased testing on UUIDMapper * Added routes to student admin * Updated EntityNotFound handler * Cleaning up code --------- Co-authored-by: chris.ditcher --- .../build.from.dev.branch.deploy.to.dev.yml | 6 +- .../build.from.main.branch.deploy.to.dev.yml | 6 +- ...uild.from.release.branch.deploy.to.dev.yml | 6 +- .github/workflows/deploy_prod.yml | 3 +- .github/workflows/deploy_test.yml | 3 +- .gitignore | 2 +- api/pom.xml | 20 +- .../api/trax/config/RestErrorHandler.java | 121 +++++++----- .../controller/EventHistoryController.java | 70 +++++++ .../gov/educ/api/trax/exception/ApiError.java | 86 +++++++++ .../exception/EntityNotFoundException.java | 4 + .../exception/TraxAPIRuntimeException.java | 13 ++ .../educ/api/trax/filter/BaseFilterSpecs.java | 127 +++++++++++++ .../gov/educ/api/trax/filter/Converters.java | 50 +++++ .../filter/EventHistoryFilterSpecifics.java | 28 +++ .../educ/api/trax/filter/FilterCriteria.java | 179 ++++++++++++++++++ .../educ/api/trax/filter/FilterOperation.java | 78 ++++++++ .../api/trax/filter/FilterSpecifications.java | 107 +++++++++++ .../api/trax/mapper/EventHistoryMapper.java | 90 +++++++++ .../gov/educ/api/trax/mapper/EventMapper.java | 19 ++ .../gov/educ/api/trax/mapper/UUIDMapper.java | 22 +++ .../educ/api/trax/model/dto/BaseModel.java | 2 + .../educ/api/trax/model/dto/Condition.java | 12 ++ .../bc/gov/educ/api/trax/model/dto/Event.java | 19 ++ .../educ/api/trax/model/dto/EventHistory.java | 25 +++ .../gov/educ/api/trax/model/dto/Search.java | 24 +++ .../api/trax/model/dto/SearchCriteria.java | 39 ++++ .../educ/api/trax/model/dto/ValueType.java | 28 +++ .../api/trax/model/entity/EventEntity.java | 3 + .../trax/model/entity/EventHistoryEntity.java | 10 +- .../api/trax/model/entity/v2/BaseEntity.java | 11 +- .../repository/EventHistoryRepository.java | 3 +- .../api/trax/service/EventHistoryService.java | 176 ++++++++++++++++- .../trax/util/EducGradTraxApiConstants.java | 12 ++ .../gov/educ/api/trax/util/RequestUtil.java | 39 ++++ .../gov/educ/api/trax/util/TransformUtil.java | 62 ++++++ .../bc/gov/educ/api/trax/util/UpperCase.java | 11 ++ api/src/main/resources/application.yaml | 7 + .../EventHistoryControllerTest.java | 168 ++++++++++++++++ .../gov/educ/api/trax/mapper/MapperTest.java | 123 ++++++++++++ .../service/BaseReplicationServiceTest.java | 3 +- .../trax/service/EventHistoryServiceTest.java | 20 ++ .../api/trax/util/BaseEventHistoryTest.java | 66 +++++++ api/src/test/resources/application.yaml | 7 + tools/config/update-configmap.sh | 2 + 45 files changed, 1849 insertions(+), 63 deletions(-) create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/controller/EventHistoryController.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/exception/ApiError.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/exception/TraxAPIRuntimeException.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/BaseFilterSpecs.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/Converters.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/EventHistoryFilterSpecifics.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterCriteria.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterOperation.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterSpecifications.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventHistoryMapper.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventMapper.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/mapper/UUIDMapper.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Condition.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Event.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/EventHistory.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Search.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/SearchCriteria.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ValueType.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/util/RequestUtil.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/util/TransformUtil.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/util/UpperCase.java create mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/controller/EventHistoryControllerTest.java create mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/mapper/MapperTest.java create mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/util/BaseEventHistoryTest.java diff --git a/.github/workflows/build.from.dev.branch.deploy.to.dev.yml b/.github/workflows/build.from.dev.branch.deploy.to.dev.yml index 5749d983..fc78b822 100644 --- a/.github/workflows/build.from.dev.branch.deploy.to.dev.yml +++ b/.github/workflows/build.from.dev.branch.deploy.to.dev.yml @@ -128,7 +128,8 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} + ${{ vars.APP_LOG_LEVEL }} \ + ${{ secrets.STUDENT_ADMIN_URL_ROOT }} # OVERRIDE Configmaps curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ github.event.inputs.choice }}/tools/config/override-configmap-dev.sh \ @@ -139,7 +140,8 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} + ${{ vars.APP_LOG_LEVEL }} \ + ${{ secrets.STUDENT_ADMIN_URL_ROOT }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.github/workflows/build.from.main.branch.deploy.to.dev.yml b/.github/workflows/build.from.main.branch.deploy.to.dev.yml index 9711e715..c9a9ae89 100644 --- a/.github/workflows/build.from.main.branch.deploy.to.dev.yml +++ b/.github/workflows/build.from.main.branch.deploy.to.dev.yml @@ -116,7 +116,8 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} + ${{ vars.APP_LOG_LEVEL }} \ + ${{ secrets.STUDENT_ADMIN_URL_ROOT }} # OVERRIDE Configmaps curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ env.BRANCH }}/tools/config/override-configmap-dev.sh \ @@ -127,7 +128,8 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} + ${{ vars.APP_LOG_LEVEL }} \ + ${{ secrets.STUDENT_ADMIN_URL_ROOT }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.github/workflows/build.from.release.branch.deploy.to.dev.yml b/.github/workflows/build.from.release.branch.deploy.to.dev.yml index b7837991..e69f90b4 100644 --- a/.github/workflows/build.from.release.branch.deploy.to.dev.yml +++ b/.github/workflows/build.from.release.branch.deploy.to.dev.yml @@ -123,7 +123,8 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} + ${{ vars.APP_LOG_LEVEL }} \ + ${{ secrets.STUDENT_ADMIN_URL_ROOT }} # OVERRIDE Configmaps curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ env.BRANCH }}/tools/config/override-configmap-dev.sh \ @@ -134,7 +135,8 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} + ${{ vars.APP_LOG_LEVEL }} \ + ${{ secrets.STUDENT_ADMIN_URL_ROOT }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.github/workflows/deploy_prod.yml b/.github/workflows/deploy_prod.yml index 1cbcf539..b70d7dd9 100644 --- a/.github/workflows/deploy_prod.yml +++ b/.github/workflows/deploy_prod.yml @@ -82,7 +82,8 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} + ${{ vars.APP_LOG_LEVEL }} \ + ${{ secrets.STUDENT_ADMIN_URL_ROOT }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.github/workflows/deploy_test.yml b/.github/workflows/deploy_test.yml index 34781f56..ab0f309e 100644 --- a/.github/workflows/deploy_test.yml +++ b/.github/workflows/deploy_test.yml @@ -82,7 +82,8 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} + ${{ vars.APP_LOG_LEVEL }} \ + ${{ secrets.STUDENT_ADMIN_URL_ROOT }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.gitignore b/.gitignore index 7ded25d7..4c779315 100644 --- a/.gitignore +++ b/.gitignore @@ -54,5 +54,5 @@ build/ ### VS Code ### .vscode/ -### local dev ### +### Local dev ### **/application-local.yaml diff --git a/api/pom.xml b/api/pom.xml index 5d1eb6e2..e218b47e 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -24,14 +24,16 @@ src/main/java/ca/bc/gov/educ/api/trax/model/**, src/main/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorService, src/main/java/ca/bc/gov/educ/api/trax/util/**, - src/main/java/ca/bc/gov/educ/api/trax/repository/** + src/main/java/ca/bc/gov/educ/api/trax/repository/**, + src/main/java/ca/bc/gov/educ/api/trax/filter/** src/main/java/ca/bc/gov/educ/api/trax/messaging/**, src/main/java/ca/bc/gov/educ/api/trax/scheduler/**, src/main/java/ca/bc/gov/educ/api/trax/model/**, src/main/java/ca/bc/gov/educ/api/trax/util/**, - src/main/java/ca/bc/gov/educ/api/trax/repository/** + src/main/java/ca/bc/gov/educ/api/trax/repository/**, + src/main/java/ca/bc/gov/educ/api/trax/filter/** 18 3.10.1 @@ -71,6 +73,11 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.security + spring-security-test + test + org.springframework.boot spring-boot-starter-data-redis @@ -83,6 +90,10 @@ org.apache.commons commons-pool2 + + org.springframework.boot + spring-boot-starter-validation + org.apache.logging.log4j log4j-api @@ -322,6 +333,11 @@ lombok ${lombok.version} + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + org.springframework spring-context-indexer diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/config/RestErrorHandler.java b/api/src/main/java/ca/bc/gov/educ/api/trax/config/RestErrorHandler.java index 0b3c8933..1f948717 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/config/RestErrorHandler.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/config/RestErrorHandler.java @@ -1,63 +1,99 @@ package ca.bc.gov.educ.api.trax.config; +import ca.bc.gov.educ.api.trax.exception.ApiError; +import ca.bc.gov.educ.api.trax.exception.EntityNotFoundException; import ca.bc.gov.educ.api.trax.exception.GradBusinessRuleException; import ca.bc.gov.educ.api.trax.util.ApiResponseMessage.MessageTypeEnum; import ca.bc.gov.educ.api.trax.util.ApiResponseModel; import ca.bc.gov.educ.api.trax.util.GradValidation; +import lombok.extern.slf4j.Slf4j; import org.hibernate.dialect.lock.OptimisticEntityLockException; import org.hibernate.exception.ConstraintViolationException; -import org.jboss.logging.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataRetrievalFailureException; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.orm.jpa.JpaObjectRetrievalFailureException; import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import java.util.HashMap; +import java.util.Map; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; + +@Slf4j @ControllerAdvice public class RestErrorHandler extends ResponseEntityExceptionHandler { - private static final Logger LOGGER = Logger.getLogger(RestErrorHandler.class); + GradValidation validation; @Autowired - GradValidation validation; + public RestErrorHandler(GradValidation validation) { + this.validation = validation; + } + + /** + * Override method handles exception thrown by @Valid validation + * on controller methods (for example) + */ + @Override + protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { + Map errors = new HashMap<>(); + ex.getBindingResult().getAllErrors().forEach(error -> { + String fieldName = ((FieldError) error).getField(); + String errorMessage = error.getDefaultMessage(); + errors.put(fieldName, errorMessage); + }); + ApiError apiError = new ApiError(BAD_REQUEST); + apiError.setMessage(errors.toString()); + return buildResponseEntity(apiError); + } @ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class }) - protected ResponseEntity handleConflict(RuntimeException ex, WebRequest request) { - LOGGER.error("Illegal argument ERROR IS: " + ex.getClass().getName(), ex); + protected ResponseEntity handleConflict(RuntimeException ex) { + log.error("Illegal argument ERROR IS: {}", ex.getClass().getName(), ex); ApiResponseModel response = ApiResponseModel.ERROR(null, ex.getLocalizedMessage()); - validation.ifErrors(errorList -> response.addErrorMessages(errorList)); - validation.ifWarnings(warningList -> response.addWarningMessages(warningList)); + validation.ifErrors(response::addErrorMessages); + validation.ifWarnings(response::addWarningMessages); validation.clear(); return new ResponseEntity<>(response, HttpStatus.UNPROCESSABLE_ENTITY); } - @ExceptionHandler(value = { JpaObjectRetrievalFailureException.class, DataRetrievalFailureException.class }) - protected ResponseEntity handleEntityNotFound(RuntimeException ex, WebRequest request) { - LOGGER.error("JPA ERROR IS: " + ex.getClass().getName(), ex); - validation.clear(); - return new ResponseEntity<>(ApiResponseModel.ERROR(null, ex.getLocalizedMessage()), HttpStatus.BAD_REQUEST); + @ExceptionHandler(value = { JpaObjectRetrievalFailureException.class, DataRetrievalFailureException.class, EntityNotFoundException.class }) + protected ResponseEntity handleEntityNotFound(RuntimeException ex) { + // no need to log EntityNotFoundExceptions as error + // it is used to denote NOT FOUND and is normal part of operations + if(!(ex instanceof EntityNotFoundException)){ + log.error("JPA ERROR IS: {}", ex.getClass().getName(), ex); + } + ApiError apiError = new ApiError(NOT_FOUND); + apiError.setMessage(ex.getMessage()); + return buildResponseEntity(apiError); } @ExceptionHandler(value = { AccessDeniedException.class }) - protected ResponseEntity handleAuthorizationErrors(Exception ex, WebRequest request) { - - LOGGER.error("Authorization error EXCETPION IS: " + ex.getClass().getName()); + protected ResponseEntity handleAuthorizationErrors(AccessDeniedException ex) { + log.error("Authorization error EXCEPTION IS: {}", ex.getClass().getName()); String message = "You are not authorized to access this resource."; validation.clear(); return new ResponseEntity<>(ApiResponseModel.ERROR(null, message), HttpStatus.FORBIDDEN); } @ExceptionHandler(value = { GradBusinessRuleException.class }) - protected ResponseEntity handleGradBusinessException(Exception ex, WebRequest request) { + protected ResponseEntity handleGradBusinessException(GradBusinessRuleException ex) { ApiResponseModel response = ApiResponseModel.ERROR(null); - validation.ifErrors(errorList -> response.addErrorMessages(errorList)); - validation.ifWarnings(warningList -> response.addWarningMessages(warningList)); + validation.ifErrors(response::addErrorMessages); + validation.ifWarnings(response::addWarningMessages); if (response.getMessages().isEmpty()) { response.addMessageItem(ex.getLocalizedMessage(), MessageTypeEnum.ERROR); } @@ -66,55 +102,54 @@ protected ResponseEntity handleGradBusinessException(Exception ex, WebRe } @ExceptionHandler(value = { OptimisticEntityLockException.class }) - protected ResponseEntity handleOptimisticEntityLockException(OptimisticEntityLockException ex, WebRequest request) { - - LOGGER.error("EXCEPTION IS: " + ex.getClass().getName(), ex); - LOGGER.error("Illegal argument ERROR IS: " + ex.getClass().getName(), ex); - ApiResponseModel response = ApiResponseModel.ERROR(null); - validation.ifErrors(errorList -> response.addErrorMessages(errorList)); - validation.ifWarnings(warningList -> response.addWarningMessages(warningList)); - if (!validation.hasErrors()) { - response.addMessageItem(ex.getLocalizedMessage(), MessageTypeEnum.ERROR); - } - validation.clear(); - return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + protected ResponseEntity handleOptimisticEntityLockException(OptimisticEntityLockException ex) { + return getGenericUncaughtExceptionResponse(ex); } @ExceptionHandler(value = { DataIntegrityViolationException.class }) - protected ResponseEntity handleSQLException(DataIntegrityViolationException ex, WebRequest request) { + protected ResponseEntity handleSQLException(DataIntegrityViolationException ex) { - LOGGER.error("DATA INTEGRITY VIOLATION IS: " + ex.getClass().getName(), ex); + log.error("DATA INTEGRITY VIOLATION IS: {}", ex.getClass().getName(), ex); String msg = ex.getLocalizedMessage(); - Throwable cause = ex.getCause(); if (cause instanceof ConstraintViolationException) { ConstraintViolationException contraintViolation = (ConstraintViolationException) cause; if ("23000".equals(contraintViolation.getSQLState())) { // primary key violation - probably inserting a duplicate record - msg = ex.getRootCause().getMessage(); + msg = (ex.getRootCause() != null) ? ex.getRootCause().getMessage() : ""; } } - ApiResponseModel response = ApiResponseModel.ERROR(null, msg); - validation.ifErrors(errorList -> response.addErrorMessages(errorList)); - validation.ifWarnings(warningList -> response.addWarningMessages(warningList)); + validation.ifErrors(response::addErrorMessages); + validation.ifWarnings(response::addWarningMessages); validation.clear(); return new ResponseEntity<>(response, HttpStatus.UNPROCESSABLE_ENTITY); } @ExceptionHandler(value = { Exception.class }) - protected ResponseEntity handleUncaughtException(Exception ex, WebRequest request) { + protected ResponseEntity handleUncaughtException(Exception ex) { + return getGenericUncaughtExceptionResponse(ex); + } - LOGGER.error("EXCEPTION IS: " + ex.getClass().getName(), ex); - LOGGER.error("Illegal argument ERROR IS: " + ex.getClass().getName(), ex); + private ResponseEntity getGenericUncaughtExceptionResponse(Exception ex) { + log.error("EXCEPTION IS: {}", ex.getClass().getName(), ex); ApiResponseModel response = ApiResponseModel.ERROR(null); - validation.ifErrors(errorList -> response.addErrorMessages(errorList)); - validation.ifWarnings(warningList -> response.addWarningMessages(warningList)); + validation.ifErrors(response::addErrorMessages); + validation.ifWarnings(response::addWarningMessages); if (!validation.hasErrors()) { response.addMessageItem(ex.getLocalizedMessage(), MessageTypeEnum.ERROR); } validation.clear(); return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } - + + /** + * Build response entity response entity. + * + * @param apiError the api error + * @return the response entity + */ + private ResponseEntity buildResponseEntity(ApiError apiError) { + return new ResponseEntity<>(apiError, apiError.getStatus()); + } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/EventHistoryController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/EventHistoryController.java new file mode 100644 index 00000000..3f82bd07 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/EventHistoryController.java @@ -0,0 +1,70 @@ +package ca.bc.gov.educ.api.trax.controller; +import ca.bc.gov.educ.api.trax.mapper.EventHistoryMapper; +import ca.bc.gov.educ.api.trax.model.dto.EventHistory; +import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; +import ca.bc.gov.educ.api.trax.service.EventHistoryService; +import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; +import ca.bc.gov.educ.api.trax.util.JsonUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.scheduling.annotation.Async; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@CrossOrigin +@RestController +@RequestMapping(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1) +@Tag(name = "Event History", description = "Endpoints for Event History.") +public class EventHistoryController { + + private final EventHistoryService eventHistoryService; + private final EventHistoryMapper mapper; + + @Autowired + public EventHistoryController(EventHistoryService eventHistoryService, EventHistoryMapper mapper) { + this.eventHistoryService = eventHistoryService; + this.mapper = mapper; + } + + @GetMapping("/paginated") + @Async + @PreAuthorize("hasAuthority('SCOPE_READ_EVENT_HISTORY')") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR.")}) + @Transactional(readOnly = true) + @Operation(summary = "Find All Event History paginated", description = "Find all Event History using pagination", tags = { "Event History" }) + public CompletableFuture> findAll(@RequestParam(name = "pageNumber", defaultValue = "0") Integer pageNumber, + @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, + @RequestParam(name = "sort", defaultValue = "") String sort, + @RequestParam(name = "searchParams", required = false) String searchParams) { + final List sorts = new ArrayList<>(); + Specification eventHistorySpecs = eventHistoryService.setSpecificationAndSortCriteria(sort, searchParams, JsonUtil.mapper, sorts); + return this.eventHistoryService.findAll(eventHistorySpecs, pageNumber, pageSize, sorts).thenApplyAsync(schoolEntities -> schoolEntities.map(mapper::toStructure)); + } + + @PutMapping + @PreAuthorize("hasAuthority('SCOPE_WRITE_EVENT_HISTORY')") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "NOT FOUND"), + @ApiResponse(responseCode = "400", description = "BAD REQUEST"), + @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR.")}) + @Transactional + @Operation(summary = "Update an event history entity", description = "Update a valid event history", tags = { "Event History" }) + public EventHistory updateEventHistory(@Valid @RequestBody EventHistory eventHistory) { + return this.eventHistoryService.updateEventHistory(eventHistory); + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/ApiError.java b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/ApiError.java new file mode 100644 index 00000000..9072d610 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/ApiError.java @@ -0,0 +1,86 @@ +package ca.bc.gov.educ.api.trax.exception; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import org.springframework.http.HttpStatus; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * The type Api error. + */ +@AllArgsConstructor +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ApiError implements Serializable { + + /** + * The Status. + */ + private HttpStatus status; + /** + * The Timestamp. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") + private LocalDateTime timestamp; + /** + * The Message. + */ + private String message; + /** + * The Debug message. + */ + private String debugMessage; + + /** + * Instantiates a new Api error. + */ + private ApiError() { + timestamp = LocalDateTime.now(); + } + + /** + * Instantiates a new Api error. + * + * @param status the status + */ + public ApiError(HttpStatus status) { + this(); + this.status = status; + } + + /** + * Instantiates a new Api error. + * + * @param status the status + * @param ex the ex + */ + ApiError(HttpStatus status, Throwable ex) { + this(); + this.status = status; + this.message = "Unexpected error"; + this.debugMessage = ex.getLocalizedMessage(); + } + + /** + * Instantiates a new Api error. + * + * @param status the status + * @param message the message + * @param ex the ex + */ + public ApiError(HttpStatus status, String message, Throwable ex) { + this(); + this.status = status; + this.message = message; + this.debugMessage = ex.getLocalizedMessage(); + } + + +} + diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/EntityNotFoundException.java b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/EntityNotFoundException.java index 6c22c65e..0609973a 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/EntityNotFoundException.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/EntityNotFoundException.java @@ -14,6 +14,10 @@ public class EntityNotFoundException extends RuntimeException { + public EntityNotFoundException(String message) { + super(message); + } + public EntityNotFoundException(Class clazz, String... searchParamsMap) { super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), toMap(String.class, String.class, searchParamsMap))); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/TraxAPIRuntimeException.java b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/TraxAPIRuntimeException.java new file mode 100644 index 00000000..6f2a6997 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/TraxAPIRuntimeException.java @@ -0,0 +1,13 @@ +package ca.bc.gov.educ.api.trax.exception; + +import java.io.Serial; + +public class TraxAPIRuntimeException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + public TraxAPIRuntimeException(String message) { + super(message); + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/BaseFilterSpecs.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/BaseFilterSpecs.java new file mode 100644 index 00000000..5cdd48ca --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/BaseFilterSpecs.java @@ -0,0 +1,127 @@ +package ca.bc.gov.educ.api.trax.filter; + +import org.springframework.data.jpa.domain.Specification; + +import java.time.chrono.ChronoLocalDate; +import java.time.chrono.ChronoLocalDateTime; +import java.util.UUID; +import java.util.function.Function; + +/** + * this is the generic class to support all kind of filter specifications for different entities + * + * @param the entity type. + * @author Om + */ +public abstract class BaseFilterSpecs { + + private final FilterSpecifications dateFilterSpecifications; + private final FilterSpecifications> dateTimeFilterSpecifications; + private final FilterSpecifications integerFilterSpecifications; + private final FilterSpecifications stringFilterSpecifications; + private final FilterSpecifications longFilterSpecifications; + private final FilterSpecifications uuidFilterSpecifications; + private final Converters converters; + + /** + * Instantiates a new Base filter specs. + * + * @param dateFilterSpecifications the date filter specifications + * @param dateTimeFilterSpecifications the date time filter specifications + * @param integerFilterSpecifications the integer filter specifications + * @param stringFilterSpecifications the string filter specifications + * @param longFilterSpecifications the long filter specifications + * @param uuidFilterSpecifications the uuid filter specifications + * @param converters the converters + */ + protected BaseFilterSpecs(FilterSpecifications dateFilterSpecifications, FilterSpecifications> dateTimeFilterSpecifications, FilterSpecifications integerFilterSpecifications, FilterSpecifications stringFilterSpecifications, FilterSpecifications longFilterSpecifications, FilterSpecifications uuidFilterSpecifications, Converters converters) { + this.dateFilterSpecifications = dateFilterSpecifications; + this.dateTimeFilterSpecifications = dateTimeFilterSpecifications; + this.integerFilterSpecifications = integerFilterSpecifications; + this.stringFilterSpecifications = stringFilterSpecifications; + this.longFilterSpecifications = longFilterSpecifications; + this.uuidFilterSpecifications = uuidFilterSpecifications; + this.converters = converters; + } + + /** + * Gets date type specification. + * + * @param fieldName the field name + * @param filterValue the filter value + * @param filterOperation the filter operation + * @return the date type specification + */ + public Specification getDateTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { + return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(ChronoLocalDate.class), dateFilterSpecifications); + } + + /** + * Gets date time type specification. + * + * @param fieldName the field name + * @param filterValue the filter value + * @param filterOperation the filter operation + * @return the date time type specification + */ + public Specification getDateTimeTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { + return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(ChronoLocalDateTime.class), dateTimeFilterSpecifications); + } + + /** + * Gets integer type specification. + * + * @param fieldName the field name + * @param filterValue the filter value + * @param filterOperation the filter operation + * @return the integer type specification + */ + public Specification getIntegerTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { + return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(Integer.class), integerFilterSpecifications); + } + + /** + * Gets long type specification. + * + * @param fieldName the field name + * @param filterValue the filter value + * @param filterOperation the filter operation + * @return the long type specification + */ + public Specification getLongTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { + return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(Long.class), longFilterSpecifications); + } + + /** + * Gets string type specification. + * + * @param fieldName the field name + * @param filterValue the filter value + * @param filterOperation the filter operation + * @return the string type specification + */ + public Specification getStringTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { + return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(String.class), stringFilterSpecifications); + } + + /** + * Gets uuid type specification. + * + * @param fieldName the field name + * @param filterValue the filter value + * @param filterOperation the filter operation + * @return the uuid type specification + */ + public Specification getUUIDTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { + return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(UUID.class), uuidFilterSpecifications); + } + + private > Specification getSpecification(String fieldName, + String filterValue, + FilterOperation filterOperation, + Function converter, + FilterSpecifications specifications) { + FilterCriteria criteria = new FilterCriteria<>(fieldName, filterValue, filterOperation, converter); + return specifications.getSpecification(criteria.getOperation()).apply(criteria); + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/Converters.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/Converters.java new file mode 100644 index 00000000..bbae2325 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/Converters.java @@ -0,0 +1,50 @@ +package ca.bc.gov.educ.api.trax.filter; + +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.chrono.ChronoLocalDate; +import java.time.chrono.ChronoLocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; + +/** + * The type Converters. + * + * @author om + */ +@Service +public class Converters { + + private final Map, Function>> map = new HashMap<>(); + + /** + * Init. + */ + @PostConstruct + public void init() { + map.put(String.class, s -> s); + map.put(Long.class, Long::valueOf); + map.put(Integer.class, Integer::valueOf); + map.put(ChronoLocalDate.class, LocalDate::parse); + map.put(ChronoLocalDateTime.class, LocalDateTime::parse); + map.put(UUID.class, UUID::fromString); + } + + /** + * Gets function. + * + * @param the type parameter + * @param classObj the class obj + * @return the function + */ + @SuppressWarnings("unchecked") + public > Function getFunction(Class classObj) { + return (Function) map.get(classObj); + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/EventHistoryFilterSpecifics.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/EventHistoryFilterSpecifics.java new file mode 100644 index 00000000..75588793 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/EventHistoryFilterSpecifics.java @@ -0,0 +1,28 @@ +package ca.bc.gov.educ.api.trax.filter; + +import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.chrono.ChronoLocalDate; +import java.time.chrono.ChronoLocalDateTime; +import java.util.UUID; + +@Service +@Slf4j +public class EventHistoryFilterSpecifics extends BaseFilterSpecs { + /** + * Instantiates a new Base filter specs. + * + * @param eventHistoryEntityChronoLocalDateFilterSpecifications the date filter specifications + * @param eventHistoryEntityChronoLocalDateTimeFilterSpecifications the date time filter specifications + * @param eventHistoryEntityIntegerFilterSpecifications the integer filter specifications + * @param eventHistoryEntityStringFilterSpecifications the string filter specifications + * @param eventHistoryEntityLongFilterSpecifications the long filter specifications + * @param uuidFilterSpecifications the uuid filter specifications + * @param converters the converters + */ + public EventHistoryFilterSpecifics(FilterSpecifications eventHistoryEntityChronoLocalDateFilterSpecifications, FilterSpecifications> eventHistoryEntityChronoLocalDateTimeFilterSpecifications, FilterSpecifications eventHistoryEntityIntegerFilterSpecifications, FilterSpecifications eventHistoryEntityStringFilterSpecifications, FilterSpecifications eventHistoryEntityLongFilterSpecifications, FilterSpecifications uuidFilterSpecifications, Converters converters) { + super(eventHistoryEntityChronoLocalDateFilterSpecifications, eventHistoryEntityChronoLocalDateTimeFilterSpecifications, eventHistoryEntityIntegerFilterSpecifications, eventHistoryEntityStringFilterSpecifications, eventHistoryEntityLongFilterSpecifications, uuidFilterSpecifications, converters); + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterCriteria.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterCriteria.java new file mode 100644 index 00000000..0f407f22 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterCriteria.java @@ -0,0 +1,179 @@ +package ca.bc.gov.educ.api.trax.filter; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.lang.NonNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Filter Criteria Holder + * + * @param is the java type of the DB table column + * @author om + */ +public class FilterCriteria> { + + /** + * Holds the operation {@link FilterOperation} + */ + private final FilterOperation operation; + + /** + * Table column name + */ + private final String fieldName; + + /** + * Holds the Function to convertString to + */ + private final Function converterFunction; + /** + * Holds the filter criteria + */ + private final Collection originalValues; + /** + * Holds the filter criteria as type + */ + private final Collection convertedValues; + /** + * Converted value + */ + private T convertedSingleValue; + /** + * minimum value - application only for {@link FilterOperation#BETWEEN} + */ + private T minValue; + /** + * maximum value - application only for {@link FilterOperation#BETWEEN} + */ + private T maxValue; + + /** + * Instantiates a new Filter criteria. + * + * @param fieldName the field name + * @param fieldValue the field value + * @param filterOperation the filter operation + * @param converterFunction the converter function + */ + public FilterCriteria(@NonNull String fieldName, String fieldValue, @NonNull FilterOperation filterOperation, Function converterFunction) { + + this.fieldName = fieldName; + this.converterFunction = converterFunction; + + String[] operationValues; + + if (filterOperation == FilterOperation.BETWEEN || filterOperation == FilterOperation.IN || filterOperation == FilterOperation.NOT_IN) { + if (fieldValue != null) { + // Split the fieldValue value as comma separated. + operationValues = StringUtils.split(fieldValue, ","); + } else { + operationValues = new String[]{null}; + } + if (operationValues.length < 1) { + throw new IllegalArgumentException("multiple values expected(comma separated) for IN, NOT IN and BETWEEN operations."); + } + } else { + operationValues = new String[]{fieldValue}; + } + this.operation = filterOperation; + this.originalValues = Arrays.asList(operationValues); + this.convertedValues = new ArrayList<>(); + + // Validate other conditions + validateAndAssign(operationValues); + + } + + private void validateAndAssign(String[] operationValues) { + + //For operation 'btn' + if (FilterOperation.BETWEEN == operation) { + if (operationValues.length != 2) { + throw new IllegalArgumentException("For 'btn' operation two values are expected"); + } else { + + //Convert + T value1 = this.converterFunction.apply(operationValues[0]); + T value2 = this.converterFunction.apply(operationValues[1]); + + //Set min and max values + if (value1.compareTo(value2) > 0) { + this.minValue = value2; + this.maxValue = value1; + } else { + this.minValue = value1; + this.maxValue = value2; + } + } + + //For 'in' or 'nin' operation + } else if (FilterOperation.IN == operation || FilterOperation.NOT_IN == operation) { + convertedValues.addAll(originalValues.stream().map(converterFunction).collect(Collectors.toList())); + } else { + //All other operation + this.convertedSingleValue = converterFunction.apply(operationValues[0]); + } + + } + + /** + * Gets converted single value. + * + * @return the converted single value + */ + public T getConvertedSingleValue() { + return convertedSingleValue; + } + + /** + * Gets min value. + * + * @return the min value + */ + public T getMinValue() { + return minValue; + } + + /** + * Gets max value. + * + * @return the max value + */ + public T getMaxValue() { + return maxValue; + } + + /** + * Gets operation. + * + * @return the operation + */ + public FilterOperation getOperation() { + return operation; + } + + /** + * Gets field name. + * + * @return the field name + */ + public String getFieldName() { + return fieldName; + } + + + /** + * Gets converted values. + * + * @return the converted values + */ + public Collection getConvertedValues() { + return convertedValues; + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterOperation.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterOperation.java new file mode 100644 index 00000000..03fe3b20 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterOperation.java @@ -0,0 +1,78 @@ +package ca.bc.gov.educ.api.trax.filter; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum FilterOperation { + /** + * Equal filter operation. + */ + EQUAL("eq"), + /** + * Not equal filter operation. + */ + NOT_EQUAL("neq"), + /** + * Greater than filter operation. + */ + GREATER_THAN("gt"), + /** + * Greater than or equal to filter operation. + */ + GREATER_THAN_OR_EQUAL_TO("gte"), + /** + * Less than filter operation. + */ + LESS_THAN("lt"), + /** + * Less than or equal to filter operation. + */ + LESS_THAN_OR_EQUAL_TO("lte"), + /** + * In filter operation. + */ + IN("in"), + /** + * Not in filter operation. + */ + NOT_IN("nin"), + /** + * Between filter operation. + */ + BETWEEN("btn"), + /** + * Contains filter operation. + */ + CONTAINS("like"), + /** + * Starts with filter operation. + */ + STARTS_WITH("starts_with"), + /** + * Not Starts with filter operation. + */ + NOT_STARTS_WITH("not_starts_with"), + /** + * Ends with filter operation. + */ + ENDS_WITH("ends_with"), + /** + * Starts with ignore case filter operation. + */ + STARTS_WITH_IGNORE_CASE("starts_with_ignore_case"), + /** + * Contains ignore case filter operation. + */ + CONTAINS_IGNORE_CASE("like_ignore_case"), + ; + private final String value; + + FilterOperation(String value) { + this.value = value; + } + + @Override + @JsonValue + public String toString() { + return String.valueOf(value); + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterSpecifications.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterSpecifications.java new file mode 100644 index 00000000..16f498c7 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterSpecifications.java @@ -0,0 +1,107 @@ +package ca.bc.gov.educ.api.trax.filter; + +import jakarta.annotation.PostConstruct; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; + +import java.util.EnumMap; +import java.util.function.Function; + +/** + * The type Filter specifications. + * + * @param the type parameter + * @param the type parameter + */ +@Service +public class FilterSpecifications> { + + private EnumMap, Specification>> map; + + /** + * Instantiates a new Filter specifications. + */ + public FilterSpecifications() { + initSpecifications(); + } + + /** + * Gets specification. + * + * @param operation the operation + * @return the specification + */ + public Function, Specification> getSpecification(FilterOperation operation) { + return map.get(operation); + } + + /** + * Init specifications. + */ + @PostConstruct + public void initSpecifications() { + + map = new EnumMap<>(FilterOperation.class); + + // Equal + map.put(FilterOperation.EQUAL, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> { + if(filterCriteria.getConvertedSingleValue() == null){ + return criteriaBuilder.isNull(root.get(filterCriteria.getFieldName())); + } + return criteriaBuilder + .equal(root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue()); + }); + + map.put(FilterOperation.NOT_EQUAL, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> { + if(filterCriteria.getConvertedSingleValue() == null){ + return criteriaBuilder.isNotNull(root.get(filterCriteria.getFieldName())); + } + return criteriaBuilder + .notEqual(root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue()); + }); + + map.put(FilterOperation.GREATER_THAN, + filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.greaterThan( + root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue())); + + map.put(FilterOperation.GREATER_THAN_OR_EQUAL_TO, + filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo( + root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue())); + + map.put(FilterOperation.LESS_THAN, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder + .lessThan(root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue())); + + map.put(FilterOperation.LESS_THAN_OR_EQUAL_TO, + filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo( + root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue())); + + map.put(FilterOperation.IN, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> root + .get(filterCriteria.getFieldName()).in(filterCriteria.getConvertedValues())); + + map.put(FilterOperation.NOT_IN, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder + .not(root.get(filterCriteria.getFieldName()).in(filterCriteria.getConvertedValues()))); + + map.put(FilterOperation.BETWEEN, + filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.between( + root.get(filterCriteria.getFieldName()), filterCriteria.getMinValue(), + filterCriteria.getMaxValue())); + + map.put(FilterOperation.CONTAINS, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder + .like(root.get(filterCriteria.getFieldName()), "%" + filterCriteria.getConvertedSingleValue() + "%")); + + map.put(FilterOperation.STARTS_WITH, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder + .like(root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue() + "%")); + + map.put(FilterOperation.NOT_STARTS_WITH, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder + .notLike(root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue() + "%")); + + map.put(FilterOperation.ENDS_WITH, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder + .like(root.get(filterCriteria.getFieldName()), "%" + filterCriteria.getConvertedSingleValue())); + + map.put(FilterOperation.CONTAINS_IGNORE_CASE, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder + .like(criteriaBuilder.lower(root.get(filterCriteria.getFieldName())), "%" + filterCriteria.getConvertedSingleValue().toString().toLowerCase() + "%")); + + map.put(FilterOperation.STARTS_WITH_IGNORE_CASE, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder + .like(criteriaBuilder.lower(root.get(filterCriteria.getFieldName())), filterCriteria.getConvertedSingleValue().toString().toLowerCase() + "%")); + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventHistoryMapper.java b/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventHistoryMapper.java new file mode 100644 index 00000000..be383ddc --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventHistoryMapper.java @@ -0,0 +1,90 @@ +package ca.bc.gov.educ.api.trax.mapper; + +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.model.dto.AuthorityContact; +import ca.bc.gov.educ.api.trax.model.dto.DistrictContact; +import ca.bc.gov.educ.api.trax.model.dto.EventHistory; +import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; +import ca.bc.gov.educ.api.trax.model.dto.institute.MoveSchoolData; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; +import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; +import ca.bc.gov.educ.api.trax.util.JsonUtil; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.springframework.beans.factory.annotation.Autowired; + +@Slf4j +@Mapper(componentModel = "spring", uses = {EventMapper.class, UUIDMapper.class}) +public abstract class EventHistoryMapper { + + EducGradTraxApiConstants constants; + + @Autowired + public void setConstants(EducGradTraxApiConstants constants){ + this.constants = constants; + } + + @Mapping(source = "event", target = "eventHistoryUrl", qualifiedByName = "getUrlFromEventHistoryEntity") + public abstract EventHistory toStructure(EventHistoryEntity eventHistoryEntity); + + @Mapping(target = "event.eventPayloadBytes", ignore = true) + public abstract EventHistoryEntity toEntity(EventHistory eventHistory); + + @Named("getUrlFromEventHistoryEntity") + String getUrlFromEventHistoryEntity(EventEntity eventEntity) { + String url = null; + if (eventEntity != null) { + try { + switch (EventType.valueOf(eventEntity.getEventType())) { + case CREATE_SCHOOL, UPDATE_SCHOOL -> { + val school = JsonUtil.getJsonObjectFromString(ca.bc.gov.educ.api.trax.model.dto.institute.School.class, eventEntity.getEventPayload()); + url = this.getStudentAdminSchoolDetailsUrl(school.getSchoolId()); + } + case MOVE_SCHOOL -> { + val schoolMoved = JsonUtil.getJsonObjectFromString(MoveSchoolData.class, eventEntity.getEventPayload()); + url = this.getStudentAdminSchoolDetailsUrl(schoolMoved.getToSchool().getSchoolId()); + } + case CREATE_SCHOOL_CONTACT, UPDATE_SCHOOL_CONTACT, DELETE_SCHOOL_CONTACT -> { + val schoolContact = JsonUtil.getJsonObjectFromString(SchoolContact.class, eventEntity.getEventPayload()); + url = this.getStudentAdminSchoolDetailsUrl(schoolContact.getSchoolId()); + } + case CREATE_DISTRICT, UPDATE_DISTRICT -> { + val district = JsonUtil.getJsonObjectFromString(ca.bc.gov.educ.api.trax.model.dto.institute.District.class, eventEntity.getEventPayload()); + url = this.getStudentAdminDistrictDetailsUrl(district.getDistrictId()); + } + case CREATE_AUTHORITY_CONTACT, UPDATE_AUTHORITY_CONTACT, DELETE_AUTHORITY_CONTACT -> { + val authorityContact = JsonUtil.getJsonObjectFromString(AuthorityContact.class, eventEntity.getEventPayload()); + url = this.getStudentAdminAuthorityDetailsUrl(authorityContact.getIndependentAuthorityId()); + } + case CREATE_DISTRICT_CONTACT, UPDATE_DISTRICT_CONTACT, DELETE_DISTRICT_CONTACT -> { + val districtContact = JsonUtil.getJsonObjectFromString(DistrictContact.class, eventEntity.getEventPayload()); + url = this.getStudentAdminDistrictDetailsUrl(districtContact.getDistrictId()); + } + default -> { + return null; + } + } + } catch (final Exception exception) { + log.error(exception.getMessage()); + } + } + return url; + } + + private String getStudentAdminSchoolDetailsUrl(String schoolId) { + return String.format(constants.getStudentAdminSchoolDetailsUrl(), schoolId); + } + + private String getStudentAdminAuthorityDetailsUrl(String authorityId) { + return String.format(constants.getStudentAdminAuthorityDetailsUrl(), authorityId); + } + + private String getStudentAdminDistrictDetailsUrl(String districtId) { + return String.format(constants.getStudentAdminDistrictDetailsUrl(), districtId); + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventMapper.java b/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventMapper.java new file mode 100644 index 00000000..f9b3cff3 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventMapper.java @@ -0,0 +1,19 @@ +package ca.bc.gov.educ.api.trax.mapper; + +import ca.bc.gov.educ.api.trax.model.dto.Event; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper(componentModel = "spring", uses = {UUIDMapper.class}) +public interface EventMapper { + + EventMapper mapper = Mappers.getMapper(EventMapper.class); + + @Mapping(target = "eventPayload", ignore = true) + Event toStructure(EventEntity eventEntity); + + EventEntity toEntity(Event event); + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/UUIDMapper.java b/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/UUIDMapper.java new file mode 100644 index 00000000..3b735971 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/UUIDMapper.java @@ -0,0 +1,22 @@ +package ca.bc.gov.educ.api.trax.mapper; + +import org.apache.commons.lang3.StringUtils; + +import java.util.UUID; + +public class UUIDMapper { + + public UUID map(String value) { + if (StringUtils.isBlank(value)) { + return null; + } + return UUID.fromString(value); + } + + public String map(UUID value) { + if (value == null) { + return null; + } + return value.toString(); + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/BaseModel.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/BaseModel.java index 5b3d5120..314337bc 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/BaseModel.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/BaseModel.java @@ -1,11 +1,13 @@ package ca.bc.gov.educ.api.trax.model.dto; +import jakarta.validation.constraints.NotBlank; import lombok.Data; @Data public class BaseModel { private String createUser; private String createDate; + @NotBlank(message = "updateUser must not be null or empty") private String updateUser; private String updateDate; } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Condition.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Condition.java new file mode 100644 index 00000000..50d15cd2 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Condition.java @@ -0,0 +1,12 @@ +package ca.bc.gov.educ.api.trax.model.dto; + +public enum Condition { + /** + * And condition. + */ + AND, + /** + * Or condition. + */ + OR +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Event.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Event.java new file mode 100644 index 00000000..f45058f8 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Event.java @@ -0,0 +1,19 @@ +package ca.bc.gov.educ.api.trax.model.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.UUID; + +@EqualsAndHashCode(callSuper = true) +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Event extends BaseModel { + private UUID replicationEventId; + private UUID eventId; + private String eventPayload; + private String eventStatus; + private String eventType; + private String eventOutcome; + private String activityCode; +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/EventHistory.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/EventHistory.java new file mode 100644 index 00000000..4fca7d8f --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/EventHistory.java @@ -0,0 +1,25 @@ +package ca.bc.gov.educ.api.trax.model.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.UUID; + +@EqualsAndHashCode(callSuper = true) +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class EventHistory extends BaseModel { + @NotNull(message = "id can not be null.") + private UUID id; + private Event event; + private String eventHistoryUrl; + @Pattern(regexp = "^[Yy|Nn]$", message = "acknowledge flag must be either Y or N.") + private String acknowledgeFlag; + + public void setAcknowledgeFlag(String acknowledgeFlag){ + this.acknowledgeFlag = acknowledgeFlag.toUpperCase(); + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Search.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Search.java new file mode 100644 index 00000000..73dc4e39 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Search.java @@ -0,0 +1,24 @@ +package ca.bc.gov.educ.api.trax.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class Search { + /** + * The Condition. ENUM to hold and AND OR + */ + Condition condition; + + /** + * The Search criteria list. + */ + List searchCriteriaList; +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/SearchCriteria.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/SearchCriteria.java new file mode 100644 index 00000000..ed7b8375 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/SearchCriteria.java @@ -0,0 +1,39 @@ +package ca.bc.gov.educ.api.trax.model.dto; + +import ca.bc.gov.educ.api.trax.filter.FilterOperation; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Data +public class SearchCriteria { + /** + * The Key. + */ + @NotNull + String key; + /** + * The Operation. + */ + @NotNull + FilterOperation operation; + /** + * The Value. + */ + String value; + /** + * The Value type. + */ + @NotNull + ValueType valueType; + /** + * The Condition. ENUM to hold and AND OR + */ + Condition condition; + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ValueType.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ValueType.java new file mode 100644 index 00000000..25f8148f --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ValueType.java @@ -0,0 +1,28 @@ +package ca.bc.gov.educ.api.trax.model.dto; + +public enum ValueType { + /** + * String value type. + */ + STRING, + /** + * Integer value type. + */ + INTEGER, + /** + * Long value type. + */ + LONG, + /** + * Date value type. + */ + DATE, + /** + * Date time value type. + */ + DATE_TIME, + /** + * Uuid value type. + */ + UUID +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java index ed6bd837..c9467b5e 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java @@ -1,5 +1,6 @@ package ca.bc.gov.educ.api.trax.model.entity; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.*; import org.hibernate.annotations.DynamicUpdate; import org.hibernate.annotations.GenericGenerator; @@ -58,6 +59,7 @@ public class EventEntity { /** * The EventEntity payload. */ + @JsonIgnore @NotNull(message = "eventPayload cannot be null") @Lob @Column(name = "EVENT_PAYLOAD") @@ -88,6 +90,7 @@ public class EventEntity { @Column(name = "ACTIVITY_CODE") private String activityCode; + @JsonIgnore @OneToOne(cascade = CascadeType.ALL, mappedBy = "event") private EventHistoryEntity eventHistoryEntity; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java index a02234c1..22e27517 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java @@ -4,15 +4,17 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -import lombok.Getter; -import lombok.Setter; +import lombok.*; +import lombok.experimental.SuperBuilder; import org.hibernate.annotations.DynamicUpdate; import org.hibernate.annotations.GenericGenerator; import java.util.UUID; -@Getter -@Setter +@Data +@NoArgsConstructor +@AllArgsConstructor +@SuperBuilder @Entity @Table(name = "EVENT_HISTORY") @DynamicUpdate diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java index 0e3400ae..7f4164cc 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java @@ -4,12 +4,18 @@ import ca.bc.gov.educ.api.trax.util.ThreadLocalStateUtil; import jakarta.persistence.*; import jakarta.validation.constraints.PastOrPresent; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; import org.apache.commons.lang3.StringUtils; import java.time.LocalDateTime; @Data +@NoArgsConstructor +@AllArgsConstructor +@SuperBuilder @MappedSuperclass public class BaseEntity { @@ -43,7 +49,10 @@ protected void onPersist() { private void initUserInfo() { String user = ThreadLocalStateUtil.getCurrentUser(); - this.updateUser = (StringUtils.isBlank(user)) ? EducGradTraxApiConstants.DEFAULT_UPDATED_BY : user; + if(this.updateUser == null){ + this.updateUser = (StringUtils.isBlank(user)) ? EducGradTraxApiConstants.DEFAULT_UPDATED_BY : user; + } this.createUser = (StringUtils.isBlank(createUser) && StringUtils.isBlank(user)) ? EducGradTraxApiConstants.DEFAULT_CREATED_BY : user; } + } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java index fe011d1d..e8494da8 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java @@ -2,13 +2,14 @@ import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; import java.util.Optional; import java.util.UUID; @Repository -public interface EventHistoryRepository extends JpaRepository { +public interface EventHistoryRepository extends JpaRepository, JpaSpecificationExecutor { Optional findByEvent_ReplicationEventId(UUID replicationEventId); diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java index 57eae2a1..737a40a3 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java @@ -1,24 +1,67 @@ package ca.bc.gov.educ.api.trax.service; +import ca.bc.gov.educ.api.trax.exception.EntityNotFoundException; +import ca.bc.gov.educ.api.trax.exception.InvalidParameterException; import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.exception.TraxAPIRuntimeException; +import ca.bc.gov.educ.api.trax.filter.EventHistoryFilterSpecifics; +import ca.bc.gov.educ.api.trax.filter.FilterOperation; +import ca.bc.gov.educ.api.trax.mapper.EventHistoryMapper; +import ca.bc.gov.educ.api.trax.model.dto.*; +import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; +import ca.bc.gov.educ.api.trax.repository.EventHistoryRepository; import ca.bc.gov.educ.api.trax.repository.EventRepository; +import ca.bc.gov.educ.api.trax.util.RequestUtil; +import ca.bc.gov.educ.api.trax.util.TransformUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.jboss.threads.EnhancedQueueExecutor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import java.time.Duration; import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.Executor; @Slf4j @Service public class EventHistoryService { - private EventRepository eventRepository; + private final EventHistoryMapper mapper; + private final EventHistoryFilterSpecifics eventHistoryFilterSpecs; + private final EventRepository eventRepository; + private final EventHistoryRepository eventHistoryRepository; + private final Executor paginatedQueryExecutor = new EnhancedQueueExecutor.Builder() + .setThreadFactory(new ThreadFactoryBuilder().setNameFormat("async-pagination-query-executor-%d").build()) + .setCorePoolSize(2).setMaximumPoolSize(10).setKeepAliveTime(Duration.ofSeconds(60)).build(); @Autowired - public EventHistoryService(EventRepository eventRepository) { + public EventHistoryService(EventHistoryMapper mapper, EventHistoryFilterSpecifics eventHistoryFilterSpecs, EventRepository eventRepository, EventHistoryRepository eventHistoryRepository) { + this.mapper = mapper; + this.eventHistoryFilterSpecs = eventHistoryFilterSpecs; this.eventRepository = eventRepository; + this.eventHistoryRepository = eventHistoryRepository; } + /** + * Deletes old records from event and event history + * @param sinceBefore the LocalDateTime object. Removes records older than this. + * @throws ServiceException if there is an issue + */ public void purgeOldEventAndEventHistoryRecords(LocalDateTime sinceBefore) throws ServiceException { try { this.eventRepository.deleteByCreateDateLessThan(sinceBefore); @@ -27,5 +70,134 @@ public void purgeOldEventAndEventHistoryRecords(LocalDateTime sinceBefore) throw } } + private Specification getSpecifications(Specification schoolSpecs, int i, Search search) { + if (i == 0) { + schoolSpecs = getEventHistorySpecification(search.getSearchCriteriaList()); + } else { + if (search.getCondition() == Condition.AND) { + schoolSpecs = schoolSpecs.and(getEventHistorySpecification(search.getSearchCriteriaList())); + } else { + schoolSpecs = schoolSpecs.or(getEventHistorySpecification(search.getSearchCriteriaList())); + } + } + return schoolSpecs; + } + + public Specification setSpecificationAndSortCriteria(String sortCriteriaJson, String searchCriteriaListJson, ObjectMapper objectMapper, List sorts) { + Specification eventHistoryEntitySpecification = null; + try { + RequestUtil.getSortCriteria(sortCriteriaJson, objectMapper, sorts); + if (StringUtils.isNotBlank(searchCriteriaListJson)) { + List searches = objectMapper.readValue(searchCriteriaListJson, new TypeReference<>() { + }); + int i = 0; + for (var search : searches) { + eventHistoryEntitySpecification = getSpecifications(eventHistoryEntitySpecification, i, search); + i++; + } + } + } catch (JsonProcessingException e) { + throw new TraxAPIRuntimeException(e.getMessage()); + } + return eventHistoryEntitySpecification; + } + private Specification getEventHistorySpecification(List criteriaList) { + Specification eventHistoryEntitySpecification = null; + if (!criteriaList.isEmpty()) { + int i = 0; + for (SearchCriteria criteria : criteriaList) { + if (criteria.getKey() != null && criteria.getOperation() != null && criteria.getValueType() != null) { + var criteriaValue = criteria.getValue(); + if(StringUtils.isNotBlank(criteria.getValue()) && TransformUtil.isUppercaseField(EventHistoryEntity.class, criteria.getKey())) { + criteriaValue = criteriaValue.toUpperCase(); + } + Specification typeSpecification = getTypeSpecification(criteria.getKey(), criteria.getOperation(), criteriaValue, criteria.getValueType()); + eventHistoryEntitySpecification = getSpecificationPerGroup(eventHistoryEntitySpecification, i, criteria, typeSpecification); + i++; + } else { + throw new InvalidParameterException("Search Criteria can not contain null values for key, value and operation type"); + } + } + } + return eventHistoryEntitySpecification; + } + + private Specification getSpecificationPerGroup(Specification eventHistoryEntitySpecification, int i, SearchCriteria criteria, Specification typeSpecification) { + if (i == 0) { + eventHistoryEntitySpecification = Specification.where(typeSpecification); + } else { + if (criteria.getCondition() == Condition.AND) { + eventHistoryEntitySpecification = eventHistoryEntitySpecification.and(typeSpecification); + } else { + eventHistoryEntitySpecification = eventHistoryEntitySpecification.or(typeSpecification); + } + } + return eventHistoryEntitySpecification; + } + + private Specification getTypeSpecification(String key, FilterOperation filterOperation, String value, ValueType valueType) { + Specification schoolEntitySpecification = null; + switch (valueType) { + case STRING: + schoolEntitySpecification = eventHistoryFilterSpecs.getStringTypeSpecification(key, value, filterOperation); + break; + case DATE_TIME: + schoolEntitySpecification = eventHistoryFilterSpecs.getDateTimeTypeSpecification(key, value, filterOperation); + break; + case LONG: + schoolEntitySpecification = eventHistoryFilterSpecs.getLongTypeSpecification(key, value, filterOperation); + break; + case INTEGER: + schoolEntitySpecification = eventHistoryFilterSpecs.getIntegerTypeSpecification(key, value, filterOperation); + break; + case DATE: + schoolEntitySpecification = eventHistoryFilterSpecs.getDateTypeSpecification(key, value, filterOperation); + break; + case UUID: + schoolEntitySpecification = eventHistoryFilterSpecs.getUUIDTypeSpecification(key, value, filterOperation); + break; + default: + break; + } + return schoolEntitySpecification; + } + + + @Transactional(propagation = Propagation.SUPPORTS) + public CompletableFuture> findAll(Specification eventHistorySpecs, final Integer pageNumber, final Integer pageSize, final List sorts) { + log.trace("In find all query: {}", eventHistorySpecs); + return CompletableFuture.supplyAsync(() -> { + Pageable paging = PageRequest.of(pageNumber, pageSize, Sort.by(sorts)); + try { + log.trace("Running paginated query: {}", eventHistorySpecs); + var results = this.eventHistoryRepository.findAll(eventHistorySpecs, paging); + log.trace("Paginated query returned with results: {}", results); + return results; + } catch (final Throwable ex) { + log.error("Failure querying for paginated schools: {}", ex.getMessage()); + throw new CompletionException(ex); + } + }, paginatedQueryExecutor); + + } + + /** + * Updates the event history. Currently, the only fields that will update are + * updateUser and acknowledgeFlag + * @param eventHistory the history event you want to update + * @return the updated history event + */ + public EventHistory updateEventHistory(EventHistory eventHistory) { + EventHistoryEntity eventHistoryEntity = eventHistoryRepository.findById(eventHistory.getId()).orElse(null); + if(eventHistoryEntity != null){ + eventHistoryEntity.setUpdateUser(eventHistory.getUpdateUser()); + eventHistoryEntity.setUpdateDate(LocalDateTime.now()); + eventHistoryEntity.setAcknowledgeFlag(eventHistory.getAcknowledgeFlag()); + eventHistoryRepository.save(eventHistoryEntity); + return mapper.toStructure(eventHistoryEntity); + } else { + throw new EntityNotFoundException(String.format("EventHistory with id: %s not found", eventHistory.getId())); + } + } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java index 3dd06d0c..abcc5051 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java @@ -87,6 +87,9 @@ public class EducGradTraxApiConstants { public static final String POST_SAVE_TRAX_STUDENT_NO_MAPPING = "/trax-student-no"; public static final String DELETE_TRAX_STUDENT_NO_MAPPING = "/trax-student-no/{pen}"; + // Event urls + public static final String EVENT_HISTORY_MAPPING_V1 = GRAD_TRAX_API_ROOT_MAPPING_V1 + "/event/history"; + //Default Attribute value constants public static final String DEFAULT_CREATED_BY = "API_GRAD_TRAX"; protected static final Date DEFAULT_CREATED_TIMESTAMP = new Date(); @@ -175,6 +178,15 @@ public class EducGradTraxApiConstants { @Value("${endpoint.institute-api.get-all-school-funding-group-codes.url}") private String allSchoolFundingGroupCodesFromInstituteApiUrl; + @Value("${endpoint.student-admin.school-details.url}") + private String studentAdminSchoolDetailsUrl; + + @Value("${endpoint.student-admin.district-details.url}") + private String studentAdminDistrictDetailsUrl; + + @Value("${endpoint.student-admin.authority-details.url}") + private String studentAdminAuthorityDetailsUrl; + // Scheduler: ongoing updates from TRAX to GRAD @Value("${cron.scheduled.process.events.trax-to-grad.run}") private String traxToGradCronRun; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/RequestUtil.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/RequestUtil.java new file mode 100644 index 00000000..96cf4101 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/util/RequestUtil.java @@ -0,0 +1,39 @@ +package ca.bc.gov.educ.api.trax.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Sort; + +import java.util.List; +import java.util.Map; + +public class RequestUtil { + + private RequestUtil() { + } + + /** + * Get the Sort.Order list from JSON string + * + * @param sortCriteriaJson The sort criterio JSON + * @param objectMapper The object mapper + * @param sorts The Sort.Order list + * @throws JsonProcessingException the json processing exception + */ + public static void getSortCriteria(String sortCriteriaJson, ObjectMapper objectMapper, List sorts) throws JsonProcessingException { + if (StringUtils.isNotBlank(sortCriteriaJson)) { + Map sortMap = objectMapper.readValue(sortCriteriaJson, new TypeReference<>() { + }); + sortMap.forEach((k, v) -> { + if ("ASC".equalsIgnoreCase(v)) { + sorts.add(new Sort.Order(Sort.Direction.ASC, k)); + } else { + sorts.add(new Sort.Order(Sort.Direction.DESC, k)); + } + }); + } + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/TransformUtil.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/TransformUtil.java new file mode 100644 index 00000000..76ccf040 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/util/TransformUtil.java @@ -0,0 +1,62 @@ +package ca.bc.gov.educ.api.trax.util; + +import ca.bc.gov.educ.api.trax.exception.TraxAPIRuntimeException; + +import java.beans.Expression; +import java.beans.Statement; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.springframework.util.StringUtils.capitalize; + +/** + * The type Transform util. + */ +public class TransformUtil { + + private TransformUtil() { + } + + /** + * Is uppercase field boolean. + * + * @param clazz the clazz + * @param fieldName the field name + * @return the boolean + */ + public static boolean isUppercaseField(Class clazz, String fieldName) { + var superClazz = clazz; + while (!superClazz.equals(Object.class)) { + try { + Field field = superClazz.getDeclaredField(fieldName); + return field.getAnnotation(UpperCase.class) != null; + } catch (NoSuchFieldException e) { + superClazz = superClazz.getSuperclass(); + } + } + return false; + } + + private static void transformFieldToUppercase(Field field, T claz) { + if (!field.getType().equals(String.class)) { + return; + } + + if (field.getAnnotation(UpperCase.class) != null) { + try { + var fieldName = capitalize(field.getName()); + var expr = new Expression(claz, "get" + fieldName, new Object[0]); + var entityFieldValue = (String) expr.getValue(); + if (entityFieldValue != null) { + var stmt = new Statement(claz, "set" + fieldName, new Object[]{entityFieldValue.toUpperCase()}); + stmt.execute(); + } + } catch (Exception ex) { + throw new TraxAPIRuntimeException(ex.getMessage()); + } + } + + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/UpperCase.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/UpperCase.java new file mode 100644 index 00000000..28951128 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/util/UpperCase.java @@ -0,0 +1,11 @@ +package ca.bc.gov.educ.api.trax.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface UpperCase { +} diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml index 3424dc39..ddea4b2a 100644 --- a/api/src/main/resources/application.yaml +++ b/api/src/main/resources/application.yaml @@ -194,6 +194,13 @@ endpoint: url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/category-codes get-all-school-funding-group-codes: url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/funding-group-codes + student-admin: + school-details: + url: ${STUDENT_ADMIN_URL_ROOT}institute/school/%s/details + district-details: + url: ${STUDENT_ADMIN_URL_ROOT}district/%s + authority-details: + url: ${STUDENT_ADMIN_URL_ROOT}authority/%s # other properties props: diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/controller/EventHistoryControllerTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/controller/EventHistoryControllerTest.java new file mode 100644 index 00000000..1b3eb304 --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/controller/EventHistoryControllerTest.java @@ -0,0 +1,168 @@ +package ca.bc.gov.educ.api.trax.controller; + +import ca.bc.gov.educ.api.trax.EducGradTraxApiApplication; +import ca.bc.gov.educ.api.trax.filter.FilterOperation; +import ca.bc.gov.educ.api.trax.mapper.EventHistoryMapper; +import ca.bc.gov.educ.api.trax.model.dto.EventHistory; +import ca.bc.gov.educ.api.trax.model.dto.Search; +import ca.bc.gov.educ.api.trax.model.dto.SearchCriteria; +import ca.bc.gov.educ.api.trax.model.dto.ValueType; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.repository.EventHistoryRepository; +import ca.bc.gov.educ.api.trax.repository.EventRepository; +import ca.bc.gov.educ.api.trax.support.TestUtils; +import ca.bc.gov.educ.api.trax.util.BaseEventHistoryTest; +import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; +import ca.bc.gov.educ.api.trax.util.JsonUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import java.time.LocalDateTime; +import java.util.*; + +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin; + +@SpringBootTest(classes = { EducGradTraxApiApplication.class }) +@ActiveProfiles("test") +@AutoConfigureMockMvc +class EventHistoryControllerTest extends BaseEventHistoryTest { + + private static final String READ_SCOPE = "SCOPE_READ_EVENT_HISTORY"; + private static final String WRITE_SCOPE = "SCOPE_WRITE_EVENT_HISTORY"; + private AutoCloseable closeable; + + @Autowired + private EventHistoryMapper mapper; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private EventHistoryRepository eventHistoryRepository; + + @Autowired + private EventRepository eventRepository; + + + @BeforeEach + void setUp() { + closeable = MockitoAnnotations.openMocks(this); + EventEntity event = this.eventRepository.save(this.createEventData()); + this.eventHistoryRepository.save(this.createEventHistoryData(event)); + } + + @AfterEach + void tearDown() throws Exception { + this.eventRepository.deleteAll(); + closeable.close(); + } + + @Test + void testReadEventHistoryPaginated_givenValueNull_ShouldReturnStatusOk() throws Exception { + final SearchCriteria criteria = SearchCriteria.builder().key("website").operation(FilterOperation.EQUAL).value(null).valueType(ValueType.STRING).build(); + final List criteriaList = new ArrayList<>(); + criteriaList.add(criteria); + final List searches = new LinkedList<>(); + searches.add(Search.builder().searchCriteriaList(criteriaList).build()); + final ObjectMapper objectMapper = new ObjectMapper(); + final String criteriaJSON = objectMapper.writeValueAsString(searches); + this.mockMvc.perform(get(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1 + "/paginated").with(getMockAuthority(READ_SCOPE)).param("searchCriteriaList", criteriaJSON) + .contentType(APPLICATION_JSON)).andDo(print()).andExpect(status().isOk()); + } + + @Test + void testReadEventHistoryPaginated_givenUserNameFilter_ShouldReturnStatusOk() throws Exception { + final ObjectMapper objectMapper = new ObjectMapper(); + final SearchCriteria criteria = SearchCriteria.builder().key("createUser").operation(FilterOperation.EQUAL).value(USER).valueType(ValueType.STRING).build(); + final List criteriaList = new ArrayList<>(); + criteriaList.add(criteria); + final List searches = new LinkedList<>(); + searches.add(Search.builder().searchCriteriaList(criteriaList).build()); + final String criteriaJSON = objectMapper.writeValueAsString(searches); + final MvcResult result = this.mockMvc + .perform(get(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1 + "/paginated").with(getMockAuthority(READ_SCOPE)).param("searchCriteriaList", criteriaJSON) + .contentType(APPLICATION_JSON)) + .andReturn(); + this.mockMvc.perform(asyncDispatch(result)).andDo(print()).andExpect(status().isOk()).andExpect(jsonPath("$.content", hasSize(1))); + } + + // test update goes ok + @Test + void testUpdateEventHistory_givenValidObject_shouldReturnStatusOk() throws Exception { + final String acknowledgeFlag = "Y"; + final String updateUser = "Wattie"; + var eventHistory = createEventHistoryData(); + eventHistory.setAcknowledgeFlag(acknowledgeFlag); + eventHistory.setUpdateUser(updateUser); + this.mockMvc.perform(MockMvcRequestBuilders + .put(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1) + .with(getMockAuthority(WRITE_SCOPE)) + .content(JsonUtil.getJsonStringFromObject(eventHistory)) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$.acknowledgeFlag").value(acknowledgeFlag)) + .andExpect(MockMvcResultMatchers.jsonPath("$.updateUser").value(updateUser)); + } + + // test update returns bad request + @Test + void testUpdateEventHistory_givenBadDate_shouldReturnStatusBadRequest() throws Exception { + var eventHistory = createEventHistoryData(); + eventHistory.setAcknowledgeFlag("Q"); + eventHistory.setUpdateUser(null); + this.mockMvc.perform(MockMvcRequestBuilders + .put(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1) + .with(getMockAuthority(WRITE_SCOPE)) + .content(JsonUtil.getJsonStringFromObject(eventHistory)) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + // test update not found + @Test + void testUpdateEventHistory_givenInvalidId_shouldReturnStatusNotFound() throws Exception { + var eventHistory = createEventHistoryData(); + eventHistory.setId(UUID.randomUUID()); + this.mockMvc.perform(MockMvcRequestBuilders + .put(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1) + .with(getMockAuthority(WRITE_SCOPE)) + .content(JsonUtil.getJsonStringFromObject(eventHistory)) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + private EventHistory createEventHistoryData() throws JsonProcessingException { + var event = TestUtils.createEvent("DELETE_DISTRICT_CONTACT", TestUtils.createDistrictContact(), LocalDateTime.now(), eventRepository); + var eventHistory = TestUtils.createEventHistory(event, LocalDateTime.now(), eventHistoryRepository); + return mapper.toStructure(eventHistory); + } + + private SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor getMockAuthority(String scope) { + final GrantedAuthority grantedAuthority = () -> scope; + return oidcLogin().authorities(grantedAuthority); + } +} \ No newline at end of file diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/mapper/MapperTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/mapper/MapperTest.java new file mode 100644 index 00000000..f2b19418 --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/mapper/MapperTest.java @@ -0,0 +1,123 @@ +package ca.bc.gov.educ.api.trax.mapper; + +import ca.bc.gov.educ.api.trax.EducGradTraxApiApplication; +import ca.bc.gov.educ.api.trax.constant.EventType; +import ca.bc.gov.educ.api.trax.model.dto.AuthorityContact; +import ca.bc.gov.educ.api.trax.model.dto.EventHistory; +import ca.bc.gov.educ.api.trax.model.dto.institute.District; +import ca.bc.gov.educ.api.trax.model.dto.institute.MoveSchoolData; +import ca.bc.gov.educ.api.trax.model.dto.institute.School; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; +import ca.bc.gov.educ.api.trax.support.TestUtils; +import ca.bc.gov.educ.api.trax.util.BaseEventHistoryTest; +import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; +import ca.bc.gov.educ.api.trax.util.JsonUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.UUID; + +@SpringBootTest(classes = { EducGradTraxApiApplication.class }) +@ActiveProfiles("test") +class MapperTest extends BaseEventHistoryTest { + + @Autowired + private EventHistoryMapper eventHistoryMapper; + @Autowired + private EducGradTraxApiConstants constants; + private static final UUIDMapper uuidMapper = new UUIDMapper(); + + @Test + void testGetMapper_shouldNotBeNull() { + Assertions.assertNotNull(EventMapper.mapper); + } + + @Test + void testUUIDMapper_givenValidString_shouldReturnUUID() { + Assertions.assertNotNull(uuidMapper.map(UUID.randomUUID().toString())); + } + + @Test + void testUUIDMapper_givenValidUUID_shouldReturnString() { + Assertions.assertNotNull(uuidMapper.map(UUID.randomUUID())); + } + + @Test + void testUUIDMapper_givenBlankString_shouldReturnNull() { + Assertions.assertNull(uuidMapper.map("")); + } + + + @Test + void testToEventHistory_givenSchoolEvent_shouldReturnCorrectUrl() throws JsonProcessingException { + School school = TestUtils.createSchool(); + Pair pair = createUrlAndEntity("CREATE_SCHOOL", school, school.getSchoolId()); + EventHistory eventHistory = eventHistoryMapper.toStructure(pair.getRight()); + Assertions.assertEquals(true, eventHistory.getEventHistoryUrl().equals(pair.getLeft())); + } + + @Test + void testToEventHistory_givenMoveSchoolEvent_shouldReturnCorrectUrl() throws JsonProcessingException { + MoveSchoolData school = TestUtils.createMoveSchoolData(); + Pair pair = createUrlAndEntity("MOVE_SCHOOL", school, school.getToSchool().getSchoolId()); + EventHistory eventHistory = eventHistoryMapper.toStructure(pair.getRight()); + Assertions.assertEquals(true, eventHistory.getEventHistoryUrl().equals(pair.getLeft())); + } + + @Test + void testToEventHistory_givenCreateDistrictEvent_shouldReturnCorrectUrl() throws JsonProcessingException { + District district = TestUtils.createDistrict(); + Pair pair = createUrlAndEntity("CREATE_DISTRICT", district, district.getDistrictId()); + EventHistory eventHistory = eventHistoryMapper.toStructure(pair.getRight()); + Assertions.assertEquals(true, eventHistory.getEventHistoryUrl().equals(pair.getLeft())); + } + + @Test + void testToEventHistory_givenCreateDistrictContact_shouldReturnCorrectUrl() throws JsonProcessingException { + District district = TestUtils.createDistrict(); + Pair pair = createUrlAndEntity("CREATE_DISTRICT_CONTACT", district, district.getDistrictId()); + EventHistory eventHistory = eventHistoryMapper.toStructure(pair.getRight()); + Assertions.assertEquals(true, eventHistory.getEventHistoryUrl().equals(pair.getLeft())); + } + + @Test + void testToEventHistory_givenCreateAuthorityContact_shouldReturnCorrectUrl() throws JsonProcessingException { + AuthorityContact authorityContact = TestUtils.createAuthorityContact(); + Pair pair = createUrlAndEntity("CREATE_AUTHORITY_CONTACT", authorityContact, authorityContact.getIndependentAuthorityId()); + EventHistory eventHistory = eventHistoryMapper.toStructure(pair.getRight()); + Assertions.assertEquals(true, eventHistory.getEventHistoryUrl().equals(pair.getLeft())); + } + + private Pair createUrlAndEntity(String eventType, Object eventPayload, String id) throws JsonProcessingException { + EventEntity eventEntity = this.createEventData(); + eventEntity.setEventType(eventType); + eventEntity.setEventPayload(JsonUtil.getJsonStringFromObject(eventPayload)); + EventHistoryEntity eventHistoryEntity = this.createEventHistoryData(eventEntity); + String url = String.format(resolveURL(eventType), id); + return Pair.of(url, eventHistoryEntity); + } + + private String resolveURL(String eventType) { + switch (EventType.valueOf(eventType)) { + case CREATE_SCHOOL, UPDATE_SCHOOL, MOVE_SCHOOL, CREATE_SCHOOL_CONTACT, UPDATE_SCHOOL_CONTACT, DELETE_SCHOOL_CONTACT -> { + return constants.getStudentAdminSchoolDetailsUrl(); + } + case CREATE_DISTRICT, UPDATE_DISTRICT, CREATE_DISTRICT_CONTACT, UPDATE_DISTRICT_CONTACT, DELETE_DISTRICT_CONTACT -> { + return constants.getStudentAdminDistrictDetailsUrl(); + } + case CREATE_AUTHORITY_CONTACT, UPDATE_AUTHORITY_CONTACT, DELETE_AUTHORITY_CONTACT -> { + return constants.getStudentAdminAuthorityDetailsUrl(); + } + default -> { + return null; + } + } + } + +} diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java index 8ae57049..b6393607 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java @@ -1,6 +1,7 @@ package ca.bc.gov.educ.api.trax.service; import ca.bc.gov.educ.api.trax.EducGradTraxApiApplication; +import ca.bc.gov.educ.api.trax.mapper.EventHistoryMapperImpl; import ca.bc.gov.educ.api.trax.messaging.NatsConnection; import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; @@ -20,7 +21,7 @@ import redis.clients.jedis.JedisCluster; @RunWith(SpringRunner.class) -@SpringBootTest(classes = {EducGradTraxApiApplication.class}) +@SpringBootTest(classes = {EducGradTraxApiApplication.class, EventHistoryMapperImpl.class, EventHistoryMapperImpl.class}) @ActiveProfiles("test") @AutoConfigureMockMvc public abstract class BaseReplicationServiceTest { diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java index 424bc75f..d216ef88 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java @@ -1,16 +1,22 @@ package ca.bc.gov.educ.api.trax.service; +import ca.bc.gov.educ.api.trax.exception.TraxAPIRuntimeException; import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; import ca.bc.gov.educ.api.trax.repository.EventHistoryRepository; import ca.bc.gov.educ.api.trax.repository.EventRepository; import ca.bc.gov.educ.api.trax.support.TestUtils; +import ca.bc.gov.educ.api.trax.util.JsonUtil; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; public class EventHistoryServiceTest extends BaseReplicationServiceTest { @@ -61,4 +67,18 @@ public void purgeOldEventAndEventHistoryRecords_givenNoExceptionAndNewRecord_sho Optional eventHistoryThatShouldNotBePurgedAlso = eventHistoryRepository.findById(eventHistory.getId()); Assert.assertTrue(eventHistoryThatShouldNotBePurgedAlso.isPresent() && eventThatShouldNotBePurgedOptional.isPresent()); } + + @Test + public void setSpecificationAndSortCriteria_givenValidData_shouldReturnOk() throws TraxAPIRuntimeException{ + String sort = "{ \"schoolNumber\": \"ASC\" }"; + String searchParams = "[{\"condition\":null,\"searchCriteriaList\":[{\"key\":\"openedDate\",\"operation\":\"lte\",\"value\":\"2024-08-26T09:05:51.782\",\"valueType\":\"DATE_TIME\",\"condition\":\"AND\"},{\"key\":\"strAnd\",\"operation\":\"eq\",\"value\":\"Test String\",\"valueType\":\"STRING\",\"condition\":\"AND\"},{\"key\":\"longOr\",\"operation\":\"gt\",\"value\":\"1230\",\"valueType\":\"LONG\",\"condition\":\"OR\"},{\"key\":\"intOr\",\"operation\":\"gte\",\"value\":\"12\",\"valueType\":\"INTEGER\",\"condition\":\"OR\"},{\"key\":\"dateAnd\",\"operation\":\"eq\",\"value\":\"2024-08-26\",\"valueType\":\"DATE\",\"condition\":\"AND\"},{\"key\":\"uuidOr\",\"operation\":\"eq\",\"value\":\"6f84aa52-ad90-4f04-be66-04614ed24c37\",\"valueType\":\"UUID\",\"condition\":\"OR\"}]}]"; + Specification eventHistorySpecs = eventHistoryService.setSpecificationAndSortCriteria(sort, searchParams, JsonUtil.mapper, new ArrayList<>()); + Assert.assertNotNull(eventHistorySpecs); + } + + @Test + public void setSpecificationAndSortCriteria_givenInvalisData_shouldThrowTraxAPIRuntimeException() { + final List sorts = new ArrayList<>(); + Assert.assertThrows(TraxAPIRuntimeException.class, () -> eventHistoryService.setSpecificationAndSortCriteria(null, "{ \"bunkjunk\": \"ASC\" }", JsonUtil.mapper, sorts)); + } } \ No newline at end of file diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/util/BaseEventHistoryTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/util/BaseEventHistoryTest.java new file mode 100644 index 00000000..1f52574f --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/util/BaseEventHistoryTest.java @@ -0,0 +1,66 @@ +package ca.bc.gov.educ.api.trax.util; + +import ca.bc.gov.educ.api.trax.messaging.NatsConnection; +import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; +import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; +import ca.bc.gov.educ.api.trax.model.entity.EventEntity; +import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import redis.clients.jedis.JedisCluster; + +import java.time.LocalDateTime; +import java.util.UUID; + +public abstract class BaseEventHistoryTest { + + protected static final String USER = "TEST"; + + @MockBean + private Publisher publisher; + @MockBean + private Subscriber subscriber; + @MockBean + private NatsConnection natsConnection; + @MockBean + private ClientRegistrationRepository clientRegistrationRepository; + @MockBean + private OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository; + @MockBean + private OAuth2AuthorizedClientService oAuth2AuthorizedClientService; + @MockBean + private JedisConnectionFactory jedisConnectionFactoryMock; + @MockBean + private JedisCluster jedisClusterMock; + + protected EventEntity createEventData() { + return EventEntity.builder() + .eventId(UUID.randomUUID()) + .eventPayload("") + .eventStatus("PROCESSED") + .eventType("CREATE_SCHOOL_CONTACT") + .eventOutcome("SCHOOL_CONTACT_CREATED") + .createUser(USER) + .createDate(LocalDateTime.now()) + .updateUser(USER) + .updateDate(LocalDateTime.now()) + .activityCode("INSTITUTE_EVENT") + .build(); + + } + + protected EventHistoryEntity createEventHistoryData(EventEntity event) { + return EventHistoryEntity.builder() + .event(event) + .acknowledgeFlag("N") + .createDate(LocalDateTime.now()) + .createUser(USER) + .updateDate(LocalDateTime.now()) + .updateUser(USER) + .build(); + } + +} diff --git a/api/src/test/resources/application.yaml b/api/src/test/resources/application.yaml index b1c45be8..4672df06 100644 --- a/api/src/test/resources/application.yaml +++ b/api/src/test/resources/application.yaml @@ -152,6 +152,13 @@ endpoint: url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/category-codes get-all-school-funding-group-codes: url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/funding-group-codes + student-admin: + school-details: + url: https://student-admin-8878b4-dev.apps.silver.devops.gov.bc.ca/institute/school/%s/details + district-details: + url: https://student-admin-8878b4-dev.apps.silver.devops.gov.bc.ca/district/%s + authority-details: + url: https://student-admin-8878b4-dev.apps.silver.devops.gov.bc.ca/authority/%s # other properties props: diff --git a/tools/config/update-configmap.sh b/tools/config/update-configmap.sh index 702127fb..e42e4794 100644 --- a/tools/config/update-configmap.sh +++ b/tools/config/update-configmap.sh @@ -8,6 +8,7 @@ COMMON_NAMESPACE=$4 BUSINESS_NAMESPACE=$5 SPLUNK_TOKEN=$6 APP_LOG_LEVEL=$7 +STUDENT_ADMIN_URL_ROOT=$8 SPLUNK_URL="gww.splunk.educ.gov.bc.ca" FLB_CONFIG="[SERVICE] @@ -74,6 +75,7 @@ oc create -n "$GRAD_NAMESPACE"-"$envValue" configmap "$APP_NAME"-config-map \ --from-literal=INSTITUTE_API_URL_ROOT="http://institute-api-master.$COMMON_NAMESPACE-$envValue.svc.cluster.local:8080/" \ --from-literal=MAX_RETRY_ATTEMPTS="3" \ --from-literal=SCHOOL_CACHE_EXPIRY_IN_MINS="240" \ + --from-literal=STUDENT_ADMIN_URL_ROOT="$STUDENT_ADMIN_URL_ROOT" \ --dry-run=client -o yaml | oc apply -f - echo Creating config map "$APP_NAME"-flb-sc-config-map From 3f9224c3a0f9e0506bff6a81c346bdf8ca1df543 Mon Sep 17 00:00:00 2001 From: "chris.ditcher" Date: Tue, 10 Sep 2024 09:23:50 -0700 Subject: [PATCH 11/16] Fixing syntax error in configmap --- tools/config/update-configmap.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/config/update-configmap.sh b/tools/config/update-configmap.sh index e42e4794..de8112e3 100644 --- a/tools/config/update-configmap.sh +++ b/tools/config/update-configmap.sh @@ -55,8 +55,8 @@ echo Creating config map "$APP_NAME"-config-map oc create -n "$GRAD_NAMESPACE"-"$envValue" configmap "$APP_NAME"-config-map \ --from-literal=APP_LOG_LEVEL="$APP_LOG_LEVEL" \ --from-literal=BASELINE_ON_MIGRATE="true" \ - --from-literal=CRON_SCHEDULED_PURGE_OLD_RECORDS: "0 0 0 * * *" \ - --from-literal=RECORDS_STALE_IN_DAYS: 365 \ + --from-literal=CRON_SCHEDULED_PURGE_OLD_RECORDS="0 0 0 * * *" \ + --from-literal=RECORDS_STALE_IN_DAYS=365 \ --from-literal=CRON_SCHEDULED_GRAD_TO_TRAX_EVENTS="0 0/5 * * * *" \ --from-literal=CRON_SCHEDULED_GRAD_TO_TRAX_EVENTS_LOCK_AT_LEAST_FOR="PT1M" \ --from-literal=CRON_SCHEDULED_GRAD_TO_TRAX_EVENTS_LOCK_AT_MOST_FOR="PT5M" \ @@ -82,4 +82,4 @@ echo Creating config map "$APP_NAME"-flb-sc-config-map oc create -n "$GRAD_NAMESPACE"-"$envValue" configmap "$APP_NAME"-flb-sc-config-map \ --from-literal=fluent-bit.conf="$FLB_CONFIG" \ --from-literal=parsers.conf="$PARSER_CONFIG" \ - --dry-run=client -o yaml | oc apply -f - + --dry-run=client -o yaml | oc apply -f - \ No newline at end of file From e304bb75b879a9839ac2bf3caae9ea58a8b1147b Mon Sep 17 00:00:00 2001 From: "chris.ditcher" Date: Tue, 10 Sep 2024 10:23:07 -0700 Subject: [PATCH 12/16] Revert "GRAD2-2768 (#348)" This reverts commit 6a6bea15596279e2a977e7f4899faafc74852909. --- .../build.from.dev.branch.deploy.to.dev.yml | 6 +- .../build.from.main.branch.deploy.to.dev.yml | 6 +- ...uild.from.release.branch.deploy.to.dev.yml | 6 +- .github/workflows/deploy_prod.yml | 3 +- .github/workflows/deploy_test.yml | 3 +- .gitignore | 2 +- api/pom.xml | 20 +- .../api/trax/config/RestErrorHandler.java | 121 +++++------- .../controller/EventHistoryController.java | 70 ------- .../gov/educ/api/trax/exception/ApiError.java | 86 --------- .../exception/EntityNotFoundException.java | 4 - .../exception/TraxAPIRuntimeException.java | 13 -- .../educ/api/trax/filter/BaseFilterSpecs.java | 127 ------------- .../gov/educ/api/trax/filter/Converters.java | 50 ----- .../filter/EventHistoryFilterSpecifics.java | 28 --- .../educ/api/trax/filter/FilterCriteria.java | 179 ------------------ .../educ/api/trax/filter/FilterOperation.java | 78 -------- .../api/trax/filter/FilterSpecifications.java | 107 ----------- .../api/trax/mapper/EventHistoryMapper.java | 90 --------- .../gov/educ/api/trax/mapper/EventMapper.java | 19 -- .../gov/educ/api/trax/mapper/UUIDMapper.java | 22 --- .../educ/api/trax/model/dto/BaseModel.java | 2 - .../educ/api/trax/model/dto/Condition.java | 12 -- .../bc/gov/educ/api/trax/model/dto/Event.java | 19 -- .../educ/api/trax/model/dto/EventHistory.java | 25 --- .../gov/educ/api/trax/model/dto/Search.java | 24 --- .../api/trax/model/dto/SearchCriteria.java | 39 ---- .../educ/api/trax/model/dto/ValueType.java | 28 --- .../api/trax/model/entity/EventEntity.java | 3 - .../trax/model/entity/EventHistoryEntity.java | 10 +- .../api/trax/model/entity/v2/BaseEntity.java | 11 +- .../repository/EventHistoryRepository.java | 3 +- .../api/trax/service/EventHistoryService.java | 176 +---------------- .../trax/util/EducGradTraxApiConstants.java | 12 -- .../gov/educ/api/trax/util/RequestUtil.java | 39 ---- .../gov/educ/api/trax/util/TransformUtil.java | 62 ------ .../bc/gov/educ/api/trax/util/UpperCase.java | 11 -- api/src/main/resources/application.yaml | 7 - .../EventHistoryControllerTest.java | 168 ---------------- .../gov/educ/api/trax/mapper/MapperTest.java | 123 ------------ .../service/BaseReplicationServiceTest.java | 3 +- .../trax/service/EventHistoryServiceTest.java | 20 -- .../api/trax/util/BaseEventHistoryTest.java | 66 ------- api/src/test/resources/application.yaml | 7 - tools/config/update-configmap.sh | 2 - 45 files changed, 63 insertions(+), 1849 deletions(-) delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/controller/EventHistoryController.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/exception/ApiError.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/exception/TraxAPIRuntimeException.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/BaseFilterSpecs.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/Converters.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/EventHistoryFilterSpecifics.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterCriteria.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterOperation.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterSpecifications.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventHistoryMapper.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventMapper.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/mapper/UUIDMapper.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Condition.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Event.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/EventHistory.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Search.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/SearchCriteria.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ValueType.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/util/RequestUtil.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/util/TransformUtil.java delete mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/util/UpperCase.java delete mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/controller/EventHistoryControllerTest.java delete mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/mapper/MapperTest.java delete mode 100644 api/src/test/java/ca/bc/gov/educ/api/trax/util/BaseEventHistoryTest.java diff --git a/.github/workflows/build.from.dev.branch.deploy.to.dev.yml b/.github/workflows/build.from.dev.branch.deploy.to.dev.yml index fc78b822..5749d983 100644 --- a/.github/workflows/build.from.dev.branch.deploy.to.dev.yml +++ b/.github/workflows/build.from.dev.branch.deploy.to.dev.yml @@ -128,8 +128,7 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} \ - ${{ secrets.STUDENT_ADMIN_URL_ROOT }} + ${{ vars.APP_LOG_LEVEL }} # OVERRIDE Configmaps curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ github.event.inputs.choice }}/tools/config/override-configmap-dev.sh \ @@ -140,8 +139,7 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} \ - ${{ secrets.STUDENT_ADMIN_URL_ROOT }} + ${{ vars.APP_LOG_LEVEL }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.github/workflows/build.from.main.branch.deploy.to.dev.yml b/.github/workflows/build.from.main.branch.deploy.to.dev.yml index c9a9ae89..9711e715 100644 --- a/.github/workflows/build.from.main.branch.deploy.to.dev.yml +++ b/.github/workflows/build.from.main.branch.deploy.to.dev.yml @@ -116,8 +116,7 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} \ - ${{ secrets.STUDENT_ADMIN_URL_ROOT }} + ${{ vars.APP_LOG_LEVEL }} # OVERRIDE Configmaps curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ env.BRANCH }}/tools/config/override-configmap-dev.sh \ @@ -128,8 +127,7 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} \ - ${{ secrets.STUDENT_ADMIN_URL_ROOT }} + ${{ vars.APP_LOG_LEVEL }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.github/workflows/build.from.release.branch.deploy.to.dev.yml b/.github/workflows/build.from.release.branch.deploy.to.dev.yml index e69f90b4..b7837991 100644 --- a/.github/workflows/build.from.release.branch.deploy.to.dev.yml +++ b/.github/workflows/build.from.release.branch.deploy.to.dev.yml @@ -123,8 +123,7 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} \ - ${{ secrets.STUDENT_ADMIN_URL_ROOT }} + ${{ vars.APP_LOG_LEVEL }} # OVERRIDE Configmaps curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ env.BRANCH }}/tools/config/override-configmap-dev.sh \ @@ -135,8 +134,7 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} \ - ${{ secrets.STUDENT_ADMIN_URL_ROOT }} + ${{ vars.APP_LOG_LEVEL }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.github/workflows/deploy_prod.yml b/.github/workflows/deploy_prod.yml index b70d7dd9..1cbcf539 100644 --- a/.github/workflows/deploy_prod.yml +++ b/.github/workflows/deploy_prod.yml @@ -82,8 +82,7 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} \ - ${{ secrets.STUDENT_ADMIN_URL_ROOT }} + ${{ vars.APP_LOG_LEVEL }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.github/workflows/deploy_test.yml b/.github/workflows/deploy_test.yml index ab0f309e..34781f56 100644 --- a/.github/workflows/deploy_test.yml +++ b/.github/workflows/deploy_test.yml @@ -82,8 +82,7 @@ jobs: ${{ env.COMMON_NAMESPACE }} \ ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ - ${{ vars.APP_LOG_LEVEL }} \ - ${{ secrets.STUDENT_ADMIN_URL_ROOT }} + ${{ vars.APP_LOG_LEVEL }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.gitignore b/.gitignore index 4c779315..7ded25d7 100644 --- a/.gitignore +++ b/.gitignore @@ -54,5 +54,5 @@ build/ ### VS Code ### .vscode/ -### Local dev ### +### local dev ### **/application-local.yaml diff --git a/api/pom.xml b/api/pom.xml index e218b47e..5d1eb6e2 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -24,16 +24,14 @@ src/main/java/ca/bc/gov/educ/api/trax/model/**, src/main/java/ca/bc/gov/educ/api/trax/service/EventHandlerDelegatorService, src/main/java/ca/bc/gov/educ/api/trax/util/**, - src/main/java/ca/bc/gov/educ/api/trax/repository/**, - src/main/java/ca/bc/gov/educ/api/trax/filter/** + src/main/java/ca/bc/gov/educ/api/trax/repository/** src/main/java/ca/bc/gov/educ/api/trax/messaging/**, src/main/java/ca/bc/gov/educ/api/trax/scheduler/**, src/main/java/ca/bc/gov/educ/api/trax/model/**, src/main/java/ca/bc/gov/educ/api/trax/util/**, - src/main/java/ca/bc/gov/educ/api/trax/repository/**, - src/main/java/ca/bc/gov/educ/api/trax/filter/** + src/main/java/ca/bc/gov/educ/api/trax/repository/** 18 3.10.1 @@ -73,11 +71,6 @@ org.springframework.boot spring-boot-starter-data-jpa - - org.springframework.security - spring-security-test - test - org.springframework.boot spring-boot-starter-data-redis @@ -90,10 +83,6 @@ org.apache.commons commons-pool2 - - org.springframework.boot - spring-boot-starter-validation - org.apache.logging.log4j log4j-api @@ -333,11 +322,6 @@ lombok ${lombok.version} - - org.projectlombok - lombok-mapstruct-binding - 0.2.0 - org.springframework spring-context-indexer diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/config/RestErrorHandler.java b/api/src/main/java/ca/bc/gov/educ/api/trax/config/RestErrorHandler.java index 1f948717..0b3c8933 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/config/RestErrorHandler.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/config/RestErrorHandler.java @@ -1,99 +1,63 @@ package ca.bc.gov.educ.api.trax.config; -import ca.bc.gov.educ.api.trax.exception.ApiError; -import ca.bc.gov.educ.api.trax.exception.EntityNotFoundException; import ca.bc.gov.educ.api.trax.exception.GradBusinessRuleException; import ca.bc.gov.educ.api.trax.util.ApiResponseMessage.MessageTypeEnum; import ca.bc.gov.educ.api.trax.util.ApiResponseModel; import ca.bc.gov.educ.api.trax.util.GradValidation; -import lombok.extern.slf4j.Slf4j; import org.hibernate.dialect.lock.OptimisticEntityLockException; import org.hibernate.exception.ConstraintViolationException; +import org.jboss.logging.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.orm.jpa.JpaObjectRetrievalFailureException; import org.springframework.security.access.AccessDeniedException; -import org.springframework.validation.FieldError; -import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import java.util.HashMap; -import java.util.Map; - -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.NOT_FOUND; - -@Slf4j @ControllerAdvice public class RestErrorHandler extends ResponseEntityExceptionHandler { - GradValidation validation; + private static final Logger LOGGER = Logger.getLogger(RestErrorHandler.class); @Autowired - public RestErrorHandler(GradValidation validation) { - this.validation = validation; - } - - /** - * Override method handles exception thrown by @Valid validation - * on controller methods (for example) - */ - @Override - protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { - Map errors = new HashMap<>(); - ex.getBindingResult().getAllErrors().forEach(error -> { - String fieldName = ((FieldError) error).getField(); - String errorMessage = error.getDefaultMessage(); - errors.put(fieldName, errorMessage); - }); - ApiError apiError = new ApiError(BAD_REQUEST); - apiError.setMessage(errors.toString()); - return buildResponseEntity(apiError); - } + GradValidation validation; @ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class }) - protected ResponseEntity handleConflict(RuntimeException ex) { - log.error("Illegal argument ERROR IS: {}", ex.getClass().getName(), ex); + protected ResponseEntity handleConflict(RuntimeException ex, WebRequest request) { + LOGGER.error("Illegal argument ERROR IS: " + ex.getClass().getName(), ex); ApiResponseModel response = ApiResponseModel.ERROR(null, ex.getLocalizedMessage()); - validation.ifErrors(response::addErrorMessages); - validation.ifWarnings(response::addWarningMessages); + validation.ifErrors(errorList -> response.addErrorMessages(errorList)); + validation.ifWarnings(warningList -> response.addWarningMessages(warningList)); validation.clear(); return new ResponseEntity<>(response, HttpStatus.UNPROCESSABLE_ENTITY); } - @ExceptionHandler(value = { JpaObjectRetrievalFailureException.class, DataRetrievalFailureException.class, EntityNotFoundException.class }) - protected ResponseEntity handleEntityNotFound(RuntimeException ex) { - // no need to log EntityNotFoundExceptions as error - // it is used to denote NOT FOUND and is normal part of operations - if(!(ex instanceof EntityNotFoundException)){ - log.error("JPA ERROR IS: {}", ex.getClass().getName(), ex); - } - ApiError apiError = new ApiError(NOT_FOUND); - apiError.setMessage(ex.getMessage()); - return buildResponseEntity(apiError); + @ExceptionHandler(value = { JpaObjectRetrievalFailureException.class, DataRetrievalFailureException.class }) + protected ResponseEntity handleEntityNotFound(RuntimeException ex, WebRequest request) { + LOGGER.error("JPA ERROR IS: " + ex.getClass().getName(), ex); + validation.clear(); + return new ResponseEntity<>(ApiResponseModel.ERROR(null, ex.getLocalizedMessage()), HttpStatus.BAD_REQUEST); } @ExceptionHandler(value = { AccessDeniedException.class }) - protected ResponseEntity handleAuthorizationErrors(AccessDeniedException ex) { - log.error("Authorization error EXCEPTION IS: {}", ex.getClass().getName()); + protected ResponseEntity handleAuthorizationErrors(Exception ex, WebRequest request) { + + LOGGER.error("Authorization error EXCETPION IS: " + ex.getClass().getName()); String message = "You are not authorized to access this resource."; validation.clear(); return new ResponseEntity<>(ApiResponseModel.ERROR(null, message), HttpStatus.FORBIDDEN); } @ExceptionHandler(value = { GradBusinessRuleException.class }) - protected ResponseEntity handleGradBusinessException(GradBusinessRuleException ex) { + protected ResponseEntity handleGradBusinessException(Exception ex, WebRequest request) { ApiResponseModel response = ApiResponseModel.ERROR(null); - validation.ifErrors(response::addErrorMessages); - validation.ifWarnings(response::addWarningMessages); + validation.ifErrors(errorList -> response.addErrorMessages(errorList)); + validation.ifWarnings(warningList -> response.addWarningMessages(warningList)); if (response.getMessages().isEmpty()) { response.addMessageItem(ex.getLocalizedMessage(), MessageTypeEnum.ERROR); } @@ -102,54 +66,55 @@ protected ResponseEntity handleGradBusinessException(GradBusinessRuleExc } @ExceptionHandler(value = { OptimisticEntityLockException.class }) - protected ResponseEntity handleOptimisticEntityLockException(OptimisticEntityLockException ex) { - return getGenericUncaughtExceptionResponse(ex); + protected ResponseEntity handleOptimisticEntityLockException(OptimisticEntityLockException ex, WebRequest request) { + + LOGGER.error("EXCEPTION IS: " + ex.getClass().getName(), ex); + LOGGER.error("Illegal argument ERROR IS: " + ex.getClass().getName(), ex); + ApiResponseModel response = ApiResponseModel.ERROR(null); + validation.ifErrors(errorList -> response.addErrorMessages(errorList)); + validation.ifWarnings(warningList -> response.addWarningMessages(warningList)); + if (!validation.hasErrors()) { + response.addMessageItem(ex.getLocalizedMessage(), MessageTypeEnum.ERROR); + } + validation.clear(); + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } @ExceptionHandler(value = { DataIntegrityViolationException.class }) - protected ResponseEntity handleSQLException(DataIntegrityViolationException ex) { + protected ResponseEntity handleSQLException(DataIntegrityViolationException ex, WebRequest request) { - log.error("DATA INTEGRITY VIOLATION IS: {}", ex.getClass().getName(), ex); + LOGGER.error("DATA INTEGRITY VIOLATION IS: " + ex.getClass().getName(), ex); String msg = ex.getLocalizedMessage(); + Throwable cause = ex.getCause(); if (cause instanceof ConstraintViolationException) { ConstraintViolationException contraintViolation = (ConstraintViolationException) cause; if ("23000".equals(contraintViolation.getSQLState())) { // primary key violation - probably inserting a duplicate record - msg = (ex.getRootCause() != null) ? ex.getRootCause().getMessage() : ""; + msg = ex.getRootCause().getMessage(); } } + ApiResponseModel response = ApiResponseModel.ERROR(null, msg); - validation.ifErrors(response::addErrorMessages); - validation.ifWarnings(response::addWarningMessages); + validation.ifErrors(errorList -> response.addErrorMessages(errorList)); + validation.ifWarnings(warningList -> response.addWarningMessages(warningList)); validation.clear(); return new ResponseEntity<>(response, HttpStatus.UNPROCESSABLE_ENTITY); } @ExceptionHandler(value = { Exception.class }) - protected ResponseEntity handleUncaughtException(Exception ex) { - return getGenericUncaughtExceptionResponse(ex); - } + protected ResponseEntity handleUncaughtException(Exception ex, WebRequest request) { - private ResponseEntity getGenericUncaughtExceptionResponse(Exception ex) { - log.error("EXCEPTION IS: {}", ex.getClass().getName(), ex); + LOGGER.error("EXCEPTION IS: " + ex.getClass().getName(), ex); + LOGGER.error("Illegal argument ERROR IS: " + ex.getClass().getName(), ex); ApiResponseModel response = ApiResponseModel.ERROR(null); - validation.ifErrors(response::addErrorMessages); - validation.ifWarnings(response::addWarningMessages); + validation.ifErrors(errorList -> response.addErrorMessages(errorList)); + validation.ifWarnings(warningList -> response.addWarningMessages(warningList)); if (!validation.hasErrors()) { response.addMessageItem(ex.getLocalizedMessage(), MessageTypeEnum.ERROR); } validation.clear(); return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } - - /** - * Build response entity response entity. - * - * @param apiError the api error - * @return the response entity - */ - private ResponseEntity buildResponseEntity(ApiError apiError) { - return new ResponseEntity<>(apiError, apiError.getStatus()); - } + } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/EventHistoryController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/EventHistoryController.java deleted file mode 100644 index 3f82bd07..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/EventHistoryController.java +++ /dev/null @@ -1,70 +0,0 @@ -package ca.bc.gov.educ.api.trax.controller; -import ca.bc.gov.educ.api.trax.mapper.EventHistoryMapper; -import ca.bc.gov.educ.api.trax.model.dto.EventHistory; -import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; -import ca.bc.gov.educ.api.trax.service.EventHistoryService; -import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; -import ca.bc.gov.educ.api.trax.util.JsonUtil; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.scheduling.annotation.Async; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -@CrossOrigin -@RestController -@RequestMapping(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1) -@Tag(name = "Event History", description = "Endpoints for Event History.") -public class EventHistoryController { - - private final EventHistoryService eventHistoryService; - private final EventHistoryMapper mapper; - - @Autowired - public EventHistoryController(EventHistoryService eventHistoryService, EventHistoryMapper mapper) { - this.eventHistoryService = eventHistoryService; - this.mapper = mapper; - } - - @GetMapping("/paginated") - @Async - @PreAuthorize("hasAuthority('SCOPE_READ_EVENT_HISTORY')") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK"), - @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR.")}) - @Transactional(readOnly = true) - @Operation(summary = "Find All Event History paginated", description = "Find all Event History using pagination", tags = { "Event History" }) - public CompletableFuture> findAll(@RequestParam(name = "pageNumber", defaultValue = "0") Integer pageNumber, - @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, - @RequestParam(name = "sort", defaultValue = "") String sort, - @RequestParam(name = "searchParams", required = false) String searchParams) { - final List sorts = new ArrayList<>(); - Specification eventHistorySpecs = eventHistoryService.setSpecificationAndSortCriteria(sort, searchParams, JsonUtil.mapper, sorts); - return this.eventHistoryService.findAll(eventHistorySpecs, pageNumber, pageSize, sorts).thenApplyAsync(schoolEntities -> schoolEntities.map(mapper::toStructure)); - } - - @PutMapping - @PreAuthorize("hasAuthority('SCOPE_WRITE_EVENT_HISTORY')") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK"), - @ApiResponse(responseCode = "404", description = "NOT FOUND"), - @ApiResponse(responseCode = "400", description = "BAD REQUEST"), - @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR.")}) - @Transactional - @Operation(summary = "Update an event history entity", description = "Update a valid event history", tags = { "Event History" }) - public EventHistory updateEventHistory(@Valid @RequestBody EventHistory eventHistory) { - return this.eventHistoryService.updateEventHistory(eventHistory); - } -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/ApiError.java b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/ApiError.java deleted file mode 100644 index 9072d610..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/ApiError.java +++ /dev/null @@ -1,86 +0,0 @@ -package ca.bc.gov.educ.api.trax.exception; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonInclude; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import org.springframework.http.HttpStatus; - -import java.io.Serializable; -import java.time.LocalDateTime; - -/** - * The type Api error. - */ -@AllArgsConstructor -@Data -@Builder -@JsonInclude(JsonInclude.Include.NON_NULL) -public class ApiError implements Serializable { - - /** - * The Status. - */ - private HttpStatus status; - /** - * The Timestamp. - */ - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") - private LocalDateTime timestamp; - /** - * The Message. - */ - private String message; - /** - * The Debug message. - */ - private String debugMessage; - - /** - * Instantiates a new Api error. - */ - private ApiError() { - timestamp = LocalDateTime.now(); - } - - /** - * Instantiates a new Api error. - * - * @param status the status - */ - public ApiError(HttpStatus status) { - this(); - this.status = status; - } - - /** - * Instantiates a new Api error. - * - * @param status the status - * @param ex the ex - */ - ApiError(HttpStatus status, Throwable ex) { - this(); - this.status = status; - this.message = "Unexpected error"; - this.debugMessage = ex.getLocalizedMessage(); - } - - /** - * Instantiates a new Api error. - * - * @param status the status - * @param message the message - * @param ex the ex - */ - public ApiError(HttpStatus status, String message, Throwable ex) { - this(); - this.status = status; - this.message = message; - this.debugMessage = ex.getLocalizedMessage(); - } - - -} - diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/EntityNotFoundException.java b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/EntityNotFoundException.java index 0609973a..6c22c65e 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/EntityNotFoundException.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/EntityNotFoundException.java @@ -14,10 +14,6 @@ public class EntityNotFoundException extends RuntimeException { - public EntityNotFoundException(String message) { - super(message); - } - public EntityNotFoundException(Class clazz, String... searchParamsMap) { super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), toMap(String.class, String.class, searchParamsMap))); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/TraxAPIRuntimeException.java b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/TraxAPIRuntimeException.java deleted file mode 100644 index 6f2a6997..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/TraxAPIRuntimeException.java +++ /dev/null @@ -1,13 +0,0 @@ -package ca.bc.gov.educ.api.trax.exception; - -import java.io.Serial; - -public class TraxAPIRuntimeException extends RuntimeException { - - @Serial - private static final long serialVersionUID = 1L; - - public TraxAPIRuntimeException(String message) { - super(message); - } -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/BaseFilterSpecs.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/BaseFilterSpecs.java deleted file mode 100644 index 5cdd48ca..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/BaseFilterSpecs.java +++ /dev/null @@ -1,127 +0,0 @@ -package ca.bc.gov.educ.api.trax.filter; - -import org.springframework.data.jpa.domain.Specification; - -import java.time.chrono.ChronoLocalDate; -import java.time.chrono.ChronoLocalDateTime; -import java.util.UUID; -import java.util.function.Function; - -/** - * this is the generic class to support all kind of filter specifications for different entities - * - * @param the entity type. - * @author Om - */ -public abstract class BaseFilterSpecs { - - private final FilterSpecifications dateFilterSpecifications; - private final FilterSpecifications> dateTimeFilterSpecifications; - private final FilterSpecifications integerFilterSpecifications; - private final FilterSpecifications stringFilterSpecifications; - private final FilterSpecifications longFilterSpecifications; - private final FilterSpecifications uuidFilterSpecifications; - private final Converters converters; - - /** - * Instantiates a new Base filter specs. - * - * @param dateFilterSpecifications the date filter specifications - * @param dateTimeFilterSpecifications the date time filter specifications - * @param integerFilterSpecifications the integer filter specifications - * @param stringFilterSpecifications the string filter specifications - * @param longFilterSpecifications the long filter specifications - * @param uuidFilterSpecifications the uuid filter specifications - * @param converters the converters - */ - protected BaseFilterSpecs(FilterSpecifications dateFilterSpecifications, FilterSpecifications> dateTimeFilterSpecifications, FilterSpecifications integerFilterSpecifications, FilterSpecifications stringFilterSpecifications, FilterSpecifications longFilterSpecifications, FilterSpecifications uuidFilterSpecifications, Converters converters) { - this.dateFilterSpecifications = dateFilterSpecifications; - this.dateTimeFilterSpecifications = dateTimeFilterSpecifications; - this.integerFilterSpecifications = integerFilterSpecifications; - this.stringFilterSpecifications = stringFilterSpecifications; - this.longFilterSpecifications = longFilterSpecifications; - this.uuidFilterSpecifications = uuidFilterSpecifications; - this.converters = converters; - } - - /** - * Gets date type specification. - * - * @param fieldName the field name - * @param filterValue the filter value - * @param filterOperation the filter operation - * @return the date type specification - */ - public Specification getDateTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { - return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(ChronoLocalDate.class), dateFilterSpecifications); - } - - /** - * Gets date time type specification. - * - * @param fieldName the field name - * @param filterValue the filter value - * @param filterOperation the filter operation - * @return the date time type specification - */ - public Specification getDateTimeTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { - return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(ChronoLocalDateTime.class), dateTimeFilterSpecifications); - } - - /** - * Gets integer type specification. - * - * @param fieldName the field name - * @param filterValue the filter value - * @param filterOperation the filter operation - * @return the integer type specification - */ - public Specification getIntegerTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { - return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(Integer.class), integerFilterSpecifications); - } - - /** - * Gets long type specification. - * - * @param fieldName the field name - * @param filterValue the filter value - * @param filterOperation the filter operation - * @return the long type specification - */ - public Specification getLongTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { - return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(Long.class), longFilterSpecifications); - } - - /** - * Gets string type specification. - * - * @param fieldName the field name - * @param filterValue the filter value - * @param filterOperation the filter operation - * @return the string type specification - */ - public Specification getStringTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { - return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(String.class), stringFilterSpecifications); - } - - /** - * Gets uuid type specification. - * - * @param fieldName the field name - * @param filterValue the filter value - * @param filterOperation the filter operation - * @return the uuid type specification - */ - public Specification getUUIDTypeSpecification(String fieldName, String filterValue, FilterOperation filterOperation) { - return getSpecification(fieldName, filterValue, filterOperation, converters.getFunction(UUID.class), uuidFilterSpecifications); - } - - private > Specification getSpecification(String fieldName, - String filterValue, - FilterOperation filterOperation, - Function converter, - FilterSpecifications specifications) { - FilterCriteria criteria = new FilterCriteria<>(fieldName, filterValue, filterOperation, converter); - return specifications.getSpecification(criteria.getOperation()).apply(criteria); - } -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/Converters.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/Converters.java deleted file mode 100644 index bbae2325..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/Converters.java +++ /dev/null @@ -1,50 +0,0 @@ -package ca.bc.gov.educ.api.trax.filter; - -import jakarta.annotation.PostConstruct; -import org.springframework.stereotype.Service; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.chrono.ChronoLocalDate; -import java.time.chrono.ChronoLocalDateTime; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.function.Function; - -/** - * The type Converters. - * - * @author om - */ -@Service -public class Converters { - - private final Map, Function>> map = new HashMap<>(); - - /** - * Init. - */ - @PostConstruct - public void init() { - map.put(String.class, s -> s); - map.put(Long.class, Long::valueOf); - map.put(Integer.class, Integer::valueOf); - map.put(ChronoLocalDate.class, LocalDate::parse); - map.put(ChronoLocalDateTime.class, LocalDateTime::parse); - map.put(UUID.class, UUID::fromString); - } - - /** - * Gets function. - * - * @param the type parameter - * @param classObj the class obj - * @return the function - */ - @SuppressWarnings("unchecked") - public > Function getFunction(Class classObj) { - return (Function) map.get(classObj); - } - -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/EventHistoryFilterSpecifics.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/EventHistoryFilterSpecifics.java deleted file mode 100644 index 75588793..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/EventHistoryFilterSpecifics.java +++ /dev/null @@ -1,28 +0,0 @@ -package ca.bc.gov.educ.api.trax.filter; - -import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.time.chrono.ChronoLocalDate; -import java.time.chrono.ChronoLocalDateTime; -import java.util.UUID; - -@Service -@Slf4j -public class EventHistoryFilterSpecifics extends BaseFilterSpecs { - /** - * Instantiates a new Base filter specs. - * - * @param eventHistoryEntityChronoLocalDateFilterSpecifications the date filter specifications - * @param eventHistoryEntityChronoLocalDateTimeFilterSpecifications the date time filter specifications - * @param eventHistoryEntityIntegerFilterSpecifications the integer filter specifications - * @param eventHistoryEntityStringFilterSpecifications the string filter specifications - * @param eventHistoryEntityLongFilterSpecifications the long filter specifications - * @param uuidFilterSpecifications the uuid filter specifications - * @param converters the converters - */ - public EventHistoryFilterSpecifics(FilterSpecifications eventHistoryEntityChronoLocalDateFilterSpecifications, FilterSpecifications> eventHistoryEntityChronoLocalDateTimeFilterSpecifications, FilterSpecifications eventHistoryEntityIntegerFilterSpecifications, FilterSpecifications eventHistoryEntityStringFilterSpecifications, FilterSpecifications eventHistoryEntityLongFilterSpecifications, FilterSpecifications uuidFilterSpecifications, Converters converters) { - super(eventHistoryEntityChronoLocalDateFilterSpecifications, eventHistoryEntityChronoLocalDateTimeFilterSpecifications, eventHistoryEntityIntegerFilterSpecifications, eventHistoryEntityStringFilterSpecifications, eventHistoryEntityLongFilterSpecifications, uuidFilterSpecifications, converters); - } -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterCriteria.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterCriteria.java deleted file mode 100644 index 0f407f22..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterCriteria.java +++ /dev/null @@ -1,179 +0,0 @@ -package ca.bc.gov.educ.api.trax.filter; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.lang.NonNull; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * Filter Criteria Holder - * - * @param is the java type of the DB table column - * @author om - */ -public class FilterCriteria> { - - /** - * Holds the operation {@link FilterOperation} - */ - private final FilterOperation operation; - - /** - * Table column name - */ - private final String fieldName; - - /** - * Holds the Function to convertString to - */ - private final Function converterFunction; - /** - * Holds the filter criteria - */ - private final Collection originalValues; - /** - * Holds the filter criteria as type - */ - private final Collection convertedValues; - /** - * Converted value - */ - private T convertedSingleValue; - /** - * minimum value - application only for {@link FilterOperation#BETWEEN} - */ - private T minValue; - /** - * maximum value - application only for {@link FilterOperation#BETWEEN} - */ - private T maxValue; - - /** - * Instantiates a new Filter criteria. - * - * @param fieldName the field name - * @param fieldValue the field value - * @param filterOperation the filter operation - * @param converterFunction the converter function - */ - public FilterCriteria(@NonNull String fieldName, String fieldValue, @NonNull FilterOperation filterOperation, Function converterFunction) { - - this.fieldName = fieldName; - this.converterFunction = converterFunction; - - String[] operationValues; - - if (filterOperation == FilterOperation.BETWEEN || filterOperation == FilterOperation.IN || filterOperation == FilterOperation.NOT_IN) { - if (fieldValue != null) { - // Split the fieldValue value as comma separated. - operationValues = StringUtils.split(fieldValue, ","); - } else { - operationValues = new String[]{null}; - } - if (operationValues.length < 1) { - throw new IllegalArgumentException("multiple values expected(comma separated) for IN, NOT IN and BETWEEN operations."); - } - } else { - operationValues = new String[]{fieldValue}; - } - this.operation = filterOperation; - this.originalValues = Arrays.asList(operationValues); - this.convertedValues = new ArrayList<>(); - - // Validate other conditions - validateAndAssign(operationValues); - - } - - private void validateAndAssign(String[] operationValues) { - - //For operation 'btn' - if (FilterOperation.BETWEEN == operation) { - if (operationValues.length != 2) { - throw new IllegalArgumentException("For 'btn' operation two values are expected"); - } else { - - //Convert - T value1 = this.converterFunction.apply(operationValues[0]); - T value2 = this.converterFunction.apply(operationValues[1]); - - //Set min and max values - if (value1.compareTo(value2) > 0) { - this.minValue = value2; - this.maxValue = value1; - } else { - this.minValue = value1; - this.maxValue = value2; - } - } - - //For 'in' or 'nin' operation - } else if (FilterOperation.IN == operation || FilterOperation.NOT_IN == operation) { - convertedValues.addAll(originalValues.stream().map(converterFunction).collect(Collectors.toList())); - } else { - //All other operation - this.convertedSingleValue = converterFunction.apply(operationValues[0]); - } - - } - - /** - * Gets converted single value. - * - * @return the converted single value - */ - public T getConvertedSingleValue() { - return convertedSingleValue; - } - - /** - * Gets min value. - * - * @return the min value - */ - public T getMinValue() { - return minValue; - } - - /** - * Gets max value. - * - * @return the max value - */ - public T getMaxValue() { - return maxValue; - } - - /** - * Gets operation. - * - * @return the operation - */ - public FilterOperation getOperation() { - return operation; - } - - /** - * Gets field name. - * - * @return the field name - */ - public String getFieldName() { - return fieldName; - } - - - /** - * Gets converted values. - * - * @return the converted values - */ - public Collection getConvertedValues() { - return convertedValues; - } - -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterOperation.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterOperation.java deleted file mode 100644 index 03fe3b20..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterOperation.java +++ /dev/null @@ -1,78 +0,0 @@ -package ca.bc.gov.educ.api.trax.filter; - -import com.fasterxml.jackson.annotation.JsonValue; - -public enum FilterOperation { - /** - * Equal filter operation. - */ - EQUAL("eq"), - /** - * Not equal filter operation. - */ - NOT_EQUAL("neq"), - /** - * Greater than filter operation. - */ - GREATER_THAN("gt"), - /** - * Greater than or equal to filter operation. - */ - GREATER_THAN_OR_EQUAL_TO("gte"), - /** - * Less than filter operation. - */ - LESS_THAN("lt"), - /** - * Less than or equal to filter operation. - */ - LESS_THAN_OR_EQUAL_TO("lte"), - /** - * In filter operation. - */ - IN("in"), - /** - * Not in filter operation. - */ - NOT_IN("nin"), - /** - * Between filter operation. - */ - BETWEEN("btn"), - /** - * Contains filter operation. - */ - CONTAINS("like"), - /** - * Starts with filter operation. - */ - STARTS_WITH("starts_with"), - /** - * Not Starts with filter operation. - */ - NOT_STARTS_WITH("not_starts_with"), - /** - * Ends with filter operation. - */ - ENDS_WITH("ends_with"), - /** - * Starts with ignore case filter operation. - */ - STARTS_WITH_IGNORE_CASE("starts_with_ignore_case"), - /** - * Contains ignore case filter operation. - */ - CONTAINS_IGNORE_CASE("like_ignore_case"), - ; - private final String value; - - FilterOperation(String value) { - this.value = value; - } - - @Override - @JsonValue - public String toString() { - return String.valueOf(value); - } -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterSpecifications.java b/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterSpecifications.java deleted file mode 100644 index 16f498c7..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/filter/FilterSpecifications.java +++ /dev/null @@ -1,107 +0,0 @@ -package ca.bc.gov.educ.api.trax.filter; - -import jakarta.annotation.PostConstruct; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.stereotype.Service; - -import java.util.EnumMap; -import java.util.function.Function; - -/** - * The type Filter specifications. - * - * @param the type parameter - * @param the type parameter - */ -@Service -public class FilterSpecifications> { - - private EnumMap, Specification>> map; - - /** - * Instantiates a new Filter specifications. - */ - public FilterSpecifications() { - initSpecifications(); - } - - /** - * Gets specification. - * - * @param operation the operation - * @return the specification - */ - public Function, Specification> getSpecification(FilterOperation operation) { - return map.get(operation); - } - - /** - * Init specifications. - */ - @PostConstruct - public void initSpecifications() { - - map = new EnumMap<>(FilterOperation.class); - - // Equal - map.put(FilterOperation.EQUAL, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> { - if(filterCriteria.getConvertedSingleValue() == null){ - return criteriaBuilder.isNull(root.get(filterCriteria.getFieldName())); - } - return criteriaBuilder - .equal(root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue()); - }); - - map.put(FilterOperation.NOT_EQUAL, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> { - if(filterCriteria.getConvertedSingleValue() == null){ - return criteriaBuilder.isNotNull(root.get(filterCriteria.getFieldName())); - } - return criteriaBuilder - .notEqual(root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue()); - }); - - map.put(FilterOperation.GREATER_THAN, - filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.greaterThan( - root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue())); - - map.put(FilterOperation.GREATER_THAN_OR_EQUAL_TO, - filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo( - root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue())); - - map.put(FilterOperation.LESS_THAN, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder - .lessThan(root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue())); - - map.put(FilterOperation.LESS_THAN_OR_EQUAL_TO, - filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo( - root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue())); - - map.put(FilterOperation.IN, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> root - .get(filterCriteria.getFieldName()).in(filterCriteria.getConvertedValues())); - - map.put(FilterOperation.NOT_IN, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder - .not(root.get(filterCriteria.getFieldName()).in(filterCriteria.getConvertedValues()))); - - map.put(FilterOperation.BETWEEN, - filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.between( - root.get(filterCriteria.getFieldName()), filterCriteria.getMinValue(), - filterCriteria.getMaxValue())); - - map.put(FilterOperation.CONTAINS, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder - .like(root.get(filterCriteria.getFieldName()), "%" + filterCriteria.getConvertedSingleValue() + "%")); - - map.put(FilterOperation.STARTS_WITH, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder - .like(root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue() + "%")); - - map.put(FilterOperation.NOT_STARTS_WITH, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder - .notLike(root.get(filterCriteria.getFieldName()), filterCriteria.getConvertedSingleValue() + "%")); - - map.put(FilterOperation.ENDS_WITH, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder - .like(root.get(filterCriteria.getFieldName()), "%" + filterCriteria.getConvertedSingleValue())); - - map.put(FilterOperation.CONTAINS_IGNORE_CASE, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder - .like(criteriaBuilder.lower(root.get(filterCriteria.getFieldName())), "%" + filterCriteria.getConvertedSingleValue().toString().toLowerCase() + "%")); - - map.put(FilterOperation.STARTS_WITH_IGNORE_CASE, filterCriteria -> (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder - .like(criteriaBuilder.lower(root.get(filterCriteria.getFieldName())), filterCriteria.getConvertedSingleValue().toString().toLowerCase() + "%")); - } -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventHistoryMapper.java b/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventHistoryMapper.java deleted file mode 100644 index be383ddc..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventHistoryMapper.java +++ /dev/null @@ -1,90 +0,0 @@ -package ca.bc.gov.educ.api.trax.mapper; - -import ca.bc.gov.educ.api.trax.constant.EventType; -import ca.bc.gov.educ.api.trax.model.dto.AuthorityContact; -import ca.bc.gov.educ.api.trax.model.dto.DistrictContact; -import ca.bc.gov.educ.api.trax.model.dto.EventHistory; -import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; -import ca.bc.gov.educ.api.trax.model.dto.institute.MoveSchoolData; -import ca.bc.gov.educ.api.trax.model.entity.EventEntity; -import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; -import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; -import ca.bc.gov.educ.api.trax.util.JsonUtil; -import lombok.extern.slf4j.Slf4j; -import lombok.val; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Named; -import org.springframework.beans.factory.annotation.Autowired; - -@Slf4j -@Mapper(componentModel = "spring", uses = {EventMapper.class, UUIDMapper.class}) -public abstract class EventHistoryMapper { - - EducGradTraxApiConstants constants; - - @Autowired - public void setConstants(EducGradTraxApiConstants constants){ - this.constants = constants; - } - - @Mapping(source = "event", target = "eventHistoryUrl", qualifiedByName = "getUrlFromEventHistoryEntity") - public abstract EventHistory toStructure(EventHistoryEntity eventHistoryEntity); - - @Mapping(target = "event.eventPayloadBytes", ignore = true) - public abstract EventHistoryEntity toEntity(EventHistory eventHistory); - - @Named("getUrlFromEventHistoryEntity") - String getUrlFromEventHistoryEntity(EventEntity eventEntity) { - String url = null; - if (eventEntity != null) { - try { - switch (EventType.valueOf(eventEntity.getEventType())) { - case CREATE_SCHOOL, UPDATE_SCHOOL -> { - val school = JsonUtil.getJsonObjectFromString(ca.bc.gov.educ.api.trax.model.dto.institute.School.class, eventEntity.getEventPayload()); - url = this.getStudentAdminSchoolDetailsUrl(school.getSchoolId()); - } - case MOVE_SCHOOL -> { - val schoolMoved = JsonUtil.getJsonObjectFromString(MoveSchoolData.class, eventEntity.getEventPayload()); - url = this.getStudentAdminSchoolDetailsUrl(schoolMoved.getToSchool().getSchoolId()); - } - case CREATE_SCHOOL_CONTACT, UPDATE_SCHOOL_CONTACT, DELETE_SCHOOL_CONTACT -> { - val schoolContact = JsonUtil.getJsonObjectFromString(SchoolContact.class, eventEntity.getEventPayload()); - url = this.getStudentAdminSchoolDetailsUrl(schoolContact.getSchoolId()); - } - case CREATE_DISTRICT, UPDATE_DISTRICT -> { - val district = JsonUtil.getJsonObjectFromString(ca.bc.gov.educ.api.trax.model.dto.institute.District.class, eventEntity.getEventPayload()); - url = this.getStudentAdminDistrictDetailsUrl(district.getDistrictId()); - } - case CREATE_AUTHORITY_CONTACT, UPDATE_AUTHORITY_CONTACT, DELETE_AUTHORITY_CONTACT -> { - val authorityContact = JsonUtil.getJsonObjectFromString(AuthorityContact.class, eventEntity.getEventPayload()); - url = this.getStudentAdminAuthorityDetailsUrl(authorityContact.getIndependentAuthorityId()); - } - case CREATE_DISTRICT_CONTACT, UPDATE_DISTRICT_CONTACT, DELETE_DISTRICT_CONTACT -> { - val districtContact = JsonUtil.getJsonObjectFromString(DistrictContact.class, eventEntity.getEventPayload()); - url = this.getStudentAdminDistrictDetailsUrl(districtContact.getDistrictId()); - } - default -> { - return null; - } - } - } catch (final Exception exception) { - log.error(exception.getMessage()); - } - } - return url; - } - - private String getStudentAdminSchoolDetailsUrl(String schoolId) { - return String.format(constants.getStudentAdminSchoolDetailsUrl(), schoolId); - } - - private String getStudentAdminAuthorityDetailsUrl(String authorityId) { - return String.format(constants.getStudentAdminAuthorityDetailsUrl(), authorityId); - } - - private String getStudentAdminDistrictDetailsUrl(String districtId) { - return String.format(constants.getStudentAdminDistrictDetailsUrl(), districtId); - } - -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventMapper.java b/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventMapper.java deleted file mode 100644 index f9b3cff3..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/EventMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package ca.bc.gov.educ.api.trax.mapper; - -import ca.bc.gov.educ.api.trax.model.dto.Event; -import ca.bc.gov.educ.api.trax.model.entity.EventEntity; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; - -@Mapper(componentModel = "spring", uses = {UUIDMapper.class}) -public interface EventMapper { - - EventMapper mapper = Mappers.getMapper(EventMapper.class); - - @Mapping(target = "eventPayload", ignore = true) - Event toStructure(EventEntity eventEntity); - - EventEntity toEntity(Event event); - -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/UUIDMapper.java b/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/UUIDMapper.java deleted file mode 100644 index 3b735971..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/mapper/UUIDMapper.java +++ /dev/null @@ -1,22 +0,0 @@ -package ca.bc.gov.educ.api.trax.mapper; - -import org.apache.commons.lang3.StringUtils; - -import java.util.UUID; - -public class UUIDMapper { - - public UUID map(String value) { - if (StringUtils.isBlank(value)) { - return null; - } - return UUID.fromString(value); - } - - public String map(UUID value) { - if (value == null) { - return null; - } - return value.toString(); - } -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/BaseModel.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/BaseModel.java index 314337bc..5b3d5120 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/BaseModel.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/BaseModel.java @@ -1,13 +1,11 @@ package ca.bc.gov.educ.api.trax.model.dto; -import jakarta.validation.constraints.NotBlank; import lombok.Data; @Data public class BaseModel { private String createUser; private String createDate; - @NotBlank(message = "updateUser must not be null or empty") private String updateUser; private String updateDate; } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Condition.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Condition.java deleted file mode 100644 index 50d15cd2..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Condition.java +++ /dev/null @@ -1,12 +0,0 @@ -package ca.bc.gov.educ.api.trax.model.dto; - -public enum Condition { - /** - * And condition. - */ - AND, - /** - * Or condition. - */ - OR -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Event.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Event.java deleted file mode 100644 index f45058f8..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Event.java +++ /dev/null @@ -1,19 +0,0 @@ -package ca.bc.gov.educ.api.trax.model.dto; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.UUID; - -@EqualsAndHashCode(callSuper = true) -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class Event extends BaseModel { - private UUID replicationEventId; - private UUID eventId; - private String eventPayload; - private String eventStatus; - private String eventType; - private String eventOutcome; - private String activityCode; -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/EventHistory.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/EventHistory.java deleted file mode 100644 index 4fca7d8f..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/EventHistory.java +++ /dev/null @@ -1,25 +0,0 @@ -package ca.bc.gov.educ.api.trax.model.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.UUID; - -@EqualsAndHashCode(callSuper = true) -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class EventHistory extends BaseModel { - @NotNull(message = "id can not be null.") - private UUID id; - private Event event; - private String eventHistoryUrl; - @Pattern(regexp = "^[Yy|Nn]$", message = "acknowledge flag must be either Y or N.") - private String acknowledgeFlag; - - public void setAcknowledgeFlag(String acknowledgeFlag){ - this.acknowledgeFlag = acknowledgeFlag.toUpperCase(); - } -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Search.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Search.java deleted file mode 100644 index 73dc4e39..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/Search.java +++ /dev/null @@ -1,24 +0,0 @@ -package ca.bc.gov.educ.api.trax.model.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; - -@AllArgsConstructor -@NoArgsConstructor -@Data -@Builder -public class Search { - /** - * The Condition. ENUM to hold and AND OR - */ - Condition condition; - - /** - * The Search criteria list. - */ - List searchCriteriaList; -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/SearchCriteria.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/SearchCriteria.java deleted file mode 100644 index ed7b8375..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/SearchCriteria.java +++ /dev/null @@ -1,39 +0,0 @@ -package ca.bc.gov.educ.api.trax.model.dto; - -import ca.bc.gov.educ.api.trax.filter.FilterOperation; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@AllArgsConstructor -@NoArgsConstructor -@Builder -@Data -public class SearchCriteria { - /** - * The Key. - */ - @NotNull - String key; - /** - * The Operation. - */ - @NotNull - FilterOperation operation; - /** - * The Value. - */ - String value; - /** - * The Value type. - */ - @NotNull - ValueType valueType; - /** - * The Condition. ENUM to hold and AND OR - */ - Condition condition; - -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ValueType.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ValueType.java deleted file mode 100644 index 25f8148f..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/ValueType.java +++ /dev/null @@ -1,28 +0,0 @@ -package ca.bc.gov.educ.api.trax.model.dto; - -public enum ValueType { - /** - * String value type. - */ - STRING, - /** - * Integer value type. - */ - INTEGER, - /** - * Long value type. - */ - LONG, - /** - * Date value type. - */ - DATE, - /** - * Date time value type. - */ - DATE_TIME, - /** - * Uuid value type. - */ - UUID -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java index c9467b5e..ed6bd837 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventEntity.java @@ -1,6 +1,5 @@ package ca.bc.gov.educ.api.trax.model.entity; -import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.*; import org.hibernate.annotations.DynamicUpdate; import org.hibernate.annotations.GenericGenerator; @@ -59,7 +58,6 @@ public class EventEntity { /** * The EventEntity payload. */ - @JsonIgnore @NotNull(message = "eventPayload cannot be null") @Lob @Column(name = "EVENT_PAYLOAD") @@ -90,7 +88,6 @@ public class EventEntity { @Column(name = "ACTIVITY_CODE") private String activityCode; - @JsonIgnore @OneToOne(cascade = CascadeType.ALL, mappedBy = "event") private EventHistoryEntity eventHistoryEntity; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java index 22e27517..a02234c1 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/EventHistoryEntity.java @@ -4,17 +4,15 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -import lombok.*; -import lombok.experimental.SuperBuilder; +import lombok.Getter; +import lombok.Setter; import org.hibernate.annotations.DynamicUpdate; import org.hibernate.annotations.GenericGenerator; import java.util.UUID; -@Data -@NoArgsConstructor -@AllArgsConstructor -@SuperBuilder +@Getter +@Setter @Entity @Table(name = "EVENT_HISTORY") @DynamicUpdate diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java index 7f4164cc..0e3400ae 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/v2/BaseEntity.java @@ -4,18 +4,12 @@ import ca.bc.gov.educ.api.trax.util.ThreadLocalStateUtil; import jakarta.persistence.*; import jakarta.validation.constraints.PastOrPresent; -import lombok.AllArgsConstructor; import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; import org.apache.commons.lang3.StringUtils; import java.time.LocalDateTime; @Data -@NoArgsConstructor -@AllArgsConstructor -@SuperBuilder @MappedSuperclass public class BaseEntity { @@ -49,10 +43,7 @@ protected void onPersist() { private void initUserInfo() { String user = ThreadLocalStateUtil.getCurrentUser(); - if(this.updateUser == null){ - this.updateUser = (StringUtils.isBlank(user)) ? EducGradTraxApiConstants.DEFAULT_UPDATED_BY : user; - } + this.updateUser = (StringUtils.isBlank(user)) ? EducGradTraxApiConstants.DEFAULT_UPDATED_BY : user; this.createUser = (StringUtils.isBlank(createUser) && StringUtils.isBlank(user)) ? EducGradTraxApiConstants.DEFAULT_CREATED_BY : user; } - } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java index e8494da8..fe011d1d 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/EventHistoryRepository.java @@ -2,14 +2,13 @@ import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; import java.util.Optional; import java.util.UUID; @Repository -public interface EventHistoryRepository extends JpaRepository, JpaSpecificationExecutor { +public interface EventHistoryRepository extends JpaRepository { Optional findByEvent_ReplicationEventId(UUID replicationEventId); diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java index 737a40a3..57eae2a1 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/EventHistoryService.java @@ -1,67 +1,24 @@ package ca.bc.gov.educ.api.trax.service; -import ca.bc.gov.educ.api.trax.exception.EntityNotFoundException; -import ca.bc.gov.educ.api.trax.exception.InvalidParameterException; import ca.bc.gov.educ.api.trax.exception.ServiceException; -import ca.bc.gov.educ.api.trax.exception.TraxAPIRuntimeException; -import ca.bc.gov.educ.api.trax.filter.EventHistoryFilterSpecifics; -import ca.bc.gov.educ.api.trax.filter.FilterOperation; -import ca.bc.gov.educ.api.trax.mapper.EventHistoryMapper; -import ca.bc.gov.educ.api.trax.model.dto.*; -import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; -import ca.bc.gov.educ.api.trax.repository.EventHistoryRepository; import ca.bc.gov.educ.api.trax.repository.EventRepository; -import ca.bc.gov.educ.api.trax.util.RequestUtil; -import ca.bc.gov.educ.api.trax.util.TransformUtil; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.jboss.threads.EnhancedQueueExecutor; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import java.time.Duration; import java.time.LocalDateTime; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.Executor; @Slf4j @Service public class EventHistoryService { - private final EventHistoryMapper mapper; - private final EventHistoryFilterSpecifics eventHistoryFilterSpecs; - private final EventRepository eventRepository; - private final EventHistoryRepository eventHistoryRepository; - private final Executor paginatedQueryExecutor = new EnhancedQueueExecutor.Builder() - .setThreadFactory(new ThreadFactoryBuilder().setNameFormat("async-pagination-query-executor-%d").build()) - .setCorePoolSize(2).setMaximumPoolSize(10).setKeepAliveTime(Duration.ofSeconds(60)).build(); + private EventRepository eventRepository; @Autowired - public EventHistoryService(EventHistoryMapper mapper, EventHistoryFilterSpecifics eventHistoryFilterSpecs, EventRepository eventRepository, EventHistoryRepository eventHistoryRepository) { - this.mapper = mapper; - this.eventHistoryFilterSpecs = eventHistoryFilterSpecs; + public EventHistoryService(EventRepository eventRepository) { this.eventRepository = eventRepository; - this.eventHistoryRepository = eventHistoryRepository; } - /** - * Deletes old records from event and event history - * @param sinceBefore the LocalDateTime object. Removes records older than this. - * @throws ServiceException if there is an issue - */ public void purgeOldEventAndEventHistoryRecords(LocalDateTime sinceBefore) throws ServiceException { try { this.eventRepository.deleteByCreateDateLessThan(sinceBefore); @@ -70,134 +27,5 @@ public void purgeOldEventAndEventHistoryRecords(LocalDateTime sinceBefore) throw } } - private Specification getSpecifications(Specification schoolSpecs, int i, Search search) { - if (i == 0) { - schoolSpecs = getEventHistorySpecification(search.getSearchCriteriaList()); - } else { - if (search.getCondition() == Condition.AND) { - schoolSpecs = schoolSpecs.and(getEventHistorySpecification(search.getSearchCriteriaList())); - } else { - schoolSpecs = schoolSpecs.or(getEventHistorySpecification(search.getSearchCriteriaList())); - } - } - return schoolSpecs; - } - - public Specification setSpecificationAndSortCriteria(String sortCriteriaJson, String searchCriteriaListJson, ObjectMapper objectMapper, List sorts) { - Specification eventHistoryEntitySpecification = null; - try { - RequestUtil.getSortCriteria(sortCriteriaJson, objectMapper, sorts); - if (StringUtils.isNotBlank(searchCriteriaListJson)) { - List searches = objectMapper.readValue(searchCriteriaListJson, new TypeReference<>() { - }); - int i = 0; - for (var search : searches) { - eventHistoryEntitySpecification = getSpecifications(eventHistoryEntitySpecification, i, search); - i++; - } - } - } catch (JsonProcessingException e) { - throw new TraxAPIRuntimeException(e.getMessage()); - } - return eventHistoryEntitySpecification; - } - private Specification getEventHistorySpecification(List criteriaList) { - Specification eventHistoryEntitySpecification = null; - if (!criteriaList.isEmpty()) { - int i = 0; - for (SearchCriteria criteria : criteriaList) { - if (criteria.getKey() != null && criteria.getOperation() != null && criteria.getValueType() != null) { - var criteriaValue = criteria.getValue(); - if(StringUtils.isNotBlank(criteria.getValue()) && TransformUtil.isUppercaseField(EventHistoryEntity.class, criteria.getKey())) { - criteriaValue = criteriaValue.toUpperCase(); - } - Specification typeSpecification = getTypeSpecification(criteria.getKey(), criteria.getOperation(), criteriaValue, criteria.getValueType()); - eventHistoryEntitySpecification = getSpecificationPerGroup(eventHistoryEntitySpecification, i, criteria, typeSpecification); - i++; - } else { - throw new InvalidParameterException("Search Criteria can not contain null values for key, value and operation type"); - } - } - } - return eventHistoryEntitySpecification; - } - - private Specification getSpecificationPerGroup(Specification eventHistoryEntitySpecification, int i, SearchCriteria criteria, Specification typeSpecification) { - if (i == 0) { - eventHistoryEntitySpecification = Specification.where(typeSpecification); - } else { - if (criteria.getCondition() == Condition.AND) { - eventHistoryEntitySpecification = eventHistoryEntitySpecification.and(typeSpecification); - } else { - eventHistoryEntitySpecification = eventHistoryEntitySpecification.or(typeSpecification); - } - } - return eventHistoryEntitySpecification; - } - - private Specification getTypeSpecification(String key, FilterOperation filterOperation, String value, ValueType valueType) { - Specification schoolEntitySpecification = null; - switch (valueType) { - case STRING: - schoolEntitySpecification = eventHistoryFilterSpecs.getStringTypeSpecification(key, value, filterOperation); - break; - case DATE_TIME: - schoolEntitySpecification = eventHistoryFilterSpecs.getDateTimeTypeSpecification(key, value, filterOperation); - break; - case LONG: - schoolEntitySpecification = eventHistoryFilterSpecs.getLongTypeSpecification(key, value, filterOperation); - break; - case INTEGER: - schoolEntitySpecification = eventHistoryFilterSpecs.getIntegerTypeSpecification(key, value, filterOperation); - break; - case DATE: - schoolEntitySpecification = eventHistoryFilterSpecs.getDateTypeSpecification(key, value, filterOperation); - break; - case UUID: - schoolEntitySpecification = eventHistoryFilterSpecs.getUUIDTypeSpecification(key, value, filterOperation); - break; - default: - break; - } - return schoolEntitySpecification; - } - - - @Transactional(propagation = Propagation.SUPPORTS) - public CompletableFuture> findAll(Specification eventHistorySpecs, final Integer pageNumber, final Integer pageSize, final List sorts) { - log.trace("In find all query: {}", eventHistorySpecs); - return CompletableFuture.supplyAsync(() -> { - Pageable paging = PageRequest.of(pageNumber, pageSize, Sort.by(sorts)); - try { - log.trace("Running paginated query: {}", eventHistorySpecs); - var results = this.eventHistoryRepository.findAll(eventHistorySpecs, paging); - log.trace("Paginated query returned with results: {}", results); - return results; - } catch (final Throwable ex) { - log.error("Failure querying for paginated schools: {}", ex.getMessage()); - throw new CompletionException(ex); - } - }, paginatedQueryExecutor); - - } - - /** - * Updates the event history. Currently, the only fields that will update are - * updateUser and acknowledgeFlag - * @param eventHistory the history event you want to update - * @return the updated history event - */ - public EventHistory updateEventHistory(EventHistory eventHistory) { - EventHistoryEntity eventHistoryEntity = eventHistoryRepository.findById(eventHistory.getId()).orElse(null); - if(eventHistoryEntity != null){ - eventHistoryEntity.setUpdateUser(eventHistory.getUpdateUser()); - eventHistoryEntity.setUpdateDate(LocalDateTime.now()); - eventHistoryEntity.setAcknowledgeFlag(eventHistory.getAcknowledgeFlag()); - eventHistoryRepository.save(eventHistoryEntity); - return mapper.toStructure(eventHistoryEntity); - } else { - throw new EntityNotFoundException(String.format("EventHistory with id: %s not found", eventHistory.getId())); - } - } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java index abcc5051..3dd06d0c 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/util/EducGradTraxApiConstants.java @@ -87,9 +87,6 @@ public class EducGradTraxApiConstants { public static final String POST_SAVE_TRAX_STUDENT_NO_MAPPING = "/trax-student-no"; public static final String DELETE_TRAX_STUDENT_NO_MAPPING = "/trax-student-no/{pen}"; - // Event urls - public static final String EVENT_HISTORY_MAPPING_V1 = GRAD_TRAX_API_ROOT_MAPPING_V1 + "/event/history"; - //Default Attribute value constants public static final String DEFAULT_CREATED_BY = "API_GRAD_TRAX"; protected static final Date DEFAULT_CREATED_TIMESTAMP = new Date(); @@ -178,15 +175,6 @@ public class EducGradTraxApiConstants { @Value("${endpoint.institute-api.get-all-school-funding-group-codes.url}") private String allSchoolFundingGroupCodesFromInstituteApiUrl; - @Value("${endpoint.student-admin.school-details.url}") - private String studentAdminSchoolDetailsUrl; - - @Value("${endpoint.student-admin.district-details.url}") - private String studentAdminDistrictDetailsUrl; - - @Value("${endpoint.student-admin.authority-details.url}") - private String studentAdminAuthorityDetailsUrl; - // Scheduler: ongoing updates from TRAX to GRAD @Value("${cron.scheduled.process.events.trax-to-grad.run}") private String traxToGradCronRun; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/RequestUtil.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/RequestUtil.java deleted file mode 100644 index 96cf4101..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/util/RequestUtil.java +++ /dev/null @@ -1,39 +0,0 @@ -package ca.bc.gov.educ.api.trax.util; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.lang3.StringUtils; -import org.springframework.data.domain.Sort; - -import java.util.List; -import java.util.Map; - -public class RequestUtil { - - private RequestUtil() { - } - - /** - * Get the Sort.Order list from JSON string - * - * @param sortCriteriaJson The sort criterio JSON - * @param objectMapper The object mapper - * @param sorts The Sort.Order list - * @throws JsonProcessingException the json processing exception - */ - public static void getSortCriteria(String sortCriteriaJson, ObjectMapper objectMapper, List sorts) throws JsonProcessingException { - if (StringUtils.isNotBlank(sortCriteriaJson)) { - Map sortMap = objectMapper.readValue(sortCriteriaJson, new TypeReference<>() { - }); - sortMap.forEach((k, v) -> { - if ("ASC".equalsIgnoreCase(v)) { - sorts.add(new Sort.Order(Sort.Direction.ASC, k)); - } else { - sorts.add(new Sort.Order(Sort.Direction.DESC, k)); - } - }); - } - } - -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/TransformUtil.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/TransformUtil.java deleted file mode 100644 index 76ccf040..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/util/TransformUtil.java +++ /dev/null @@ -1,62 +0,0 @@ -package ca.bc.gov.educ.api.trax.util; - -import ca.bc.gov.educ.api.trax.exception.TraxAPIRuntimeException; - -import java.beans.Expression; -import java.beans.Statement; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.springframework.util.StringUtils.capitalize; - -/** - * The type Transform util. - */ -public class TransformUtil { - - private TransformUtil() { - } - - /** - * Is uppercase field boolean. - * - * @param clazz the clazz - * @param fieldName the field name - * @return the boolean - */ - public static boolean isUppercaseField(Class clazz, String fieldName) { - var superClazz = clazz; - while (!superClazz.equals(Object.class)) { - try { - Field field = superClazz.getDeclaredField(fieldName); - return field.getAnnotation(UpperCase.class) != null; - } catch (NoSuchFieldException e) { - superClazz = superClazz.getSuperclass(); - } - } - return false; - } - - private static void transformFieldToUppercase(Field field, T claz) { - if (!field.getType().equals(String.class)) { - return; - } - - if (field.getAnnotation(UpperCase.class) != null) { - try { - var fieldName = capitalize(field.getName()); - var expr = new Expression(claz, "get" + fieldName, new Object[0]); - var entityFieldValue = (String) expr.getValue(); - if (entityFieldValue != null) { - var stmt = new Statement(claz, "set" + fieldName, new Object[]{entityFieldValue.toUpperCase()}); - stmt.execute(); - } - } catch (Exception ex) { - throw new TraxAPIRuntimeException(ex.getMessage()); - } - } - - } -} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/UpperCase.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/UpperCase.java deleted file mode 100644 index 28951128..00000000 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/util/UpperCase.java +++ /dev/null @@ -1,11 +0,0 @@ -package ca.bc.gov.educ.api.trax.util; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -public @interface UpperCase { -} diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml index ddea4b2a..3424dc39 100644 --- a/api/src/main/resources/application.yaml +++ b/api/src/main/resources/application.yaml @@ -194,13 +194,6 @@ endpoint: url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/category-codes get-all-school-funding-group-codes: url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/funding-group-codes - student-admin: - school-details: - url: ${STUDENT_ADMIN_URL_ROOT}institute/school/%s/details - district-details: - url: ${STUDENT_ADMIN_URL_ROOT}district/%s - authority-details: - url: ${STUDENT_ADMIN_URL_ROOT}authority/%s # other properties props: diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/controller/EventHistoryControllerTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/controller/EventHistoryControllerTest.java deleted file mode 100644 index 1b3eb304..00000000 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/controller/EventHistoryControllerTest.java +++ /dev/null @@ -1,168 +0,0 @@ -package ca.bc.gov.educ.api.trax.controller; - -import ca.bc.gov.educ.api.trax.EducGradTraxApiApplication; -import ca.bc.gov.educ.api.trax.filter.FilterOperation; -import ca.bc.gov.educ.api.trax.mapper.EventHistoryMapper; -import ca.bc.gov.educ.api.trax.model.dto.EventHistory; -import ca.bc.gov.educ.api.trax.model.dto.Search; -import ca.bc.gov.educ.api.trax.model.dto.SearchCriteria; -import ca.bc.gov.educ.api.trax.model.dto.ValueType; -import ca.bc.gov.educ.api.trax.model.entity.EventEntity; -import ca.bc.gov.educ.api.trax.repository.EventHistoryRepository; -import ca.bc.gov.educ.api.trax.repository.EventRepository; -import ca.bc.gov.educ.api.trax.support.TestUtils; -import ca.bc.gov.educ.api.trax.util.BaseEventHistoryTest; -import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; -import ca.bc.gov.educ.api.trax.util.JsonUtil; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockitoAnnotations; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; - -import java.time.LocalDateTime; -import java.util.*; - -import static org.hamcrest.Matchers.hasSize; -import static org.springframework.http.MediaType.APPLICATION_JSON; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin; - -@SpringBootTest(classes = { EducGradTraxApiApplication.class }) -@ActiveProfiles("test") -@AutoConfigureMockMvc -class EventHistoryControllerTest extends BaseEventHistoryTest { - - private static final String READ_SCOPE = "SCOPE_READ_EVENT_HISTORY"; - private static final String WRITE_SCOPE = "SCOPE_WRITE_EVENT_HISTORY"; - private AutoCloseable closeable; - - @Autowired - private EventHistoryMapper mapper; - - @Autowired - private MockMvc mockMvc; - - @Autowired - private EventHistoryRepository eventHistoryRepository; - - @Autowired - private EventRepository eventRepository; - - - @BeforeEach - void setUp() { - closeable = MockitoAnnotations.openMocks(this); - EventEntity event = this.eventRepository.save(this.createEventData()); - this.eventHistoryRepository.save(this.createEventHistoryData(event)); - } - - @AfterEach - void tearDown() throws Exception { - this.eventRepository.deleteAll(); - closeable.close(); - } - - @Test - void testReadEventHistoryPaginated_givenValueNull_ShouldReturnStatusOk() throws Exception { - final SearchCriteria criteria = SearchCriteria.builder().key("website").operation(FilterOperation.EQUAL).value(null).valueType(ValueType.STRING).build(); - final List criteriaList = new ArrayList<>(); - criteriaList.add(criteria); - final List searches = new LinkedList<>(); - searches.add(Search.builder().searchCriteriaList(criteriaList).build()); - final ObjectMapper objectMapper = new ObjectMapper(); - final String criteriaJSON = objectMapper.writeValueAsString(searches); - this.mockMvc.perform(get(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1 + "/paginated").with(getMockAuthority(READ_SCOPE)).param("searchCriteriaList", criteriaJSON) - .contentType(APPLICATION_JSON)).andDo(print()).andExpect(status().isOk()); - } - - @Test - void testReadEventHistoryPaginated_givenUserNameFilter_ShouldReturnStatusOk() throws Exception { - final ObjectMapper objectMapper = new ObjectMapper(); - final SearchCriteria criteria = SearchCriteria.builder().key("createUser").operation(FilterOperation.EQUAL).value(USER).valueType(ValueType.STRING).build(); - final List criteriaList = new ArrayList<>(); - criteriaList.add(criteria); - final List searches = new LinkedList<>(); - searches.add(Search.builder().searchCriteriaList(criteriaList).build()); - final String criteriaJSON = objectMapper.writeValueAsString(searches); - final MvcResult result = this.mockMvc - .perform(get(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1 + "/paginated").with(getMockAuthority(READ_SCOPE)).param("searchCriteriaList", criteriaJSON) - .contentType(APPLICATION_JSON)) - .andReturn(); - this.mockMvc.perform(asyncDispatch(result)).andDo(print()).andExpect(status().isOk()).andExpect(jsonPath("$.content", hasSize(1))); - } - - // test update goes ok - @Test - void testUpdateEventHistory_givenValidObject_shouldReturnStatusOk() throws Exception { - final String acknowledgeFlag = "Y"; - final String updateUser = "Wattie"; - var eventHistory = createEventHistoryData(); - eventHistory.setAcknowledgeFlag(acknowledgeFlag); - eventHistory.setUpdateUser(updateUser); - this.mockMvc.perform(MockMvcRequestBuilders - .put(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1) - .with(getMockAuthority(WRITE_SCOPE)) - .content(JsonUtil.getJsonStringFromObject(eventHistory)) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(MockMvcResultMatchers.jsonPath("$.acknowledgeFlag").value(acknowledgeFlag)) - .andExpect(MockMvcResultMatchers.jsonPath("$.updateUser").value(updateUser)); - } - - // test update returns bad request - @Test - void testUpdateEventHistory_givenBadDate_shouldReturnStatusBadRequest() throws Exception { - var eventHistory = createEventHistoryData(); - eventHistory.setAcknowledgeFlag("Q"); - eventHistory.setUpdateUser(null); - this.mockMvc.perform(MockMvcRequestBuilders - .put(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1) - .with(getMockAuthority(WRITE_SCOPE)) - .content(JsonUtil.getJsonStringFromObject(eventHistory)) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON)) - .andExpect(status().isBadRequest()); - } - - // test update not found - @Test - void testUpdateEventHistory_givenInvalidId_shouldReturnStatusNotFound() throws Exception { - var eventHistory = createEventHistoryData(); - eventHistory.setId(UUID.randomUUID()); - this.mockMvc.perform(MockMvcRequestBuilders - .put(EducGradTraxApiConstants.EVENT_HISTORY_MAPPING_V1) - .with(getMockAuthority(WRITE_SCOPE)) - .content(JsonUtil.getJsonStringFromObject(eventHistory)) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON)) - .andExpect(status().isNotFound()); - } - - private EventHistory createEventHistoryData() throws JsonProcessingException { - var event = TestUtils.createEvent("DELETE_DISTRICT_CONTACT", TestUtils.createDistrictContact(), LocalDateTime.now(), eventRepository); - var eventHistory = TestUtils.createEventHistory(event, LocalDateTime.now(), eventHistoryRepository); - return mapper.toStructure(eventHistory); - } - - private SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor getMockAuthority(String scope) { - final GrantedAuthority grantedAuthority = () -> scope; - return oidcLogin().authorities(grantedAuthority); - } -} \ No newline at end of file diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/mapper/MapperTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/mapper/MapperTest.java deleted file mode 100644 index f2b19418..00000000 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/mapper/MapperTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package ca.bc.gov.educ.api.trax.mapper; - -import ca.bc.gov.educ.api.trax.EducGradTraxApiApplication; -import ca.bc.gov.educ.api.trax.constant.EventType; -import ca.bc.gov.educ.api.trax.model.dto.AuthorityContact; -import ca.bc.gov.educ.api.trax.model.dto.EventHistory; -import ca.bc.gov.educ.api.trax.model.dto.institute.District; -import ca.bc.gov.educ.api.trax.model.dto.institute.MoveSchoolData; -import ca.bc.gov.educ.api.trax.model.dto.institute.School; -import ca.bc.gov.educ.api.trax.model.entity.EventEntity; -import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; -import ca.bc.gov.educ.api.trax.support.TestUtils; -import ca.bc.gov.educ.api.trax.util.BaseEventHistoryTest; -import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; -import ca.bc.gov.educ.api.trax.util.JsonUtil; -import com.fasterxml.jackson.core.JsonProcessingException; -import org.apache.commons.lang3.tuple.Pair; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -import java.util.UUID; - -@SpringBootTest(classes = { EducGradTraxApiApplication.class }) -@ActiveProfiles("test") -class MapperTest extends BaseEventHistoryTest { - - @Autowired - private EventHistoryMapper eventHistoryMapper; - @Autowired - private EducGradTraxApiConstants constants; - private static final UUIDMapper uuidMapper = new UUIDMapper(); - - @Test - void testGetMapper_shouldNotBeNull() { - Assertions.assertNotNull(EventMapper.mapper); - } - - @Test - void testUUIDMapper_givenValidString_shouldReturnUUID() { - Assertions.assertNotNull(uuidMapper.map(UUID.randomUUID().toString())); - } - - @Test - void testUUIDMapper_givenValidUUID_shouldReturnString() { - Assertions.assertNotNull(uuidMapper.map(UUID.randomUUID())); - } - - @Test - void testUUIDMapper_givenBlankString_shouldReturnNull() { - Assertions.assertNull(uuidMapper.map("")); - } - - - @Test - void testToEventHistory_givenSchoolEvent_shouldReturnCorrectUrl() throws JsonProcessingException { - School school = TestUtils.createSchool(); - Pair pair = createUrlAndEntity("CREATE_SCHOOL", school, school.getSchoolId()); - EventHistory eventHistory = eventHistoryMapper.toStructure(pair.getRight()); - Assertions.assertEquals(true, eventHistory.getEventHistoryUrl().equals(pair.getLeft())); - } - - @Test - void testToEventHistory_givenMoveSchoolEvent_shouldReturnCorrectUrl() throws JsonProcessingException { - MoveSchoolData school = TestUtils.createMoveSchoolData(); - Pair pair = createUrlAndEntity("MOVE_SCHOOL", school, school.getToSchool().getSchoolId()); - EventHistory eventHistory = eventHistoryMapper.toStructure(pair.getRight()); - Assertions.assertEquals(true, eventHistory.getEventHistoryUrl().equals(pair.getLeft())); - } - - @Test - void testToEventHistory_givenCreateDistrictEvent_shouldReturnCorrectUrl() throws JsonProcessingException { - District district = TestUtils.createDistrict(); - Pair pair = createUrlAndEntity("CREATE_DISTRICT", district, district.getDistrictId()); - EventHistory eventHistory = eventHistoryMapper.toStructure(pair.getRight()); - Assertions.assertEquals(true, eventHistory.getEventHistoryUrl().equals(pair.getLeft())); - } - - @Test - void testToEventHistory_givenCreateDistrictContact_shouldReturnCorrectUrl() throws JsonProcessingException { - District district = TestUtils.createDistrict(); - Pair pair = createUrlAndEntity("CREATE_DISTRICT_CONTACT", district, district.getDistrictId()); - EventHistory eventHistory = eventHistoryMapper.toStructure(pair.getRight()); - Assertions.assertEquals(true, eventHistory.getEventHistoryUrl().equals(pair.getLeft())); - } - - @Test - void testToEventHistory_givenCreateAuthorityContact_shouldReturnCorrectUrl() throws JsonProcessingException { - AuthorityContact authorityContact = TestUtils.createAuthorityContact(); - Pair pair = createUrlAndEntity("CREATE_AUTHORITY_CONTACT", authorityContact, authorityContact.getIndependentAuthorityId()); - EventHistory eventHistory = eventHistoryMapper.toStructure(pair.getRight()); - Assertions.assertEquals(true, eventHistory.getEventHistoryUrl().equals(pair.getLeft())); - } - - private Pair createUrlAndEntity(String eventType, Object eventPayload, String id) throws JsonProcessingException { - EventEntity eventEntity = this.createEventData(); - eventEntity.setEventType(eventType); - eventEntity.setEventPayload(JsonUtil.getJsonStringFromObject(eventPayload)); - EventHistoryEntity eventHistoryEntity = this.createEventHistoryData(eventEntity); - String url = String.format(resolveURL(eventType), id); - return Pair.of(url, eventHistoryEntity); - } - - private String resolveURL(String eventType) { - switch (EventType.valueOf(eventType)) { - case CREATE_SCHOOL, UPDATE_SCHOOL, MOVE_SCHOOL, CREATE_SCHOOL_CONTACT, UPDATE_SCHOOL_CONTACT, DELETE_SCHOOL_CONTACT -> { - return constants.getStudentAdminSchoolDetailsUrl(); - } - case CREATE_DISTRICT, UPDATE_DISTRICT, CREATE_DISTRICT_CONTACT, UPDATE_DISTRICT_CONTACT, DELETE_DISTRICT_CONTACT -> { - return constants.getStudentAdminDistrictDetailsUrl(); - } - case CREATE_AUTHORITY_CONTACT, UPDATE_AUTHORITY_CONTACT, DELETE_AUTHORITY_CONTACT -> { - return constants.getStudentAdminAuthorityDetailsUrl(); - } - default -> { - return null; - } - } - } - -} diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java index b6393607..8ae57049 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java @@ -1,7 +1,6 @@ package ca.bc.gov.educ.api.trax.service; import ca.bc.gov.educ.api.trax.EducGradTraxApiApplication; -import ca.bc.gov.educ.api.trax.mapper.EventHistoryMapperImpl; import ca.bc.gov.educ.api.trax.messaging.NatsConnection; import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; @@ -21,7 +20,7 @@ import redis.clients.jedis.JedisCluster; @RunWith(SpringRunner.class) -@SpringBootTest(classes = {EducGradTraxApiApplication.class, EventHistoryMapperImpl.class, EventHistoryMapperImpl.class}) +@SpringBootTest(classes = {EducGradTraxApiApplication.class}) @ActiveProfiles("test") @AutoConfigureMockMvc public abstract class BaseReplicationServiceTest { diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java index d216ef88..424bc75f 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/EventHistoryServiceTest.java @@ -1,22 +1,16 @@ package ca.bc.gov.educ.api.trax.service; -import ca.bc.gov.educ.api.trax.exception.TraxAPIRuntimeException; import ca.bc.gov.educ.api.trax.model.entity.EventEntity; import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; import ca.bc.gov.educ.api.trax.repository.EventHistoryRepository; import ca.bc.gov.educ.api.trax.repository.EventRepository; import ca.bc.gov.educ.api.trax.support.TestUtils; -import ca.bc.gov.educ.api.trax.util.JsonUtil; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.domain.Specification; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; public class EventHistoryServiceTest extends BaseReplicationServiceTest { @@ -67,18 +61,4 @@ public void purgeOldEventAndEventHistoryRecords_givenNoExceptionAndNewRecord_sho Optional eventHistoryThatShouldNotBePurgedAlso = eventHistoryRepository.findById(eventHistory.getId()); Assert.assertTrue(eventHistoryThatShouldNotBePurgedAlso.isPresent() && eventThatShouldNotBePurgedOptional.isPresent()); } - - @Test - public void setSpecificationAndSortCriteria_givenValidData_shouldReturnOk() throws TraxAPIRuntimeException{ - String sort = "{ \"schoolNumber\": \"ASC\" }"; - String searchParams = "[{\"condition\":null,\"searchCriteriaList\":[{\"key\":\"openedDate\",\"operation\":\"lte\",\"value\":\"2024-08-26T09:05:51.782\",\"valueType\":\"DATE_TIME\",\"condition\":\"AND\"},{\"key\":\"strAnd\",\"operation\":\"eq\",\"value\":\"Test String\",\"valueType\":\"STRING\",\"condition\":\"AND\"},{\"key\":\"longOr\",\"operation\":\"gt\",\"value\":\"1230\",\"valueType\":\"LONG\",\"condition\":\"OR\"},{\"key\":\"intOr\",\"operation\":\"gte\",\"value\":\"12\",\"valueType\":\"INTEGER\",\"condition\":\"OR\"},{\"key\":\"dateAnd\",\"operation\":\"eq\",\"value\":\"2024-08-26\",\"valueType\":\"DATE\",\"condition\":\"AND\"},{\"key\":\"uuidOr\",\"operation\":\"eq\",\"value\":\"6f84aa52-ad90-4f04-be66-04614ed24c37\",\"valueType\":\"UUID\",\"condition\":\"OR\"}]}]"; - Specification eventHistorySpecs = eventHistoryService.setSpecificationAndSortCriteria(sort, searchParams, JsonUtil.mapper, new ArrayList<>()); - Assert.assertNotNull(eventHistorySpecs); - } - - @Test - public void setSpecificationAndSortCriteria_givenInvalisData_shouldThrowTraxAPIRuntimeException() { - final List sorts = new ArrayList<>(); - Assert.assertThrows(TraxAPIRuntimeException.class, () -> eventHistoryService.setSpecificationAndSortCriteria(null, "{ \"bunkjunk\": \"ASC\" }", JsonUtil.mapper, sorts)); - } } \ No newline at end of file diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/util/BaseEventHistoryTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/util/BaseEventHistoryTest.java deleted file mode 100644 index 1f52574f..00000000 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/util/BaseEventHistoryTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package ca.bc.gov.educ.api.trax.util; - -import ca.bc.gov.educ.api.trax.messaging.NatsConnection; -import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; -import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; -import ca.bc.gov.educ.api.trax.model.entity.EventEntity; -import ca.bc.gov.educ.api.trax.model.entity.EventHistoryEntity; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; -import redis.clients.jedis.JedisCluster; - -import java.time.LocalDateTime; -import java.util.UUID; - -public abstract class BaseEventHistoryTest { - - protected static final String USER = "TEST"; - - @MockBean - private Publisher publisher; - @MockBean - private Subscriber subscriber; - @MockBean - private NatsConnection natsConnection; - @MockBean - private ClientRegistrationRepository clientRegistrationRepository; - @MockBean - private OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository; - @MockBean - private OAuth2AuthorizedClientService oAuth2AuthorizedClientService; - @MockBean - private JedisConnectionFactory jedisConnectionFactoryMock; - @MockBean - private JedisCluster jedisClusterMock; - - protected EventEntity createEventData() { - return EventEntity.builder() - .eventId(UUID.randomUUID()) - .eventPayload("") - .eventStatus("PROCESSED") - .eventType("CREATE_SCHOOL_CONTACT") - .eventOutcome("SCHOOL_CONTACT_CREATED") - .createUser(USER) - .createDate(LocalDateTime.now()) - .updateUser(USER) - .updateDate(LocalDateTime.now()) - .activityCode("INSTITUTE_EVENT") - .build(); - - } - - protected EventHistoryEntity createEventHistoryData(EventEntity event) { - return EventHistoryEntity.builder() - .event(event) - .acknowledgeFlag("N") - .createDate(LocalDateTime.now()) - .createUser(USER) - .updateDate(LocalDateTime.now()) - .updateUser(USER) - .build(); - } - -} diff --git a/api/src/test/resources/application.yaml b/api/src/test/resources/application.yaml index 4672df06..b1c45be8 100644 --- a/api/src/test/resources/application.yaml +++ b/api/src/test/resources/application.yaml @@ -152,13 +152,6 @@ endpoint: url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/category-codes get-all-school-funding-group-codes: url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/funding-group-codes - student-admin: - school-details: - url: https://student-admin-8878b4-dev.apps.silver.devops.gov.bc.ca/institute/school/%s/details - district-details: - url: https://student-admin-8878b4-dev.apps.silver.devops.gov.bc.ca/district/%s - authority-details: - url: https://student-admin-8878b4-dev.apps.silver.devops.gov.bc.ca/authority/%s # other properties props: diff --git a/tools/config/update-configmap.sh b/tools/config/update-configmap.sh index de8112e3..f21e0879 100644 --- a/tools/config/update-configmap.sh +++ b/tools/config/update-configmap.sh @@ -8,7 +8,6 @@ COMMON_NAMESPACE=$4 BUSINESS_NAMESPACE=$5 SPLUNK_TOKEN=$6 APP_LOG_LEVEL=$7 -STUDENT_ADMIN_URL_ROOT=$8 SPLUNK_URL="gww.splunk.educ.gov.bc.ca" FLB_CONFIG="[SERVICE] @@ -75,7 +74,6 @@ oc create -n "$GRAD_NAMESPACE"-"$envValue" configmap "$APP_NAME"-config-map \ --from-literal=INSTITUTE_API_URL_ROOT="http://institute-api-master.$COMMON_NAMESPACE-$envValue.svc.cluster.local:8080/" \ --from-literal=MAX_RETRY_ATTEMPTS="3" \ --from-literal=SCHOOL_CACHE_EXPIRY_IN_MINS="240" \ - --from-literal=STUDENT_ADMIN_URL_ROOT="$STUDENT_ADMIN_URL_ROOT" \ --dry-run=client -o yaml | oc apply -f - echo Creating config map "$APP_NAME"-flb-sc-config-map From 28ced7c16a5722ff716e964f1159362b0961b480 Mon Sep 17 00:00:00 2001 From: "chris.ditcher" Date: Tue, 10 Sep 2024 10:23:18 -0700 Subject: [PATCH 13/16] Revert "Fixing syntax error in configmap" This reverts commit 3f9224c3a0f9e0506bff6a81c346bdf8ca1df543. --- tools/config/update-configmap.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/config/update-configmap.sh b/tools/config/update-configmap.sh index f21e0879..702127fb 100644 --- a/tools/config/update-configmap.sh +++ b/tools/config/update-configmap.sh @@ -54,8 +54,8 @@ echo Creating config map "$APP_NAME"-config-map oc create -n "$GRAD_NAMESPACE"-"$envValue" configmap "$APP_NAME"-config-map \ --from-literal=APP_LOG_LEVEL="$APP_LOG_LEVEL" \ --from-literal=BASELINE_ON_MIGRATE="true" \ - --from-literal=CRON_SCHEDULED_PURGE_OLD_RECORDS="0 0 0 * * *" \ - --from-literal=RECORDS_STALE_IN_DAYS=365 \ + --from-literal=CRON_SCHEDULED_PURGE_OLD_RECORDS: "0 0 0 * * *" \ + --from-literal=RECORDS_STALE_IN_DAYS: 365 \ --from-literal=CRON_SCHEDULED_GRAD_TO_TRAX_EVENTS="0 0/5 * * * *" \ --from-literal=CRON_SCHEDULED_GRAD_TO_TRAX_EVENTS_LOCK_AT_LEAST_FOR="PT1M" \ --from-literal=CRON_SCHEDULED_GRAD_TO_TRAX_EVENTS_LOCK_AT_MOST_FOR="PT5M" \ @@ -80,4 +80,4 @@ echo Creating config map "$APP_NAME"-flb-sc-config-map oc create -n "$GRAD_NAMESPACE"-"$envValue" configmap "$APP_NAME"-flb-sc-config-map \ --from-literal=fluent-bit.conf="$FLB_CONFIG" \ --from-literal=parsers.conf="$PARSER_CONFIG" \ - --dry-run=client -o yaml | oc apply -f - \ No newline at end of file + --dry-run=client -o yaml | oc apply -f - From 3c10571ba83ec36079378e4e73b80ecb51360a22 Mon Sep 17 00:00:00 2001 From: "chris.ditcher" Date: Wed, 11 Sep 2024 09:50:35 -0700 Subject: [PATCH 14/16] Updating debug messages. --- .../gov/educ/api/trax/controller/v2/SchoolController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java index 5b6a9c3b..79ecdfd3 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java @@ -44,7 +44,7 @@ public SchoolController(SchoolService schoolService, GradValidation validation, @Operation(summary = "Find All Schools from Cache", description = "Get All Schools from Cache", tags = { "School" }) @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) public List getAllSchools() { - log.debug("getAllSchools : "); + log.debug("getAllSchools V2 : "); return schoolService.getSchoolsFromRedisCache(); } @@ -54,7 +54,7 @@ public List getAllSchools() { @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "204", description = "NO CONTENT")}) public ResponseEntity getSchoolByMincode(@PathVariable String minCode) { - log.debug("getSchoolByMincode : "); + log.debug("getSchoolByMincode V2 : "); School schoolResponse = schoolService.getSchoolByMincodeFromRedisCache(minCode); if(schoolResponse != null) { return response.GET(schoolResponse); @@ -68,7 +68,7 @@ public ResponseEntity getSchoolByMincode(@PathVariable String minCode) { @Operation(summary = "Find All School details from Cache", description = "Get All School details from Cache", tags = { "School" }) @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) public List getAllSchoolDetails() { - log.debug("getAllSchoolDetails : "); + log.debug("getAllSchoolDetails V2: "); return schoolService.getSchoolDetailsFromRedisCache(); } @@ -96,7 +96,7 @@ public ResponseEntity> getSchoolsBySchoolCategory(@RequestPar @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "204", description = "NO CONTENT")}) public ResponseEntity getSchoolDetailsByMincode(@PathVariable String minCode) { - log.debug("getSchoolDetails : "); + log.debug("getSchoolDetails V2 : "); SchoolDetail schoolDetailResponse = schoolService.getSchoolDetailByMincodeFromRedisCache(minCode); if(schoolDetailResponse != null) { return response.GET(schoolDetailResponse); From fbb56fa26d88de2c26b39555171f67b3d4eaeeaa Mon Sep 17 00:00:00 2001 From: "chris.ditcher" Date: Thu, 12 Sep 2024 11:21:35 -0700 Subject: [PATCH 15/16] Increased header size --- api/src/main/resources/application.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml index 3424dc39..2cfe5e48 100644 --- a/api/src/main/resources/application.yaml +++ b/api/src/main/resources/application.yaml @@ -97,6 +97,7 @@ server: worker: 128 io: 16 #port: ${HTTP_PORT} + max-http-request-header-size: 20000 #API Documentation springdoc: From 01f8812f48bc12e7ee65ecbc67305157fb2cb8e3 Mon Sep 17 00:00:00 2001 From: githubmamatha <106563495+githubmamatha@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:41:54 -0700 Subject: [PATCH 16/16] Update pom.xml --- api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/pom.xml b/api/pom.xml index 5d1eb6e2..67960af1 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ ca.bc.gov.educ educ-grad-trax-api - 1.8.57 + 1.8.58 educ-grad-trax-api Ministry of Education GRAD TRAX API