From 6a3f795fbe91109798c8d8f9e8dbcd5a41d62ff0 Mon Sep 17 00:00:00 2001 From: Khaled Smaoui Date: Tue, 7 Jan 2025 14:56:19 -0800 Subject: [PATCH] Implemented fallback mechanism to TRAX v2 endpoints that obtain data from redis cache for school and district service, added unit tests, added new endpoints --- .../educ/api/trax/service/RESTService.java | 39 ++ .../service/institute/DistrictService.java | 82 +++- .../trax/service/institute/SchoolService.java | 107 ++++- .../trax/util/EducGradTraxApiConstants.java | 6 + .../bc/gov/educ/api/trax/util/SearchUtil.java | 52 +++ api/src/main/resources/application.yaml | 4 + .../InstituteDistrictServiceTest.java | 117 +++++ .../institute/InstituteSchoolServiceTest.java | 412 +++++++++++++++++- api/src/test/resources/application.yaml | 4 + 9 files changed, 808 insertions(+), 15 deletions(-) create mode 100644 api/src/main/java/ca/bc/gov/educ/api/trax/util/SearchUtil.java 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 b7d8c23f..e188daec 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 @@ -12,9 +12,12 @@ import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; import reactor.core.publisher.Mono; +import org.springframework.web.util.UriComponentsBuilder; import reactor.util.retry.Retry; import java.time.Duration; +import java.util.HashMap; +import java.util.List; @Service public class RESTService { @@ -102,6 +105,42 @@ public T get(String url, Class clazz, WebClient webClient) { return obj; } + public T get(String url, HashMapparams, Class clazz, WebClient webClient) { + T obj; + if (webClient == null) + webClient = this.webClient; + try { + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(url); + params.forEach((key, value) -> uriBuilder.queryParam(key, value)); + String uri = uriBuilder.build().toUriString(); + + obj = webClient + .get() + .uri(uri) + .headers(h -> { h.set(EducGradTraxApiConstants.CORRELATION_ID, ThreadLocalStateUtil.getCorrelationID()); }) + .retrieve() + // if 5xx errors, throw Service error + .onStatus(HttpStatusCode::is5xxServerError, + 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 + .retryWhen(Retry.backoff(3, Duration.ofSeconds(2)) + .filter(ServiceException.class::isInstance) + .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> { + throw new ServiceException(getErrorMessage(url, SERVICE_FAILED_ERROR), HttpStatus.SERVICE_UNAVAILABLE.value()); + })) + .block(); + } catch (Exception e) { + // catches IOExceptions and the like + throw new ServiceException( + getErrorMessage(url, e.getLocalizedMessage()), + (e instanceof WebClientResponseException) ? ((WebClientResponseException) e).getStatusCode().value() : HttpStatus.SERVICE_UNAVAILABLE.value(), + e); + } + return obj; + } + /** * NOTE: Soon to be deprecated in favour of calling get method without access token below. * @param url 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 dc4586ac..e9d644bf 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,12 +3,16 @@ 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; 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.SearchUtil; +import com.fasterxml.jackson.core.JsonProcessingException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -17,7 +21,9 @@ import org.springframework.web.reactive.function.client.WebClientResponseException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Optional; @Slf4j @Service("InstituteDistrictService") @@ -59,9 +65,43 @@ public void loadDistrictsIntoRedisCache(List districts) { log.info(String.format("%s Districts Loaded into cache.", districts.size())); } + public List getDistrictsBySearchCriteriaFromInstituteApi(String key, String value) { + try { + log.debug("****Before Calling Institute API"); + HashMap params; + HashMap searchInput = new HashMap<>(); + searchInput.put(key, value); + + try { + params = SearchUtil.searchStringsToHTTPParams(searchInput); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + + List districtEntities = this.restService.get(constants.getDistrictsPaginated(),params, + List.class, webClient); + return districtTransformer.transformToDTO(districtEntities); + } catch (WebClientResponseException e) { + log.warn("Error getting District By search Criteria from Institute API"); + } catch (Exception e) { + log.error(String.format("Error while calling Institute api: %s", e.getMessage())); + } + return null; + } + public List getDistrictsFromRedisCache() { log.debug("**** Getting districts from Redis Cache."); - return districtTransformer.transformToDTO(districtRedisRepository.findAll()); + Iterable districtEntities= districtRedisRepository.findAll(); + if ( (!districtEntities.iterator().hasNext())){ + log.debug("Get District from Redis Cache returned empty"); + List districts = this.getDistrictsFromInstituteApi(); + if ((districts != null) && (!districts .isEmpty())) { + this.loadDistrictsIntoRedisCache(districts); + return districts; + } + } + return districtTransformer.transformToDTO(districtEntities); } public void initializeDistrictCache(boolean force) { @@ -70,14 +110,36 @@ public void initializeDistrictCache(boolean force) { public District getDistrictByDistNoFromRedisCache(String districtNumber) { log.debug("**** Getting district by district no. from Redis Cache."); - return districtTransformer.transformToDTO(districtRedisRepository.findByDistrictNumber(districtNumber)); + DistrictEntity districtEntity = districtRedisRepository.findByDistrictNumber(districtNumber); + if ( districtEntity == null){ + log.debug("Getting district by district no from Redis Cache returned empty"); + List districts = this.getDistrictsBySearchCriteriaFromInstituteApi("districtNumber",districtNumber ); + if ((districts != null) &&(!districts.isEmpty())){ + this.loadDistrictsIntoRedisCache(districts); + return districts.get(0); + } + } + return districtTransformer.transformToDTO(districtEntity); } public District getDistrictByIdFromRedisCache(String districtId) { log.debug("**** Getting district by ID from Redis Cache."); - return districtTransformer.transformToDTO(districtRedisRepository.findById(districtId)); + Optional districtEntity = districtRedisRepository.findById(districtId); + if((districtEntity == null) || (districtEntity.isEmpty())) { + log.debug("Getting district by ID from Redis Cache returned empty"); + District district = this.getDistrictByIdFromInstituteApi(districtId); + if (district!=null) { + this.loadDistrictsIntoRedisCache(List.of(district)); + return district; + } + + } + return districtTransformer.transformToDTO(districtEntity); } + + + public List getDistrictsBySchoolCategoryCode(String schoolCategoryCode) { List schoolDetails; @@ -93,6 +155,20 @@ public List getDistrictsBySchoolCategoryCode(String schoolCategoryCode return districts; } + public District getDistrictByIdFromInstituteApi(String districtId) { + try { + log.debug("****Before Calling Institute API"); + Optional districtEntity = this.restService.get(String.format(constants.getGetDistrictFromInstituteApiUrl(), districtId), + Optional.class, webClient); + return districtTransformer.transformToDTO(districtEntity); + } catch (WebClientResponseException e) { + log.warn("Error getting District"); + } catch (Exception e) { + log.error(String.format("Error while calling Institute api: %s", e.getMessage())); + } + return null; + } + /** * 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 21cdad6f..0ac5e96c 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 @@ -12,6 +12,9 @@ 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.SearchUtil; +import com.fasterxml.jackson.core.JsonProcessingException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -19,8 +22,8 @@ import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; -import java.util.ArrayList; -import java.util.List; + +import java.util.*; @Slf4j @Service("InstituteSchoolService") @@ -66,12 +69,33 @@ public void loadSchoolsIntoRedisCache(List getSchoolsFromRedisCache() { log.debug("**** Getting schools from Redis Cache."); - return schoolTransformer.transformToDTO(schoolRedisRepository.findAll()); + Iterable schoolEntities = schoolRedisRepository.findAll(); + if ( (!schoolEntities.iterator().hasNext())){ + log.debug("Get Schools from Redis Cache returned empty"); + List schools = getSchoolsFromInstituteApi(); + if ((schools != null) && (!schools.isEmpty())) { + loadSchoolsIntoRedisCache(schools); + return schools; + } + } + return schoolTransformer.transformToDTO(schoolEntities); } public School getSchoolByMincodeFromRedisCache(String mincode) { log.debug("Get School by Mincode from Redis Cache"); - return schoolTransformer.transformToDTO(schoolRedisRepository.findByMincode(mincode)); + SchoolEntity schoolEntity = schoolRedisRepository.findByMincode(mincode); + if (schoolEntity == null) { + log.debug("Get School by Mincode from Redis Cache returned null"); + List schools = getSchoolsBySearchCriteriaFromInstituteApi("mincode", mincode); + if ((schools != null) && (!schools.isEmpty())) { + School school = schools.get(0); + if (school != null) { + loadSchoolsIntoRedisCache(List.of(school)); + return school; + } + } + } + return schoolTransformer.transformToDTO(schoolEntity); } public boolean checkIfSchoolExists(String minCode) { @@ -97,6 +121,33 @@ public SchoolDetail getSchoolDetailByIdFromInstituteApi(String schoolId) { return null; } + + + public List getSchoolsBySearchCriteriaFromInstituteApi(String key, String value) { + try { + log.debug("****Before Calling Institute API"); + HashMap params; + HashMap searchInput = new HashMap<>(); + searchInput.put(key, value); + + try { + params = SearchUtil.searchStringsToHTTPParams(searchInput); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + + List schoolEntities = this.restService.get(constants.getSchoolsPaginated(),params, + List.class, webClient); + return schoolTransformer.transformToDTO(schoolEntities); + } catch (WebClientResponseException e) { + log.warn("Error getting School By search Criteria from Institute API"); + } catch (Exception e) { + log.error(String.format("Error while calling Institute api: %s", e.getMessage())); + } + return null; + } + public List getSchoolDetailsFromInstituteApi() { List schools = getSchoolsFromRedisCache(); @@ -110,17 +161,40 @@ public List getSchoolDetailsFromInstituteApi() { public void loadSchoolDetailsIntoRedisCache(List schoolDetails) { schoolDetailRedisRepository .saveAll(schoolDetailTransformer.transformToEntity(schoolDetails)); - log.info(String.format("%s School Details Loaded into cache.", schoolDetails.size())); + log.debug(String.format("%s School Details Loaded into cache.", schoolDetails.size())); } public List getSchoolDetailsFromRedisCache() { log.debug("**** Getting school Details from Redis Cache."); - return schoolDetailTransformer.transformToDTO(schoolDetailRedisRepository.findAll()); + + Iterable schoolDetailEntities = schoolDetailRedisRepository.findAll(); + if ( (!schoolDetailEntities.iterator().hasNext())){ + log.debug("Get Schools details from Redis Cache returned empty"); + List schoolDetails = this.getSchoolDetailsFromInstituteApi(); + if ((schoolDetails != null) && (!schoolDetails .isEmpty())) { + loadSchoolDetailsIntoRedisCache(schoolDetails); + return schoolDetails; + } + } + return schoolDetailTransformer.transformToDTO(schoolDetailEntities); } public SchoolDetail getSchoolDetailByMincodeFromRedisCache(String mincode) { log.debug("**** Getting school Details By Mincode from Redis Cache."); - return schoolDetailTransformer.transformToDTO(schoolDetailRedisRepository.findByMincode(mincode)); + SchoolDetailEntity schoolDetailEntity = schoolDetailRedisRepository.findByMincode(mincode); + if ( schoolDetailEntity == null){ + log.debug("Get Schools details by mincode from Redis Cache returned empty"); + List schools = this.getSchoolsBySearchCriteriaFromInstituteApi("mincode", mincode); + if ((schools!= null) &&(!schools.isEmpty())){ + School school = schools.get(0); + SchoolDetail schoolDetail = this.getSchoolDetailByIdFromInstituteApi(school.getSchoolId()); + if ((schoolDetail != null) ) { + loadSchoolDetailsIntoRedisCache(List.of(schoolDetail)); + return schoolDetail ; + } + } + } + return schoolDetailTransformer.transformToDTO(schoolDetailEntity ); } public void initializeSchoolDetailCache(boolean force) { @@ -129,8 +203,23 @@ public void initializeSchoolDetailCache(boolean force) { public List getSchoolDetailsBySchoolCategoryCode(String schoolCategoryCode) { - return schoolDetailTransformer.transformToDTO( - schoolDetailRedisRepository.findBySchoolCategoryCode(schoolCategoryCode)); + log.debug("**** Getting school Details By school category from Redis Cache."); + List schoolDetailEntities = schoolDetailRedisRepository.findBySchoolCategoryCode(schoolCategoryCode); + + if ( (schoolDetailEntities == null) ||(schoolDetailEntities.isEmpty())){ + log.debug("Get Schools details by category code from Redis Cache returned empty"); + + List schools = this.getSchoolsBySearchCriteriaFromInstituteApi("schoolCategoryCode",schoolCategoryCode); + ArrayList schoolDetails = new ArrayList<>(); + if ((schools != null) && (!schools.isEmpty())) { + schools.forEach(school -> { + schoolDetails.add(this.getSchoolDetailByIdFromInstituteApi(school.getSchoolId())); + }); + loadSchoolDetailsIntoRedisCache(schoolDetails); + return schoolDetails; + } + } + return schoolDetailTransformer.transformToDTO(schoolDetailEntities); } /** 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..585b8f82 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 @@ -187,6 +187,12 @@ public class EducGradTraxApiConstants { @Value("${endpoint.student-admin.authority-details.url}") private String studentAdminAuthorityDetailsUrl; + @Value("${endpoint.institute-api.get-schools-paginated.url}") + private String schoolsPaginated; + + @Value("${endpoint.institute-api.get-districts-paginated.url}") + private String districtsPaginated; + // 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/SearchUtil.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/SearchUtil.java new file mode 100644 index 00000000..bf51a637 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/util/SearchUtil.java @@ -0,0 +1,52 @@ +package ca.bc.gov.educ.api.trax.util; + +import ca.bc.gov.educ.api.trax.filter.FilterOperation; +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 com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * Util class for searches + */ +public class SearchUtil { + + public static final ObjectMapper mapper = new ObjectMapper(); + + private SearchUtil() { + } + + /** + * convert list of search strings to HTTP params + * + * @param searchStringMap the HashMap of search strings + * @return list of http params + * @throws JsonProcessingException the json process exception + * + */ + public static HashMap searchStringsToHTTPParams(HashMap searchStringMap) throws JsonProcessingException { + + List criteriaList = new ArrayList<>(); + searchStringMap.forEach((key, value) ->{ + SearchCriteria criteria = SearchCriteria.builder().key(key).operation(FilterOperation.EQUAL).value(value).valueType(ValueType.STRING).build(); + criteriaList.add(criteria); + }); + final List searches = new LinkedList<>(); + searches.add(Search.builder().searchCriteriaList(criteriaList).build()); + final String criteriaJSON; + + criteriaJSON = mapper.writeValueAsString(searches); + + HashMap params = new HashMap<>(); + params.put("searchCriteriaList", criteriaJSON); + return params; + + } + +} diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml index ce93672e..9a9c2a65 100644 --- a/api/src/main/resources/application.yaml +++ b/api/src/main/resources/application.yaml @@ -195,6 +195,10 @@ 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 + get-schools-paginated: + url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/school/paginated + get-districts-paginated: + url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/district/paginated student-admin: school-details: url: ${STUDENT_ADMIN_URL_ROOT}institute/school/%s/details 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 72f5b766..d3d93044 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 @@ -20,6 +20,8 @@ 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 ca.bc.gov.educ.api.trax.util.SearchUtil; +import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.runner.RunWith; @@ -218,6 +220,51 @@ public void whenGetDistrictsFromRedisCache_ReturnDistricts() { assertEquals(districts, districtService.getDistrictsFromRedisCache()); } + @Test + public void whenGetDistrictsFromRedisCache_ReturnNoDistricts() { + 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(Collections.emptyList()); + when (this.restServiceMock.get(constants.getAllDistrictsFromInstituteApiUrl(), + List.class, instWebClient)).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())) @@ -313,6 +360,49 @@ public void whenGetDistrictByDistNoFromRedisCache_ReturnDistrict() { assertEquals(district, districtService.getDistrictByDistNoFromRedisCache(distNo)); } + @Test + public void whenGetDistrictByDistNoFromRedisCache_ReturnNoDistrict() { + String distNo = "123"; + List districts = new ArrayList<>(); + District district = new District(); + district.setDistrictId("ID"); + district.setDistrictNumber("123"); + 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("456"); + districtEntity.setDistrictStatusCode("SC"); + districtEntity.setDistrictRegionCode("RC"); + districtEntity.setContacts(Arrays.asList(new DistrictContactEntity(), new DistrictContactEntity())); + districtEntities.add(districtEntity); + + HashMap params; + HashMap searchInput = new HashMap<>(); + searchInput.put("districtNumber", distNo ); + + try { + params = SearchUtil.searchStringsToHTTPParams(searchInput); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + when(this.districtRedisRepository.findByDistrictNumber(distNo)) + .thenReturn(null); + when(this.restServiceMock.get(constants.getDistrictsPaginated(), params, + List.class, instWebClient)).thenReturn(districtEntities); + when(this.districtTransformerMock.transformToDTO(districtEntities)) + .thenReturn(districts); + + when(this.districtTransformerMock.transformToDTO(districtEntity)) + .thenReturn(district); + assertEquals(district, districtService.getDistrictByDistNoFromRedisCache(distNo)); + } + @Test public void whenGetDistrictByIdFromRedisCache_ReturnDistrict() { District district = new District(); @@ -336,6 +426,33 @@ public void whenGetDistrictByIdFromRedisCache_ReturnDistrict() { assertEquals(district, districtService.getDistrictByIdFromRedisCache("ID")); } + @Test + public void whenGetDistrictByIdFromRedisCache_ReturnNoDistrict() { + 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(null); + when(this.districtTransformerMock.transformToDTO(Optional.of(districtEntity))) + .thenReturn(district); + when(this.restServiceMock.get(String.format(constants.getGetDistrictFromInstituteApiUrl(), district.getDistrictId()), + Optional.class, instWebClient)).thenReturn(Optional.of(districtEntity)); + + assertEquals(district, districtService.getDistrictByIdFromRedisCache("ID")); + } + + @Test public void whenInitializeDistrictCache_WithReadyAndTrue_ThenForceLoad() { 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 e1720176..8f5713d8 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 @@ -15,6 +15,8 @@ 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 ca.bc.gov.educ.api.trax.util.SearchUtil; +import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Test; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.extension.ExtendWith; @@ -38,9 +40,7 @@ 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.*; import java.util.function.Consumer; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -58,6 +58,8 @@ @SuppressWarnings({"unchecked","rawtypes"}) public class InstituteSchoolServiceTest { + + @Autowired private EducGradTraxApiConstants constants; @Autowired @@ -167,6 +169,57 @@ public void whenGetSchoolsFromInstituteApi_returnsListOfSchools() { List result = schoolService.getSchoolsFromInstituteApi(); } + @Test + public void whenGetSchoolsFromRedisCache_returnsNoSchools() { + List schools = new ArrayList<>(); + String mincode = "12345678"; + School school = new School(); + school.setSchoolId("ID1"); + 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("ID2"); + 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("ID1"); + 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("ID2"); + schoolEntity.setDistrictId("DistID"); + schoolEntity.setSchoolNumber("12345"); + schoolEntity.setMincode(mincode); + schoolEntity.setSchoolCategoryCode("SCC"); + schoolEntity.setEmail("abc@xyz.ca"); + schoolEntities.add(schoolEntity); + + when(this.schoolTransformerMock.transformToDTO(schoolEntities)) + .thenReturn(schools); + when (this.schoolRedisRepository.findAll()).thenReturn(Collections.emptyList()); + when (this.restServiceMock.get(constants.getAllSchoolsFromInstituteApiUrl(), + List.class, instWebClient)).thenReturn(schoolEntities); + List result = schoolService.getSchoolsFromRedisCache(); + assertEquals(schools, result); + + } + @Test public void whenLoadSchoolsIntoRedisCache_DoesNotThrow() { List schoolEntities = Arrays.asList(new SchoolEntity()); @@ -357,6 +410,88 @@ public void whenGetSchoolDetailByIdFromInstituteApi_ReturnSchoolDetail() { assertEquals(schoolDetail, result); } + + @Test + public void whenGetSchoolByMinCodeFromInstituteApi_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"); + + 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); + HashMap params; + HashMap searchInput = new HashMap<>(); + searchInput.put("mincode", mincode); + + try { + params = SearchUtil.searchStringsToHTTPParams(searchInput); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + when(this.restServiceMock.get(constants.getSchoolsPaginated(), params, + List.class, instWebClient)).thenReturn(schoolEntities); + when(this.schoolTransformer.transformToDTO(schoolEntities)) + .thenReturn(List.of(school)); + School result = schoolService.getSchoolsBySearchCriteriaFromInstituteApi("mincode",mincode).get(0); + + + assertEquals(school, result); + } + + @Test + public void whenGetSchoolByMincodeFromRedisCache_ReturnNoSchool() { + 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"); + + 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); + HashMap params; + HashMap searchInput = new HashMap<>(); + searchInput.put("mincode", mincode); + + try { + params =SearchUtil.searchStringsToHTTPParams(searchInput); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + when(this.restServiceMock.get(constants.getSchoolsPaginated(), params, + List.class, instWebClient)).thenReturn(schoolEntities); + when(this.schoolTransformer.transformToDTO(schoolEntities)) + .thenReturn(List.of(school)); + + when(this.schoolRedisRepository.findByMincode(mincode)) + .thenReturn(null); + assertEquals(school, schoolService.getSchoolByMincodeFromRedisCache(mincode)); + + } + @Test public void whenLoadSchoolDetailsIntoRedisCache_DoesNotThrow() { List schoolDetailEntities = Arrays.asList(new SchoolDetailEntity()); @@ -420,6 +555,104 @@ public void whenGetSchoolDetailsFromRedisCache_ReturnSchoolDetails() { assertEquals(schoolDetails, schoolService.getSchoolDetailsFromRedisCache()); } + @Test + public void whenGetSchoolDetailsFromRedisCache_ReturnNoSchoolDetails() { + String mincode = "12345678"; + List schools = new ArrayList<>(); + + School school = new School(); + school.setSchoolId("ID1"); + 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("ID2"); + 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("ID1"); + 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("ID2"); + schoolEntity.setDistrictId("DistID"); + schoolEntity.setSchoolNumber("12345"); + schoolEntity.setMincode(mincode); + schoolEntity.setSchoolCategoryCode("SCC"); + schoolEntity.setEmail("abc@xyz.ca"); + schoolEntities.add(schoolEntity); + + + + List schoolDetails = new ArrayList<>(); + SchoolDetail schoolDetail1 = new SchoolDetail(); + schoolDetail1.setSchoolId("ID1"); + schoolDetail1.setDistrictId("DistID"); + schoolDetail1.setSchoolNumber("12345"); + schoolDetail1.setMincode(mincode); + schoolDetail1.setSchoolCategoryCode("SCC"); + schoolDetail1.setEmail("abc@xyz.ca"); + schoolDetails.add(schoolDetail1); + + SchoolDetail schoolDetail2 = new SchoolDetail(); + schoolDetail2.setSchoolId("ID2"); + schoolDetail2.setDistrictId("DistID"); + schoolDetail2.setSchoolNumber("12345"); + schoolDetail2.setMincode(mincode); + schoolDetail2.setSchoolCategoryCode("SCC"); + schoolDetail2.setEmail("abc@xyz.ca"); + schoolDetails.add(schoolDetail2); + + SchoolDetailEntity schoolDetailEntity1 = new SchoolDetailEntity(); + schoolDetailEntity1.setSchoolId("ID1"); + schoolDetailEntity1.setDistrictId("DistID"); + schoolDetailEntity1.setSchoolNumber("12345"); + schoolDetailEntity1.setMincode(mincode); + schoolDetailEntity1.setSchoolCategoryCode("SCC"); + schoolDetailEntity1.setEmail("abc@xyz.ca"); + + SchoolDetailEntity schoolDetailEntity2 = new SchoolDetailEntity(); + schoolDetailEntity2.setSchoolId("ID2"); + schoolDetailEntity2.setDistrictId("DistID"); + schoolDetailEntity2.setSchoolNumber("12345"); + schoolDetailEntity2.setMincode(mincode); + schoolDetailEntity2.setSchoolCategoryCode("SCC"); + schoolDetailEntity2.setEmail("abc@xyz.ca"); + + when(this.schoolTransformerMock.transformToDTO(schoolEntities)) + .thenReturn(schools); + when(this.schoolDetailTransformerMock.transformToDTO(schoolDetailEntity1)).thenReturn(schoolDetail1); + when(this.schoolDetailTransformerMock.transformToDTO(schoolDetailEntity2)).thenReturn(schoolDetail2); + when (this.schoolRedisRepository.findAll()).thenReturn(Collections.emptyList()); + when (this.restServiceMock.get(constants.getAllSchoolsFromInstituteApiUrl(), + List.class, instWebClient)).thenReturn(schoolEntities); + + when(this.schoolDetailRedisRepository.findAll()) + .thenReturn(Collections.emptyList()); + + when (this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolDetailEntity1.getSchoolId()), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity1); + when (this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolDetailEntity2.getSchoolId()), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity2); + + assertEquals(schoolDetails, schoolService.getSchoolDetailsFromRedisCache()); + } + @Test public void whenGetSchoolDetailByMincodeFromRedisCache_ReturnSchoolDetail() { String mincode = "12345678"; @@ -446,6 +679,70 @@ public void whenGetSchoolDetailByMincodeFromRedisCache_ReturnSchoolDetail() { assertEquals(schoolDetail, schoolService.getSchoolDetailByMincodeFromRedisCache(mincode)); } + @Test + public void whenGetSchoolDetailByMincodeFromRedisCache_ReturnNoSchool() { + 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"); + + School school = new School(); + school.setSchoolId("ID"); + school.setDistrictId("DistID"); + school.setSchoolNumber("12345"); + school.setMincode(mincode); + school.setSchoolCategoryCode("SCC"); + school.setEmail("abc@xyz.ca"); + + List schoolEntities = new ArrayList<>(); + SchoolEntity schoolEntity = new SchoolEntity(); + schoolEntity.setSchoolId("ID"); + schoolEntity.setDistrictId("DistID"); + schoolEntity.setSchoolNumber("12345"); + schoolEntity.setMincode(mincode); + schoolEntity.setSchoolCategoryCode("SCC"); + schoolEntities.add(schoolEntity); + + when(this.schoolDetailRedisRepository.findByMincode(mincode)) + .thenReturn(null); + when(this.schoolDetailRedisRepository.findByMincode(mincode)) + .thenReturn(null); + HashMap searchInput = new HashMap<>(); + searchInput.put("mincode", mincode); + + HashMap params; + try { + params =SearchUtil.searchStringsToHTTPParams(searchInput); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + when(this.restServiceMock.get(constants.getSchoolsPaginated(), params, + List.class, instWebClient)).thenReturn(schoolEntities); + when(this.schoolTransformer.transformToDTO(schoolEntities)) + .thenReturn(List.of(school)); + + when (this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolEntity.getSchoolId()), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity); + + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntity)) + .thenReturn(schoolDetail); + assertEquals(schoolDetail, schoolService.getSchoolDetailByMincodeFromRedisCache(mincode)); + } + + @Test public void whenGetSchoolDetailBySchoolCategoryCode_ReturnSchoolDetail() { String schoolCategoryCode = "ABC"; @@ -489,4 +786,113 @@ public void whenGetSchoolDetailBySchoolCategoryCode_ReturnSchoolDetail() { .thenReturn(schoolDetails); assertEquals(schoolDetails, schoolService.getSchoolDetailsBySchoolCategoryCode(schoolCategoryCode)); } + + @Test + public void whenGetSchoolDetailBySchoolCategoryCode_ReturnNoSchoolDetail() { + String schoolCategoryCode = "ABC"; + List schoolDetails = new ArrayList<>(); + SchoolDetail schoolDetail1 = new SchoolDetail(); + schoolDetail1.setSchoolId("ID1"); + schoolDetail1.setDistrictId("DistID"); + schoolDetail1.setSchoolNumber("12345"); + schoolDetail1.setSchoolCategoryCode(schoolCategoryCode); + schoolDetail1.setEmail("abc@xyz.ca"); + schoolDetails.add(schoolDetail1); + + SchoolDetail schoolDetail2 = new SchoolDetail(); + schoolDetail2.setSchoolId("ID2"); + schoolDetail2.setDistrictId("DistID"); + schoolDetail2.setSchoolNumber("12345"); + schoolDetail2.setSchoolCategoryCode(schoolCategoryCode); + schoolDetail2.setEmail("abc@xyz.ca"); + schoolDetails.add(schoolDetail2); + + List schoolDetailEntities = new ArrayList<>(); + SchoolDetailEntity schoolDetailEntity1 = new SchoolDetailEntity(); + schoolDetailEntity1.setSchoolId("ID1"); + schoolDetailEntity1.setDistrictId("DistID"); + schoolDetailEntity1.setSchoolNumber("12345"); + schoolDetailEntity1.setSchoolCategoryCode(schoolCategoryCode); + schoolDetailEntity1.setEmail("abc@xyz.ca"); + schoolDetailEntities.add(schoolDetailEntity1); + + SchoolDetailEntity schoolDetailEntity2 = new SchoolDetailEntity(); + schoolDetailEntity2.setSchoolId("ID2"); + schoolDetailEntity2.setDistrictId("DistID"); + schoolDetailEntity2.setSchoolNumber("12345"); + schoolDetailEntity2.setSchoolCategoryCode(schoolCategoryCode); + schoolDetailEntity2.setEmail("abc@xyz.ca"); + schoolDetailEntities.add(schoolDetailEntity2); + + List schoolEntities = new ArrayList<>(); + SchoolEntity schoolEntity1 = new SchoolEntity(); + schoolEntity1.setSchoolId("ID1"); + schoolEntity1.setDistrictId("DistID"); + schoolEntity1.setSchoolNumber("12345"); + schoolEntity1.setMincode("2569"); + schoolEntity1.setSchoolCategoryCode(schoolCategoryCode); + schoolEntity1.setEmail("abc@xyz.ca"); + schoolEntities.add(schoolEntity1); + + SchoolEntity schoolEntity2 = new SchoolEntity(); + schoolEntity2.setSchoolId("ID2"); + schoolEntity2.setDistrictId("DistID"); + schoolEntity2.setSchoolNumber("12345"); + schoolEntity2.setMincode("2569"); + schoolEntity2.setSchoolCategoryCode(schoolCategoryCode); + schoolEntity2.setEmail("abc@xyz.ca"); + schoolEntities.add(schoolEntity2); + List schools = new ArrayList<>(); + + School school1 = new School(); + school1.setSchoolId("ID1"); + school1.setDistrictId("DistID"); + school1.setSchoolNumber("12345"); + school1.setMincode("589"); + school1.setSchoolCategoryCode(schoolCategoryCode); + school1.setEmail("abc@xyz.ca"); + schools.add(school1); + + School school2 = new School(); + school2.setSchoolId("ID2"); + school2.setDistrictId("DistID"); + school2.setSchoolNumber("12345"); + school2.setMincode("589"); + school2.setSchoolCategoryCode(schoolCategoryCode); + school2.setEmail("abc@xyz.ca"); + schools.add(school2); + + HashMap params; + HashMap searchInput = new HashMap<>(); + searchInput.put("schoolCategoryCode", schoolCategoryCode); + + try { + params =SearchUtil.searchStringsToHTTPParams(searchInput); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + when(this.restServiceMock.get(constants.getSchoolsPaginated(), params, + List.class, instWebClient)).thenReturn(schoolEntities); + when(this.schoolTransformer.transformToDTO(schoolEntities)) + .thenReturn(schools); + + when(this.schoolDetailRedisRepository.findBySchoolCategoryCode(schoolCategoryCode)) + .thenReturn(null); + + when (this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), school1.getSchoolId()), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity1); + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntity1)) + .thenReturn(schoolDetail1); + + when (this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), school2.getSchoolId()), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity2); + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntity2)) + .thenReturn(schoolDetail2); + + 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 4672df06..6ab782e8 100644 --- a/api/src/test/resources/application.yaml +++ b/api/src/test/resources/application.yaml @@ -152,6 +152,10 @@ 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 + get-schools-paginated: + url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/school/paginated + get-districts-paginated: + url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/district/paginated student-admin: school-details: url: https://student-admin-8878b4-dev.apps.silver.devops.gov.bc.ca/institute/school/%s/details