diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/config/GradTraxConfig.java b/api/src/main/java/ca/bc/gov/educ/api/trax/config/GradTraxConfig.java index b83b9967..d91e39c9 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/config/GradTraxConfig.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/config/GradTraxConfig.java @@ -17,6 +17,8 @@ import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.security.oauth2.client.*; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction; @@ -26,6 +28,7 @@ import org.springframework.web.reactive.function.client.WebClient; import reactor.netty.http.client.HttpClient; +@EnableAsync @Configuration @Profile("!test") public class GradTraxConfig { @@ -125,5 +128,16 @@ public LockProvider lockProvider(@Autowired JdbcTemplate jdbcTemplate, @Autowire return new JdbcTemplateLockProvider(jdbcTemplate, transactionManager, "REPLICATION_SHEDLOCK"); } + /** + * Thread pool task scheduler thread pool task scheduler. + * + * @return the thread pool task scheduler + */ + @Bean(name = "taskExecutor") + public ThreadPoolTaskScheduler threadPoolTaskScheduler() { + final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); + threadPoolTaskScheduler.setPoolSize(5); + return threadPoolTaskScheduler; + } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/constant/CacheKey.java b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/CacheKey.java index b371ecb2..fc1dd522 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/constant/CacheKey.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/CacheKey.java @@ -1,5 +1,5 @@ package ca.bc.gov.educ.api.trax.constant; public enum CacheKey { - SCHOOL_CACHE, DISTRICT_CACHE, SCHOOL_CATEGORY_CODE_CACHE, SCHOOL_FUNDING_GROUP_CODE_CACHE + SCHOOL_CACHE, SCHOOL_DETAIL_CACHE, DISTRICT_CACHE, SCHOOL_CATEGORY_CODE_CACHE, SCHOOL_FUNDING_GROUP_CODE_CACHE } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/PaginatedResponse.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/PaginatedResponse.java new file mode 100644 index 00000000..fe9d83a8 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/PaginatedResponse.java @@ -0,0 +1,41 @@ +package ca.bc.gov.educ.api.trax.model.dto.institute; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.EqualsAndHashCode; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.ArrayList; +import java.util.List; + +public class PaginatedResponse extends PageImpl { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public PaginatedResponse(@JsonProperty("content") List content, + @JsonProperty("number") int number, + @JsonProperty("size") int size, + @JsonProperty("totalElements") Long totalElements, + @JsonProperty("pageable") JsonNode pageable, + @JsonProperty("last") boolean last, + @JsonProperty("totalPages") int totalPages, + @JsonProperty("sort") JsonNode sort, + @JsonProperty("first") boolean first, + @JsonProperty("empty") boolean empty) { + + super(content, PageRequest.of(number, size), totalElements); + } + + public PaginatedResponse(List content, Pageable pageable, long total) { + super(content, pageable, total); + } + + public PaginatedResponse(List content) { + super(content); + } + + public PaginatedResponse() { + super(new ArrayList<>()); + } +} \ No newline at end of file diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/CacheService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/CacheService.java new file mode 100644 index 00000000..e9ef1dfb --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/CacheService.java @@ -0,0 +1,142 @@ +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.constant.CacheStatus; +import ca.bc.gov.educ.api.trax.model.entity.institute.*; +import ca.bc.gov.educ.api.trax.repository.redis.*; +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import redis.clients.jedis.JedisCluster; + +import java.util.List; + +@Slf4j +@AllArgsConstructor +@Service("cacheService") +public class CacheService { + + SchoolRedisRepository schoolRedisRepository; + + SchoolDetailRedisRepository schoolDetailRedisRepository; + + DistrictRedisRepository districtRedisRepository; + + SchoolCategoryCodeRedisRepository schoolCategoryCodeRedisRepository; + + SchoolFundingGroupCodeRedisRepository schoolFundingGroupCodeRedisRepository; + + JedisCluster jedisCluster; + + @Async("taskExecutor") + public void loadSchoolsIntoRedisCacheAsync(List schools) { + if(!CollectionUtils.isEmpty(schools)) { + loadSchoolsIntoRedisCache(schools); + } + } + + public void loadSchoolsIntoRedisCache(List schools) { + if(!isCacheLoading(CacheKey.SCHOOL_CACHE) && !CollectionUtils.isEmpty(schools)) { + setCacheStateLoading(CacheKey.SCHOOL_CACHE); + long start = System.currentTimeMillis(); + log.debug("****Before loading schools into cache"); + for (List partition : Lists.partition(schools, 1000)) { + schoolRedisRepository.saveAll(partition); + } + log.info("{} Schools Loaded into cache in {} ms.", schools.size(), (System.currentTimeMillis() - start)); + setCacheReadiness(CacheKey.SCHOOL_CACHE); + } + } + + @Async("taskExecutor") + public void loadSchoolDetailsIntoRedisCacheAsync(List schoolDetails) { + if(!CollectionUtils.isEmpty(schoolDetails)) { + loadSchoolDetailsIntoRedisCache(schoolDetails); + } + } + + public void loadSchoolDetailsIntoRedisCache(List schoolDetails) { + if(!isCacheLoading(CacheKey.SCHOOL_DETAIL_CACHE) && !CollectionUtils.isEmpty(schoolDetails)) { + setCacheStateLoading(CacheKey.SCHOOL_DETAIL_CACHE); + long start = System.currentTimeMillis(); + log.debug("****Before loading school details into cache"); + for (List partition : Lists.partition(schoolDetails, 1000)) { + schoolDetailRedisRepository.saveAll(partition); + } + log.info("{} School Details Loaded into cache in {} ms.", schoolDetails.size(), (System.currentTimeMillis() - start)); + setCacheReadiness(CacheKey.SCHOOL_DETAIL_CACHE); + } + } + + @Async("taskExecutor") + public void loadDistrictsIntoRedisCacheAsync(List districts) { + if(!CollectionUtils.isEmpty(districts)) { + loadDistrictsIntoRedisCache(districts); + } + } + + public void loadDistrictsIntoRedisCache(List districts) { + if(!isCacheLoading(CacheKey.DISTRICT_CACHE) && !CollectionUtils.isEmpty(districts)) { + setCacheStateLoading(CacheKey.DISTRICT_CACHE); + long start = System.currentTimeMillis(); + log.debug("****Before loading districts into cache"); + districtRedisRepository.saveAll(districts); + log.info("{} Districts Loaded into cache in {} ms.", districts.size(), (System.currentTimeMillis() - start)); + setCacheReadiness(CacheKey.DISTRICT_CACHE); + } + } + + @Async("taskExecutor") + public void loadSchoolCategoryCodesIntoRedisCacheAsync(List schoolCategoryCodes) { + if(!CollectionUtils.isEmpty(schoolCategoryCodes)) { + loadSchoolCategoryCodesIntoRedisCache(schoolCategoryCodes); + } + } + + public void loadSchoolCategoryCodesIntoRedisCache(List schoolCategoryCodes) { + if(!isCacheLoading(CacheKey.SCHOOL_CATEGORY_CODE_CACHE) && !CollectionUtils.isEmpty(schoolCategoryCodes)) { + long start = System.currentTimeMillis(); + log.debug("****Before loading School Category Codes into cache"); + setCacheStateLoading(CacheKey.SCHOOL_CATEGORY_CODE_CACHE); + schoolCategoryCodeRedisRepository.saveAll(schoolCategoryCodes); + log.info("{} School Category Codes Loaded into cache in {} ms.", schoolCategoryCodes.size(), (System.currentTimeMillis() - start)); + setCacheReadiness(CacheKey.SCHOOL_CATEGORY_CODE_CACHE); + } + } + + @Async("taskExecutor") + public void loadSchoolFundingGroupCodesIntoRedisCacheAsync(List schoolFundingGroupCodes) { + if(!CollectionUtils.isEmpty(schoolFundingGroupCodes)) { + loadSchoolFundingGroupCodesIntoRedisCache(schoolFundingGroupCodes); + } + } + + public void loadSchoolFundingGroupCodesIntoRedisCache(List schoolFundingGroupCodes) { + if(!isCacheLoading(CacheKey.SCHOOL_FUNDING_GROUP_CODE_CACHE) && !CollectionUtils.isEmpty(schoolFundingGroupCodes)) { + long start = System.currentTimeMillis(); + log.debug("****Before loading School Funding Group Codes into cache"); + setCacheStateLoading(CacheKey.SCHOOL_FUNDING_GROUP_CODE_CACHE); + schoolFundingGroupCodeRedisRepository.saveAll(schoolFundingGroupCodes); + log.info("{} School Funding Group Codes Loaded into cache in {} ms.", schoolFundingGroupCodes.size(), (System.currentTimeMillis() - start)); + setCacheReadiness(CacheKey.SCHOOL_FUNDING_GROUP_CODE_CACHE); + } + } + + private boolean isCacheLoading(CacheKey cacheKey) { + String cacheStatus = jedisCluster.get(cacheKey.name()); + return CacheStatus.LOADING.name().equals(cacheStatus); + } + + private void setCacheStateLoading(CacheKey cacheKey) { + jedisCluster.set(cacheKey.name(), CacheStatus.LOADING.name()); + } + + private void setCacheReadiness(CacheKey cacheKey) { + jedisCluster.set(cacheKey.name(), CacheStatus.READY.name()); + log.info(String.format("Success! - %s is now READY", cacheKey)); + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/CodeService.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/CodeService.java index 7eada39c..574880e0 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/CodeService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/CodeService.java @@ -1,7 +1,9 @@ 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.SchoolCategoryCode; +import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolFundingGroupCode; import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolCategoryCodeEntity; import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolFundingGroupCodeEntity; @@ -15,9 +17,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; +import java.util.Collections; import java.util.List; @Slf4j @@ -41,10 +45,12 @@ public class CodeService { ServiceHelper serviceHelper; @Autowired RESTService restService; + @Autowired + CacheService cacheService; public List getSchoolCategoryCodesFromInstituteApi() { try { - log.debug("****Before Calling Institute API"); + log.debug("****Before Calling Institute API for SchoolCategoryCode"); List response = this.restService.get(constants.getAllSchoolCategoryCodesFromInstituteApiUrl(), List.class, webClient); return schoolCategoryCodeTransformer.transformToDTO(response); @@ -53,23 +59,44 @@ public List getSchoolCategoryCodesFromInstituteApi() { } catch (Exception e) { log.error(String.format("Error while calling school-api: %s", e.getMessage())); } - return null; + return Collections.emptyList(); + } + + public List loadSchoolCategoryCodesFromInstituteApiIntoRedisCacheAsync() { + List schoolCategoryCodes = getSchoolCategoryCodesFromInstituteApi(); + if(!CollectionUtils.isEmpty(schoolCategoryCodes)) { + cacheService.loadSchoolCategoryCodesIntoRedisCacheAsync(schoolCategoryCodeTransformer.transformToEntity(schoolCategoryCodes)); + } + return schoolCategoryCodes; } public void loadSchoolCategoryCodesIntoRedisCache(List schoolCategoryCodes) { - schoolCategoryCodeRedisRepository - .saveAll(schoolCategoryCodeTransformer.transformToEntity(schoolCategoryCodes)); - log.info(String.format("%s School Category Codes Loaded into cache.", schoolCategoryCodes.size())); + if(!CollectionUtils.isEmpty(schoolCategoryCodes)) { + cacheService.loadSchoolCategoryCodesIntoRedisCache(schoolCategoryCodeTransformer.transformToEntity(schoolCategoryCodes)); + } } public SchoolCategoryCode getSchoolCategoryCodeFromRedisCache(String schoolCategoryCode) { - log.debug("**** Getting school category codes from Redis Cache."); - return schoolCategoryCodeTransformer.transformToDTO(schoolCategoryCodeRedisRepository.findById(schoolCategoryCode)); + log.debug("**** Getting school category codes from Redis Cache for : {}.", schoolCategoryCode); + return schoolCategoryCodeRedisRepository.findById(schoolCategoryCode) + .map(schoolCategoryCodeTransformer::transformToDTO) + .orElseGet(() -> { + SchoolCategoryCode schoolCategory = getSchoolCategoryCodesFromInstituteApi().stream() + .filter(schoolCategoryCode1 -> schoolCategoryCode1.getSchoolCategoryCode().equals(schoolCategoryCode)) + .findFirst() + .orElse(null); + if(schoolCategory != null) { + updateSchoolCategoryCode(schoolCategory); + } + return schoolCategory; + + }); } public List getSchoolCategoryCodesFromRedisCache() { log.debug("**** Getting school category codes from Redis Cache."); - return schoolCategoryCodeTransformer.transformToDTO(schoolCategoryCodeRedisRepository.findAll()); + List schoolCategoryCodes = schoolCategoryCodeTransformer.transformToDTO(schoolCategoryCodeRedisRepository.findAll()); + return CollectionUtils.isEmpty(schoolCategoryCodes) ? loadSchoolCategoryCodesFromInstituteApiIntoRedisCacheAsync() : schoolCategoryCodes; } public void initializeSchoolCategoryCodeCache(boolean force) { @@ -78,7 +105,7 @@ public void initializeSchoolCategoryCodeCache(boolean force) { public List getSchoolFundingGroupCodesFromInstituteApi() { try { - log.debug("****Before Calling Institute API"); + log.debug("****Before Calling Institute API for SchoolFundingGroupCode"); List response = this.restService.get(constants.getAllSchoolFundingGroupCodesFromInstituteApiUrl(), List.class, webClient); return schoolFundingGroupCodeTransformer.transformToDTO(response); @@ -87,21 +114,39 @@ public List getSchoolFundingGroupCodesFromInstituteApi() } catch (Exception e) { log.error(String.format("Error while calling school-api: %s", e.getMessage())); } - return null; + return Collections.emptyList(); + } + + public List loadSchoolFundingGroupCodesFromInstituteApiIntoRedisCacheAsync() { + List schoolFundingGroupCodes = getSchoolFundingGroupCodesFromInstituteApi(); + if(!CollectionUtils.isEmpty(schoolFundingGroupCodes)) { + cacheService.loadSchoolFundingGroupCodesIntoRedisCacheAsync(schoolFundingGroupCodeTransformer.transformToEntity(schoolFundingGroupCodes)); + } + return schoolFundingGroupCodes; } public void loadSchoolFundingGroupCodesIntoRedisCache(List schoolFundingGroupCodes) { - schoolFundingGroupCodeRedisRepository - .saveAll(schoolFundingGroupCodeTransformer.transformToEntity(schoolFundingGroupCodes)); - log.info(String.format("%s School Funding Group Codes Loaded into cache.", schoolFundingGroupCodes.size())); + if(!CollectionUtils.isEmpty(schoolFundingGroupCodes)) { + cacheService.loadSchoolFundingGroupCodesIntoRedisCache(schoolFundingGroupCodeTransformer.transformToEntity(schoolFundingGroupCodes)); + } } public List getSchoolFundingGroupCodesFromRedisCache() { log.debug("**** Getting school funding group codes from Redis Cache."); - return schoolFundingGroupCodeTransformer.transformToDTO(schoolFundingGroupCodeRedisRepository.findAll()); + List schoolFundingGroupCodes = schoolFundingGroupCodeTransformer.transformToDTO(schoolFundingGroupCodeRedisRepository.findAll()); + return CollectionUtils.isEmpty(schoolFundingGroupCodes) ? loadSchoolFundingGroupCodesFromInstituteApiIntoRedisCacheAsync() : schoolFundingGroupCodes; } public void initializeSchoolFundingGroupCodeCache(boolean force) { serviceHelper.initializeCache(force, CacheKey.SCHOOL_FUNDING_GROUP_CODE_CACHE, this); } + + /** + * Updates the school category code in the cache + * @param schoolCategoryCode the school detail object + */ + public void updateSchoolCategoryCode(SchoolCategoryCode schoolCategoryCode) throws ServiceException { + schoolCategoryCodeRedisRepository.save(schoolCategoryCodeTransformer.transformToEntity(schoolCategoryCode)); + } + } 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 63aa20c4..669e7612 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 @@ -13,12 +13,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; @Slf4j @Service("instituteDistrictService") @@ -39,10 +41,12 @@ public class DistrictService { SchoolService schoolService; @Autowired RESTService restService; + @Autowired + CacheService cacheService; public List getDistrictsFromInstituteApi() { try { - log.debug("****Before Calling Institute API"); + log.debug("****Before Calling Institute API for districts"); List response = this.restService.get(constants.getAllDistrictsFromInstituteApiUrl(), List.class, webClient); List dList = districtTransformer.transformToDTO(response); @@ -73,15 +77,25 @@ public District getDistrictByIdFromInstituteApi(String districtId) { return null; } + private List loadDistrictFromInstituteApiIntoRedisCacheAsync() { + List districts = getDistrictsFromInstituteApi(); + if(!CollectionUtils.isEmpty(districts)) { + cacheService.loadDistrictsIntoRedisCacheAsync(districtTransformer.transformToEntity(districts)); + } + return districts; + } + public void loadDistrictsIntoRedisCache(List districts) { - districtRedisRepository - .saveAll(districtTransformer.transformToEntity(districts)); - log.info(String.format("%s Districts Loaded into cache.", districts.size())); + if(!CollectionUtils.isEmpty(districts)) { + log.info(String.format("%s Districts fetched from Institute API.", districts.size())); + cacheService.loadDistrictsIntoRedisCache(districtTransformer.transformToEntity(districts)); + } } public List getDistrictsFromRedisCache() { log.debug("**** Getting districts from Redis Cache."); - return districtTransformer.transformToDTO(districtRedisRepository.findAll()); + List districts = districtTransformer.transformToDTO(districtRedisRepository.findAll()); + return CollectionUtils.isEmpty(districts) ? loadDistrictFromInstituteApiIntoRedisCacheAsync() : districts; } public void initializeDistrictCache(boolean force) { @@ -90,12 +104,30 @@ public void initializeDistrictCache(boolean force) { public District getDistrictByDistNoFromRedisCache(String districtNumber) { log.debug("**** Getting district by district no. from Redis Cache."); - return districtRedisRepository.findByDistrictNumber(districtNumber).map(districtTransformer::transformToDTO).orElse(null); + return districtRedisRepository.findByDistrictNumber(districtNumber) + .map(districtTransformer::transformToDTO) + .orElseGet(() -> { + log.debug("district not found in cache for districtNumber: {}, , fetched from API.", districtNumber); + District district = getDistrictsFromInstituteApi().stream().filter(entry -> entry.getDistrictNumber().equals(districtNumber)).findFirst().orElse(null); + if(district != null) { + updateDistrictCache(district); + } + return district; + }); } public District getDistrictByIdFromRedisCache(String districtId) { log.debug("**** Getting district by ID from Redis Cache."); - return districtRedisRepository.findById(districtId).map(districtTransformer::transformToDTO).orElse(null); + return districtRedisRepository.findById(districtId) + .map(districtTransformer::transformToDTO) + .orElseGet(() -> { + log.debug("district not found in cache for districtId: {}, , fetched from API.", districtId); + District district = getDistrictByIdFromInstituteApi(districtId); + if(district != null) { + updateDistrictCache(district); + } + return district; + }); } public List getDistrictsBySchoolCategoryCode(String schoolCategoryCode) { @@ -107,8 +139,14 @@ public List getDistrictsBySchoolCategoryCode(String schoolCategoryCode schoolDetails = schoolService.getSchoolDetailsBySchoolCategoryCode(schoolCategoryCode); List districts = new ArrayList<>(); - for (SchoolDetail schoolDetail : schoolDetails) { - districts.add(getDistrictByIdFromRedisCache(schoolDetail.getDistrictId())); + if(!CollectionUtils.isEmpty(schoolDetails)) { + List districtIds = schoolDetails.stream() + .map(schoolDetail -> schoolDetail.getDistrictId()) + .distinct() + .collect(Collectors.toList()); + for (String districtId : districtIds) { + districts.add(getDistrictByIdFromRedisCache(districtId)); + } } return districts; } @@ -119,9 +157,18 @@ public List getDistrictsBySchoolCategoryCode(String schoolCategoryCode * @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)); + District district = getDistrictByIdFromInstituteApi(districtId); + updateDistrictCache(district); + } + + /** + * Updates the district details in the cache + */ + private void updateDistrictCache(District district) throws ServiceException { + if(district != null) { + log.debug(String.format("Updating district %s in cache.", district.getDistrictId())); + districtRedisRepository.save(this.districtTransformer.transformToEntity(district)); + log.debug(String.format("Updated district %s in cache.", district.getDistrictId())); + } } } 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 e3c249e9..77bf804f 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,17 +12,29 @@ 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.model.dto.institute.PaginatedResponse; +import ca.bc.gov.educ.api.trax.util.JsonUtil; +import ca.bc.gov.educ.api.trax.util.ThreadLocalStateUtil; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Strings; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; +import org.springframework.web.util.UriBuilder; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import java.util.*; +import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor @@ -46,10 +58,53 @@ public class SchoolService { ServiceHelper serviceHelper; @Autowired RESTService restService; + @Autowired + CacheService cacheService; + + ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + public List getSchoolsFromRedisCache() { + log.debug("**** Getting schools from Redis Cache."); + List schools = schoolTransformer.transformToDTO(schoolRedisRepository.findAll()); + return CollectionUtils.isEmpty(schools) ? loadSchoolsFromInstituteApiIntoRedisCacheAsync() : schools; + } + + public School getSchoolByMinCodeFromRedisCache(String minCode) { + log.debug("Get School by minCode from Redis Cache: {}", minCode); + return schoolRedisRepository.findByMincode(minCode) + .map(schoolTransformer::transformToDTO) + .orElseGet(() -> { + log.debug("School not found in cache, fetched from API."); + School school = getSchoolByMinCodeFromInstituteApi(minCode); + updateSchoolCache(school.getSchoolId()); + return school; + }); + } + + public boolean checkIfSchoolExists(String minCode) { + Optional schoolOptional = schoolRedisRepository.findByMincode(minCode); + return schoolOptional.isPresent() || getSchoolByMinCodeFromInstituteApi(minCode) != null; + } + + private List loadSchoolsFromInstituteApiIntoRedisCacheAsync() { + List schools = getSchoolsFromInstituteApi(); + if(!CollectionUtils.isEmpty(schools)) { + log.info(String.format("%s Schools fetched from Institute API.", schools.size())); + cacheService.loadSchoolsIntoRedisCacheAsync(schoolTransformer.transformToEntity(schools)); + } + return schools; + } + + public void loadSchoolsIntoRedisCache(List schools) { + if(!CollectionUtils.isEmpty(schools)) { + log.info(String.format("%s Schools fetched from Institute API.", schools.size())); + cacheService.loadSchoolsIntoRedisCache(schoolTransformer.transformToEntity(schools)); + } + } public List getSchoolsFromInstituteApi() { try { - log.debug("****Before Calling Institute API"); + log.debug("****Before Calling Institute API for schools"); List response = this.restService.get(constants.getAllSchoolsFromInstituteApiUrl(), List.class, webClient); return schoolTransformer.transformToDTO(response); @@ -61,34 +116,21 @@ public List getSchoolsFromInstituteApi() { return Collections.emptyList(); } - public void loadSchoolsIntoRedisCache(List schools) { - schoolRedisRepository - .saveAll(schoolTransformer.transformToEntity(schools)); - log.info(String.format("%s Schools Loaded into cache.", schools.size())); - } - - public List getSchoolsFromRedisCache() { - log.debug("**** Getting schools from Redis Cache."); - return schoolTransformer.transformToDTO(schoolRedisRepository.findAll()); - } - - public School getSchoolByMinCodeFromRedisCache(String minCode) { - log.debug("Get School by Mincode from Redis Cache: {}", minCode); - return schoolRedisRepository.findByMincode(minCode).map(schoolTransformer::transformToDTO).orElse(null); - } - - public boolean checkIfSchoolExists(String minCode) { - Optional schoolOptional = schoolRedisRepository.findByMincode(minCode); - return schoolOptional.isPresent(); + public School getSchoolByMinCodeFromInstituteApi(String minCode) { + return getSchoolsFromInstituteApi().stream().filter(school -> school.getMincode().equals(minCode)).findFirst().orElse(null); } + /** + * This method invokes loadSchoolsIntoRedisCache. + * @param force + */ public void initializeSchoolCache(boolean force) { serviceHelper.initializeCache(force, CacheKey.SCHOOL_CACHE, this); } - public SchoolDetail getSchoolDetailByIdFromInstituteApi(String schoolId) { + public SchoolDetail getSchoolDetailByIdFromInstituteApi(String schoolId) { try { - log.debug("****Before Calling Institute API"); + log.debug("****Before Calling Institute API for schoolId: {}", schoolId); SchoolDetailEntity sde = this.restService.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolId), SchoolDetailEntity.class, webClient); return schoolDetailTransformer.transformToDTO(sde); @@ -100,46 +142,105 @@ public SchoolDetail getSchoolDetailByIdFromInstituteApi(String schoolId) { return null; } - public List getSchoolDetailsFromInstituteApi() { + private PaginatedResponse getSchoolDetailsPaginatedFromInstituteApi(int pageNumber) { + int pageSize = 1000; + try { + return this.restService.get(String.format(constants.getSchoolsPaginatedFromInstituteApiUrl()+"?pageNumber=%d&pageSize=%d", pageNumber, pageSize), + PaginatedResponse.class, webClient); + } catch (WebClientResponseException e) { + log.warn(String.format("Error getting School Details from Institute API: %s", e.getMessage())); + } catch (Exception e) { + log.error(String.format("Error while calling institute-api: %s", e.getMessage())); + } + log.warn("No school details found for the given search criteria."); + return null; + } - List schools = getSchoolsFromRedisCache(); - List schoolDetails = new ArrayList<>(); - for (School s : schools) { - schoolDetails.add(getSchoolDetailByIdFromInstituteApi(s.getSchoolId())); - } - return schoolDetails; - } + // Recursive method to fetch school details page by page + public List getSchoolDetailsPaginatedFromInstituteApi(int pageNumber, List schoolDetails) { + PaginatedResponse response = getSchoolDetailsPaginatedFromInstituteApi(pageNumber); + if (response == null) { + return schoolDetails; + } + schoolDetails.addAll(response.getContent().stream() + .map(entry -> mapper.convertValue(entry, SchoolDetail.class)) + .toList()); + + if (response.hasNext()) { + return getSchoolDetailsPaginatedFromInstituteApi(response.nextPageable().getPageNumber(), schoolDetails); + } + return schoolDetails; + } + + public List getSchoolDetailsFromInstituteApi() { + log.debug("****Before Calling Institute API for school details"); + return getSchoolDetailsPaginatedFromInstituteApi(0, new ArrayList<>()); + } + + public List loadSchoolDetailsFromInstituteApiIntoRedisCacheAsync() { + List schoolDetails = getSchoolDetailsFromInstituteApi(); + if(!CollectionUtils.isEmpty(schoolDetails)) { + log.info(String.format("%s School details fetched from Institute API.", schoolDetails.size())); + cacheService.loadSchoolDetailsIntoRedisCacheAsync(schoolDetailTransformer.transformToEntity(schoolDetails)); + } + return schoolDetails; + } public void loadSchoolDetailsIntoRedisCache(List schoolDetails) { - schoolDetailRedisRepository - .saveAll(schoolDetailTransformer.transformToEntity(schoolDetails)); - log.info(String.format("%s School Details Loaded into cache.", schoolDetails.size())); + if(!CollectionUtils.isEmpty(schoolDetails)) { + log.info(String.format("%s Schools details fetched from Institute API.", schoolDetails.size())); + cacheService.loadSchoolDetailsIntoRedisCache(schoolDetailTransformer.transformToEntity(schoolDetails)); + } } public List getSchoolDetailsFromRedisCache() { log.debug("**** Getting school Details from Redis Cache."); - return schoolDetailTransformer.transformToDTO(schoolDetailRedisRepository.findAll()); + List schoolDetails = schoolDetailTransformer.transformToDTO(schoolDetailRedisRepository.findAll()); + return CollectionUtils.isEmpty(schoolDetails) ? loadSchoolDetailsFromInstituteApiIntoRedisCacheAsync() : schoolDetails; } - public SchoolDetail getSchoolDetailByMincodeFromRedisCache(String mincode) { + public SchoolDetail getSchoolDetailByMincodeFromRedisCache(String minCode) { log.debug("**** Getting school Details By Mincode from Redis Cache."); - return schoolDetailRedisRepository.findByMincode(mincode).map(schoolDetailTransformer::transformToDTO).orElse(null); + return schoolDetailRedisRepository.findByMincode(minCode) + .map(schoolDetailTransformer::transformToDTO) + .orElseGet(() -> { + log.debug("School detail not found in cache for mincode: {}, , fetched from API.", minCode); + School school = getSchoolByMinCodeFromRedisCache(minCode); + if(school != null) { + return getSchoolDetailBySchoolIdFromRedisCache(UUID.fromString(school.getSchoolId())); + } + return null; + }); } public SchoolDetail getSchoolDetailBySchoolIdFromRedisCache(UUID schoolId) { log.debug("**** Getting school Details By SchoolId from Redis Cache."); - return schoolDetailRedisRepository.findById(String.valueOf(schoolId)).map(schoolDetailTransformer::transformToDTO).orElse(null); + return schoolDetailRedisRepository.findById(String.valueOf(schoolId)) + .map(schoolDetailTransformer::transformToDTO) + .orElseGet(() -> { + log.debug("School detail not found in cache for schoolId: {}, , fetched from API.", schoolId); + SchoolDetail schoolDetail = getSchoolDetailByIdFromInstituteApi(schoolId.toString()); + updateSchoolCache(schoolDetail); + return schoolDetail; + }); } public List getSchoolDetailsBySchoolCategoryCode(String schoolCategoryCode) { - - return schoolDetailTransformer.transformToDTO( - schoolDetailRedisRepository.findBySchoolCategoryCode(schoolCategoryCode)); + List schoolDetails = schoolDetailTransformer.transformToDTO(schoolDetailRedisRepository.findBySchoolCategoryCode(schoolCategoryCode)); + if(CollectionUtils.isEmpty(schoolDetails)) { + log.debug("School detail not found in cache for schoolCategoryCode: {}, fetched from API.", schoolCategoryCode); + return loadSchoolDetailsFromInstituteApiIntoRedisCacheAsync().stream().filter(schoolDetail -> schoolDetail.getSchoolCategoryCode().equals(schoolCategoryCode)).toList(); + } + return schoolDetails; } public List getSchoolDetailsByDistrictFromRedisCache(String districtId) { - return schoolDetailTransformer.transformToDTO( - schoolDetailRedisRepository.findByDistrictId(districtId)); + List schoolDetails = schoolDetailTransformer.transformToDTO(schoolDetailRedisRepository.findByDistrictId(districtId)); + if(CollectionUtils.isEmpty(schoolDetails)) { + log.debug("School detail not found in cache for districtId: {}, fetched from API.", districtId); + return loadSchoolDetailsFromInstituteApiIntoRedisCacheAsync().stream().filter(schoolDetail -> schoolDetail.getDistrictId().equals(districtId)).toList(); + } + return schoolDetails; } /** @@ -152,7 +253,7 @@ public void updateSchoolCache(String schoolId) throws ServiceException { 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()); + log.debug("Retrieved school: {} from Institute API", schoolId); updateSchoolCache(schoolDetail); } @@ -177,7 +278,17 @@ public void updateSchoolCache(List schoolIds) throws ServiceException { } public Optional getSchoolBySchoolId(UUID schoolId) { - return schoolRedisRepository.findById(String.valueOf(schoolId)).map(schoolTransformer::transformToDTO); + log.debug("**** Getting school By SchoolId from Redis Cache."); + return schoolRedisRepository.findById(String.valueOf(schoolId)) + .map(schoolTransformer::transformToDTO) + .or(() -> { + log.debug("School not found in cache for schoolId: {}, , fetched from API.", schoolId); + Optional school = getSchoolsFromInstituteApi().stream().filter(entry -> entry.getSchoolId().equals(schoolId.toString())).findFirst(); + if(school.isPresent()) { + updateSchoolCache(school.get().getSchoolId()); + } + return school; + }); } /** @@ -190,7 +301,6 @@ public Optional getSchoolBySchoolId(UUID schoolId) { */ @Transactional(readOnly = true) public List getSchoolsByParams(String districtId, String mincode, String displayName, String distNo, List schoolCategoryCodes) { - SchoolSearchCriteria criteria = SchoolSearchCriteria.builder() .districtId(transformToWildcard(districtId)) .mincode(transformToWildcard(mincode)) @@ -200,8 +310,7 @@ public List getSchoolsByParams(String districtId, String mincode, String .build(); log.debug(criteria.toString()); - List schools = filterByCriteria(criteria, schoolRedisRepository.findAll()); - return schoolTransformer.transformToDTO(schools); + return filterByCriteria(criteria, getSchoolsFromRedisCache()); } /** @@ -210,9 +319,9 @@ public List getSchoolsByParams(String districtId, String mincode, String * @param schoolEntities * @return */ - private List filterByCriteria(SchoolSearchCriteria criteria, Iterable schoolEntities) { - List schools = new ArrayList<>(); - for (SchoolEntity school : schoolEntities) { + private List filterByCriteria(SchoolSearchCriteria criteria, List schoolEntities) { + List schools = new ArrayList<>(); + for (School school : schoolEntities) { if (school.getDistrictId().matches(criteria.getDistrictId()) && school.getMincode().matches(criteria.getMincode()) && school.getDisplayName().matches(criteria.getDisplayName()) diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/ServiceHelper.java b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/ServiceHelper.java index dc941b75..e65edb2e 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/ServiceHelper.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/ServiceHelper.java @@ -2,11 +2,15 @@ import ca.bc.gov.educ.api.trax.constant.CacheKey; import ca.bc.gov.educ.api.trax.constant.CacheStatus; +import ca.bc.gov.educ.api.trax.model.dto.institute.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import redis.clients.jedis.JedisCluster; +import java.util.List; + @Slf4j @Component public class ServiceHelper { @@ -33,41 +37,39 @@ public void initializeCache(boolean force, CacheKey cacheKey, T service) { } private void loadCache(CacheKey cacheKey, T service) { - jedisCluster.set(cacheKey.name(), CacheStatus.LOADING.name()); loadDataIntoRedisCache(cacheKey, service); - jedisCluster.set(cacheKey.name(), CacheStatus.READY.name()); - log.info(String.format("Success! - %s is now READY", cacheKey)); } private void loadDataIntoRedisCache(CacheKey cacheKey, T service) { try { switch (cacheKey) { case SCHOOL_CATEGORY_CODE_CACHE -> { - ((CodeService)service).loadSchoolCategoryCodesIntoRedisCache( - ((CodeService)service).getSchoolCategoryCodesFromInstituteApi() - ); - break; + List schoolCategoryCodes = ((CodeService)service).getSchoolCategoryCodesFromInstituteApi(); + if(!CollectionUtils.isEmpty(schoolCategoryCodes)) { + ((CodeService)service).loadSchoolCategoryCodesIntoRedisCache(schoolCategoryCodes); + } } case SCHOOL_FUNDING_GROUP_CODE_CACHE -> { - ((CodeService)service).loadSchoolFundingGroupCodesIntoRedisCache( - ((CodeService)service).getSchoolFundingGroupCodesFromInstituteApi() - ); - break; + List schoolFundingGroupCodes = ((CodeService)service).getSchoolFundingGroupCodesFromInstituteApi(); + if(!CollectionUtils.isEmpty(schoolFundingGroupCodes)) { + ((CodeService)service).loadSchoolFundingGroupCodesIntoRedisCache(schoolFundingGroupCodes); + } } case DISTRICT_CACHE -> { - ((DistrictService)service).loadDistrictsIntoRedisCache( - ((DistrictService)service).getDistrictsFromInstituteApi() - ); - break; + List districts = ((DistrictService)service).getDistrictsFromInstituteApi(); + if(!CollectionUtils.isEmpty(districts)) { + ((DistrictService) service).loadDistrictsIntoRedisCache(districts); + } } case SCHOOL_CACHE -> { - ((SchoolService)service).loadSchoolsIntoRedisCache( - ((SchoolService)service).getSchoolsFromInstituteApi() - ); - ((SchoolService)service).loadSchoolDetailsIntoRedisCache( - ((SchoolService)service).getSchoolDetailsFromInstituteApi() - ); - break; + List schools = ((SchoolService)service).getSchoolsFromInstituteApi(); + if(!CollectionUtils.isEmpty(schools)) { + ((SchoolService) service).loadSchoolsIntoRedisCache(schools); + } + List schoolDetails = ((SchoolService)service).getSchoolDetailsFromInstituteApi(); + if(!CollectionUtils.isEmpty(schoolDetails)) { + ((SchoolService) service).loadSchoolDetailsIntoRedisCache(schoolDetails); + } } default -> { log.info(String.format("Invalid Cache Key %s", cacheKey)); 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 679bdc49..83c71b96 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 @@ -170,11 +170,15 @@ public class EducGradTraxApiConstants { @Value("${endpoint.institute-api.get-all-schools.url}") private String allSchoolsFromInstituteApiUrl; + @Value("${endpoint.institute-api.get-schools-paginated.url}") + private String schoolsPaginatedFromInstituteApiUrl; + @Value("${endpoint.institute-api.get-school-details-by-id.url}") private String schoolDetailsByIdFromInstituteApiUrl; @Value("${endpoint.institute-api.get-all-districts.url}") private String allDistrictsFromInstituteApiUrl; + @Value("${endpoint.institute-api.get-district.url}") private String getDistrictFromInstituteApiUrl; diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml index 3b2f7b79..0f63cb09 100644 --- a/api/src/main/resources/application.yaml +++ b/api/src/main/resources/application.yaml @@ -190,6 +190,8 @@ endpoint: institute-api: get-all-schools: url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/school + get-schools-paginated: + url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/school/paginated get-school-details-by-id: url: ${INSTITUTE_API_URL_ROOT}api/v1/institute/school/%s get-all-districts: diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/CacheServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/CacheServiceTest.java new file mode 100644 index 00000000..c913a945 --- /dev/null +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/institute/CacheServiceTest.java @@ -0,0 +1,172 @@ +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.model.dto.ResponseObj; +import ca.bc.gov.educ.api.trax.model.dto.institute.PaginatedResponse; +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.SchoolDetailTransformer; +import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolTransformer; +import ca.bc.gov.educ.api.trax.repository.redis.*; +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.h2.util.Cache; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +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.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import redis.clients.jedis.JedisCluster; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@SpringBootTest +@ActiveProfiles("test") +@ExtendWith(MockitoExtension.class) +@SuppressWarnings({"unchecked","rawtypes"}) +class CacheServiceTest { + + @Autowired + private EducGradTraxApiConstants constants; + @Autowired + private CacheService cacheService; + @MockBean + private ServiceHelper serviceHelperMock; + @MockBean + private SchoolRedisRepository schoolRedisRepository; + @MockBean + private SchoolDetailRedisRepository schoolDetailRedisRepository; + @MockBean + private DistrictRedisRepository districtRedisRepository; + @MockBean + private SchoolCategoryCodeRedisRepository schoolCategoryCodeRedisRepository; + @MockBean + private SchoolFundingGroupCodeRedisRepository schoolFundingGroupCodeRedisRepository; + @MockBean + private JedisConnectionFactory jedisConnectionFactoryMock; + @MockBean + private JedisCluster jedisClusterMock; + + @TestConfiguration + static class TestConfigInstitute { + @Bean + public ClientRegistrationRepository clientRegistrationRepository() { + return new ClientRegistrationRepository() { + @Override + public ClientRegistration findByRegistrationId(String registrationId) { + return null; + } + }; + } + } + + @Test + void whenLoadSchoolsIntoRedisCache_DoesNotThrow() { + List schoolEntities = new ArrayList<>(); + SchoolEntity schoolEntity = new SchoolEntity(); + + schoolEntity.setSchoolId("ID"); + schoolEntity.setDistrictId("DistID"); + schoolEntity.setSchoolNumber("12345"); + schoolEntity.setSchoolCategoryCode("SCC"); + schoolEntity.setEmail("abc@xyz.ca"); + schoolEntity.setDisplayName("Tk̓emlúps te Secwépemc"); + schoolEntity.setDisplayNameNoSpecialChars("Tkkemlups te Secwepemc"); + schoolEntities.add(schoolEntity); + when(this.schoolRedisRepository.saveAll(schoolEntities)) + .thenReturn(schoolEntities); + assertDoesNotThrow(() -> cacheService.loadSchoolsIntoRedisCacheAsync(schoolEntities)); + assertDoesNotThrow(() -> cacheService.loadSchoolsIntoRedisCache(schoolEntities)); + } + + @Test + void whenLoadSchoolDetailsIntoRedisCache_DoesNotThrow() { + List schoolDetailEntities = new ArrayList<>(); + String districtId = "DistID"; + String mincode = "12345678"; + SchoolDetailEntity schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId("ID"); + schoolDetailEntity.setDistrictId(districtId); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setMincode(mincode); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + schoolDetailEntities.add(schoolDetailEntity); + when(this.schoolDetailRedisRepository.saveAll(schoolDetailEntities)) + .thenReturn(schoolDetailEntities); + assertDoesNotThrow(() -> cacheService.loadSchoolDetailsIntoRedisCacheAsync(schoolDetailEntities)); + assertDoesNotThrow(() -> cacheService.loadSchoolDetailsIntoRedisCache(schoolDetailEntities)); + } + + @Test + void whenLoadDistrictsIntoRedisCache_DoesNotThrow() { + 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); + when(this.districtRedisRepository.saveAll(districtEntities)) + .thenReturn(districtEntities); + assertDoesNotThrow(() -> cacheService.loadDistrictsIntoRedisCacheAsync(districtEntities)); + assertDoesNotThrow(() -> cacheService.loadDistrictsIntoRedisCache(districtEntities)); + } + + @Test + void whenLoadSchoolCategoryCodesIntoRedisCache_DoesNotThrow() { + List schoolCategoryCodeEntities = new ArrayList<>(); + SchoolCategoryCodeEntity scce = new SchoolCategoryCodeEntity(); + scce.setSchoolCategoryCode("11"); + scce.setDescription("Description"); + scce.setLegacyCode("LegacyCode"); + scce.setLabel("Label"); + scce.setEffectiveDate("01-01-2024"); + scce.setExpiryDate("01-01-2024"); + scce.setDisplayOrder("10"); + schoolCategoryCodeEntities.add(scce); + when(this.schoolCategoryCodeRedisRepository.saveAll(schoolCategoryCodeEntities)) + .thenReturn(schoolCategoryCodeEntities); + assertDoesNotThrow(() -> cacheService.loadSchoolCategoryCodesIntoRedisCacheAsync(schoolCategoryCodeEntities)); + assertDoesNotThrow(() -> cacheService.loadSchoolCategoryCodesIntoRedisCache(schoolCategoryCodeEntities)); + } + + @Test + void whenLsoadSchoolFundingGroupCodesIntoRedisCache_DoesNotThrow() { + List schoolFundingGroupCodeEntities = new ArrayList<>(); + SchoolFundingGroupCodeEntity sfgc = new SchoolFundingGroupCodeEntity(); + + sfgc.setSchoolFundingGroupCode("CODE"); + sfgc.setDescription("Description"); + sfgc.setLabel("Label"); + sfgc.setEffectiveDate("01-01-2024"); + sfgc.setExpiryDate("01-01-2024"); + sfgc.setDisplayOrder("10"); + schoolFundingGroupCodeEntities.add(sfgc); + when(this.schoolFundingGroupCodeRedisRepository.saveAll(schoolFundingGroupCodeEntities)) + .thenReturn(schoolFundingGroupCodeEntities); + assertDoesNotThrow(() -> cacheService.loadSchoolFundingGroupCodesIntoRedisCacheAsync(schoolFundingGroupCodeEntities)); + assertDoesNotThrow(() -> cacheService.loadSchoolFundingGroupCodesIntoRedisCache(schoolFundingGroupCodeEntities)); + } + +} 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 b76a4ef1..fef2cde5 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,15 +6,19 @@ 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.SchoolCategoryCode; import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolFundingGroupCode; import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolCategoryCodeEntity; import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolFundingGroupCodeEntity; import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolCategoryCodeTransformer; +import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolFundingGroupCodeTransformer; import ca.bc.gov.educ.api.trax.repository.redis.SchoolCategoryCodeRedisRepository; import ca.bc.gov.educ.api.trax.repository.redis.SchoolFundingGroupCodeRedisRepository; +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.checkerframework.checker.nullness.Opt; import org.junit.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.runner.RunWith; @@ -40,12 +44,15 @@ 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.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.*; +import static org.wildfly.common.Assert.assertNotNull; @RunWith(SpringRunner.class) @SpringBootTest @@ -70,6 +77,9 @@ public class InstituteCodeServiceTest { @MockBean @Qualifier("default") WebClient webClientMock; + @MockBean + @Qualifier("instituteWebClient") + private WebClient instWebClient; @Mock private WebClient.RequestHeadersSpec requestHeadersSpecMock; @Mock @@ -88,9 +98,12 @@ public class InstituteCodeServiceTest { private Mono> schoolFundingGroupCodeEntitiesMock; @Mock SchoolCategoryCodeTransformer schoolCategoryCodeTransformer; + @Mock + SchoolFundingGroupCodeTransformer schoolFundingGroupCodeTransformer; @MockBean private RestUtils restUtils; - + @MockBean + private RESTService restServiceMock; // NATS @MockBean private NatsConnection natsConnection; @@ -116,78 +129,172 @@ public ClientRegistration findByRegistrationId(String registrationId) { @Test public void whenGetSchoolCategoryCodesFromInstituteApi_returnsListOfSchoolCategoryCode() { - List schoolCategoryCodes = new ArrayList<>(); SchoolCategoryCodeEntity scce = new SchoolCategoryCodeEntity(); + List scces = new ArrayList(); + scce.setSchoolCategoryCode("SCC1"); + scce.setLabel("SCC1-label"); + scces.add(scce); + scce = new SchoolCategoryCodeEntity(); + scce.setSchoolCategoryCode("SCC2"); + scce.setLabel("SCC2-label"); + scces.add(scce); - scce.setSchoolCategoryCode("11"); - scce.setDescription("Description"); - scce.setLegacyCode("LegacyCode"); - scce.setLabel("Label"); - scce.setEffectiveDate("01-01-2024"); - scce.setExpiryDate("01-01-2024"); - scce.setDisplayOrder("10"); - schoolCategoryCodes.add(scce); + SchoolCategoryCode scc = new SchoolCategoryCode(); + List sccs = new ArrayList(); + scc.setSchoolCategoryCode("SCC1"); + scc.setLabel("SCC1-label"); + scc.setDescription("Desc"); + scc.setLegacyCode("SCC1-legacy"); + scc.setDisplayOrder("10"); + scc.setEffectiveDate("01-01-2024"); + scc.setExpiryDate("01-01-2024"); + sccs.add(scc); + scc = new SchoolCategoryCode(); + scc.setSchoolCategoryCode("SCC2"); + scc.setLabel("SCC2-label"); + scc.setDescription("Desc"); + scc.setLegacyCode("SCC2-legacy"); + scc.setDisplayOrder("20"); + scc.setEffectiveDate("01-01-2024"); + scc.setExpiryDate("01-01-2024"); + sccs.add(scc); - 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(schoolCategoryCodeEntitiesMock); - when(this.schoolCategoryCodeEntitiesMock.block()) - .thenReturn(schoolCategoryCodes); + when(restServiceMock.get(constants.getAllSchoolCategoryCodesFromInstituteApiUrl(), List.class, instWebClient)).thenReturn(scces); + when(schoolCategoryCodeTransformer.transformToDTO(scces)) + .thenReturn(sccs); + List result = codeService.getSchoolCategoryCodesFromInstituteApi(); + assertNotNull(result); + assertDoesNotThrow(() -> codeService.loadSchoolCategoryCodesFromInstituteApiIntoRedisCacheAsync()); + } + @Test + public void whenGetSchoolCategoryCodeFromRedisCache_returnSchoolCategoryCode() { + SchoolCategoryCodeEntity scce = new SchoolCategoryCodeEntity(); + List scces = new ArrayList(); + scce.setSchoolCategoryCode("SCC1"); + scce.setLabel("SCC1-label"); + scces.add(scce); + scce = new SchoolCategoryCodeEntity(); + scce.setSchoolCategoryCode("SCC2"); + scce.setLabel("SCC2-label"); + scces.add(scce); + + SchoolCategoryCode scc = new SchoolCategoryCode(); + List sccs = new ArrayList(); + scc.setSchoolCategoryCode("SCC1"); + scc.setLabel("SCC1-label"); + scc.setDescription("Desc"); + scc.setLegacyCode("SCC1-legacy"); + scc.setDisplayOrder("10"); + scc.setEffectiveDate("01-01-2024"); + scc.setExpiryDate("01-01-2024"); + sccs.add(scc); + scc = new SchoolCategoryCode(); + scc.setSchoolCategoryCode("SCC2"); + scc.setLabel("SCC2-label"); + scc.setDescription("Desc"); + scc.setLegacyCode("SCC2-legacy"); + scc.setDisplayOrder("20"); + scc.setEffectiveDate("01-01-2024"); + scc.setExpiryDate("01-01-2024"); + sccs.add(scc); + + when(this.schoolCategoryCodeRedisRepository.findById("SCC1")) + .thenReturn(Optional.of(scce)); + assertNotNull(codeService.getSchoolCategoryCodeFromRedisCache("SCC1")); + } + + @Test + public void whenGetSchoolCategoryCodeFromRedisCache_NotFound_returnSchoolCategoryCode() { + SchoolCategoryCodeEntity scce = new SchoolCategoryCodeEntity(); + List scces = new ArrayList(); + scce.setSchoolCategoryCode("SCC1"); + scce.setLabel("SCC1-label"); + scces.add(scce); + scce = new SchoolCategoryCodeEntity(); + scce.setSchoolCategoryCode("SCC2"); + scce.setLabel("SCC2-label"); + scces.add(scce); + + SchoolCategoryCode scc = new SchoolCategoryCode(); + List sccs = new ArrayList(); + scc.setSchoolCategoryCode("SCC1"); + scc.setLabel("SCC1-label"); + scc.setDescription("Desc"); + scc.setLegacyCode("SCC1-legacy"); + scc.setDisplayOrder("10"); + scc.setEffectiveDate("01-01-2024"); + scc.setExpiryDate("01-01-2024"); + sccs.add(scc); + scc = new SchoolCategoryCode(); + scc.setSchoolCategoryCode("SCC2"); + scc.setLabel("SCC2-label"); + scc.setDescription("Desc"); + scc.setLegacyCode("SCC2-legacy"); + scc.setDisplayOrder("20"); + scc.setEffectiveDate("01-01-2024"); + scc.setExpiryDate("01-01-2024"); + sccs.add(scc); + + when(restServiceMock.get(constants.getAllSchoolCategoryCodesFromInstituteApiUrl(), List.class, instWebClient)).thenReturn(scces); + when(schoolCategoryCodeTransformer.transformToDTO(scces)) + .thenReturn(sccs); List result = codeService.getSchoolCategoryCodesFromInstituteApi(); + assertNotNull(result); + assertDoesNotThrow(() -> codeService.loadSchoolCategoryCodesFromInstituteApiIntoRedisCacheAsync()); + + when(this.schoolCategoryCodeRedisRepository.findById("SCC1")) + .thenReturn(Optional.empty()); + assertNotNull(codeService.getSchoolCategoryCodeFromRedisCache("SCC1")); } @Test public void whenGetSchoolFundingGroupCodesFromInstituteApi_returnsListOfSchoolFundingGroupCode() { List schoolFundingGroupCodes = new ArrayList<>(); - SchoolFundingGroupCodeEntity sfgc = new SchoolFundingGroupCodeEntity(); + SchoolFundingGroupCodeEntity sfgce = new SchoolFundingGroupCodeEntity(); - sfgc.setSchoolFundingGroupCode("CODE"); - sfgc.setDescription("Description"); - sfgc.setLabel("Label"); + sfgce.setSchoolFundingGroupCode("CODE"); + sfgce.setDescription("Description"); + sfgce.setLabel("Label"); + sfgce.setEffectiveDate("01-01-2024"); + sfgce.setExpiryDate("01-01-2024"); + sfgce.setDisplayOrder("10"); + schoolFundingGroupCodes.add(sfgce); + + SchoolFundingGroupCode sfgc = new SchoolFundingGroupCode(); + List sfgcs = new ArrayList(); + sfgc.setSchoolFundingGroupCode("SCC1"); + sfgc.setLabel("SCC1-label"); + sfgc.setDescription("Desc"); + sfgc.setDisplayOrder("10"); sfgc.setEffectiveDate("01-01-2024"); sfgc.setExpiryDate("01-01-2024"); - sfgc.setDisplayOrder("10"); - schoolFundingGroupCodes.add(sfgc); - - 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(Mono.just(schoolFundingGroupCodes)); - when(this.schoolFundingGroupCodeEntitiesMock.block()) - .thenReturn(schoolFundingGroupCodes); + sfgcs.add(sfgc); + sfgc = new SchoolFundingGroupCode(); + sfgc.setSchoolFundingGroupCode("SCC2"); + sfgc.setLabel("SCC2-label"); + sfgc.setDescription("Desc"); + sfgc.setDisplayOrder("20"); + sfgc.setEffectiveDate("01-01-2024"); + sfgc.setExpiryDate("01-01-2024"); + sfgcs.add(sfgc); + when(restServiceMock.get(constants.getAllSchoolFundingGroupCodesFromInstituteApiUrl(), List.class, instWebClient)).thenReturn(schoolFundingGroupCodes); + when(schoolFundingGroupCodeTransformer.transformToDTO(schoolFundingGroupCodes)) + .thenReturn(sfgcs); List result = codeService.getSchoolFundingGroupCodesFromInstituteApi(); + assertNotNull(result); + assertDoesNotThrow(() -> codeService.loadSchoolFundingGroupCodesFromInstituteApiIntoRedisCacheAsync()); + } @Test public void whenLoadSchoolCategoryCodesIntoRedisCache_DoesNotThrow() { - List schoolFundingGroupCodeEntities = Arrays.asList(new SchoolFundingGroupCodeEntity()); - List schoolFundingGroupCodes = Arrays.asList(new SchoolFundingGroupCode()); - when(this.schoolFundingGroupCodeRedisRepository.saveAll(schoolFundingGroupCodeEntities)) - .thenReturn(schoolFundingGroupCodeEntities); - assertDoesNotThrow(() -> codeService.loadSchoolFundingGroupCodesIntoRedisCache(schoolFundingGroupCodes)); + List schoolCategoryCodeEntities = Arrays.asList(new SchoolCategoryCodeEntity()); + List schoolCategoryCodes = Arrays.asList(new SchoolCategoryCode()); + when(this.schoolCategoryCodeRedisRepository.saveAll(schoolCategoryCodeEntities)) + .thenReturn(schoolCategoryCodeEntities); + assertDoesNotThrow(() -> codeService.loadSchoolCategoryCodesIntoRedisCache(schoolCategoryCodes)); } @Test @@ -211,6 +318,24 @@ public void whenGetSchoolCategoryCodesFromRedisCache_GetSchoolCategoryCodes() { scce.setLabel("SCC2-label"); scces.add(scce); when(schoolCategoryCodeRedisRepository.findAll()).thenReturn(scces); + List result = codeService.getSchoolCategoryCodesFromRedisCache(); + assertNotNull(result); + } + + @Test + public void whenGetSchoolFundingCodesFromRedisCache_GetSchoolCategoryCodes() { + SchoolFundingGroupCodeEntity sfgce = new SchoolFundingGroupCodeEntity(); + List sfgces = new ArrayList(); + sfgce.setSchoolFundingGroupCode("SFGC1"); + sfgce.setLabel("SFGC1-label"); + sfgces.add(sfgce); + sfgce = new SchoolFundingGroupCodeEntity(); + sfgce.setSchoolFundingGroupCode("SFGC2"); + sfgce.setLabel("SFGC2-label"); + sfgces.add(sfgce); + when(schoolFundingGroupCodeRedisRepository.findAll()).thenReturn(sfgces); + List result = codeService.getSchoolFundingGroupCodesFromRedisCache(); + assertNotNull(result); } @Test 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 701b793c..62f2de67 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 @@ -47,6 +47,7 @@ import java.util.*; import java.util.function.Consumer; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -315,6 +316,39 @@ public void whenGetDistrictByDistNoFromRedisCache_ReturnDistrict() { assertEquals(district, districtService.getDistrictByDistNoFromRedisCache(distNo)); } + @Test + public void whenGetDistrictByDistNoFromRedisCache_NotFound_ReturnDistrict() { + String distNo = "1234"; + 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 DistrictContact(), new DistrictContact())); + districts.add(district); + + when(restServiceMock.get(constants.getAllDistrictsFromInstituteApiUrl(), List.class, instWebClient)) + .thenReturn(districtEntities); + when(districtTransformerMock.transformToDTO(districtEntities)) + .thenReturn(districts); + when(districtService.getDistrictByIdFromInstituteApi(district.getDistrictId())) + .thenReturn(district); + + when(this.districtRedisRepository.findByDistrictNumber(distNo)) + .thenReturn(Optional.empty()); + assertEquals(district, districtService.getDistrictByDistNoFromRedisCache(distNo)); + } + @Test public void whenGetDistrictByIdFromRedisCache_ReturnDistrict() { District district = new District(); @@ -331,13 +365,50 @@ public void whenGetDistrictByIdFromRedisCache_ReturnDistrict() { districtEntity.setDistrictRegionCode("RC"); districtEntity.setContacts(Arrays.asList(new DistrictContactEntity(), new DistrictContactEntity())); - when(this.districtRedisRepository.findById("ID")) - .thenReturn(Optional.of(districtEntity)); + when(this.districtRedisRepository.findById("ID")).thenReturn(Optional.of(districtEntity)); when(this.districtTransformerMock.transformToDTO(districtEntity)) .thenReturn(district); assertEquals(district, districtService.getDistrictByIdFromRedisCache("ID")); } + @Test + public void whenGetDistrictByIdFromRedisCache_NotFound_ReturnDistrict() { + String distId = "ID"; + 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 DistrictContact(), new DistrictContact())); + districts.add(district); + + when(this.districtRedisRepository.findById("ID")).thenReturn(Optional.of(districtEntity)); + when(this.districtTransformerMock.transformToDTO(districtEntity)) + .thenReturn(district); + assertEquals(district, districtService.getDistrictByIdFromRedisCache("ID")); + + when(restServiceMock.get(constants.getAllDistrictsFromInstituteApiUrl(), List.class, instWebClient)) + .thenReturn(districtEntities); + when(districtTransformerMock.transformToDTO(districtEntities)) + .thenReturn(districts); + when(districtService.getDistrictByIdFromInstituteApi(district.getDistrictId())) + .thenReturn(district); + + when(this.districtRedisRepository.findById(distId)) + .thenReturn(Optional.empty()); + assertEquals(district, districtService.getDistrictByIdFromRedisCache(distId)); + } + @Test public void whenInitializeDistrictCache_WithReadyAndTrue_ThenForceLoad() { when(jedisClusterMock.get(CacheKey.DISTRICT_CACHE.name())) @@ -426,13 +497,23 @@ public void whenGetDistrictsBySchoolCategoryCode_ReturnDistricts() { .thenReturn(schoolDetails); when(this.districtRedisRepository.findById("DistID")) .thenReturn(Optional.of(districtEntity)); - when(this.districtService.getDistrictByIdFromRedisCache("DistID")) + when(restServiceMock.get(constants.getAllDistrictsFromInstituteApiUrl(), List.class, instWebClient)) + .thenReturn(districtEntities); + when(districtTransformerMock.transformToDTO(districtEntities)) + .thenReturn(districts); + when(districtService.getDistrictByIdFromInstituteApi(district.getDistrictId())) .thenReturn(district); when(this.districtTransformerMock.transformToDTO(districtEntities)) .thenReturn(districts); when(this.schoolService.getSchoolDetailsBySchoolCategoryCode(schoolCategoryCode)) .thenReturn(schoolDetails); - assertEquals(districts, districtService.getDistrictsBySchoolCategoryCode(schoolCategoryCode)); + when(districtService.getDistrictByIdFromInstituteApi("DistID")) + .thenReturn(district); + when(this.districtTransformerMock.transformToDTO(districtEntity)) + .thenReturn(district); + List result = districtService.getDistrictsBySchoolCategoryCode(schoolCategoryCode); + assertNotNull(result); + assertTrue(result.size() == 1); } @Test 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 856bb56c..115b6311 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 @@ -2,6 +2,7 @@ import ca.bc.gov.educ.api.trax.constant.CacheKey; import ca.bc.gov.educ.api.trax.model.dto.ResponseObj; +import ca.bc.gov.educ.api.trax.model.dto.institute.PaginatedResponse; 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.*; @@ -36,8 +37,7 @@ import java.util.*; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @SpringBootTest @ActiveProfiles("test") @@ -72,6 +72,11 @@ class InstituteSchoolServiceTest { @Mock private WebClient.ResponseSpec responseSpecMock; @Mock + private WebClient.RequestBodyUriSpec requestBodyUriSpec; + + @Mock + private WebClient.RequestBodySpec requestBodySpec; + @Mock private HttpHeaders httpHeadersMock; @Mock private ResponseObj responseObjectMock; @@ -140,6 +145,7 @@ void whenGetSchoolsFromInstituteApi_returnsListOfSchools() { List result = schoolService.getSchoolsFromInstituteApi(); assertEquals(schools, result); + assertDoesNotThrow(() -> schoolService.loadSchoolDetailsFromInstituteApiIntoRedisCacheAsync()); } @Test @@ -225,6 +231,85 @@ void whenGetSchoolByMincodeFromRedisCache_ReturnSchool() { assertEquals(school, schoolService.getSchoolByMinCodeFromRedisCache(mincode)); } + @Test + void whenGetSchoolByMincodeFromRedisCache_NotFound_ReturnSchool() { + List schoolEntities = new ArrayList<>(); + SchoolEntity schoolEntity = new SchoolEntity(); + String mincode = "12345678"; + schoolEntity.setSchoolId("ID"); + schoolEntity.setDistrictId("DistID"); + schoolEntity.setSchoolNumber("12345"); + schoolEntity.setSchoolCategoryCode("SCC"); + schoolEntity.setEmail("abc@xyz.ca"); + schoolEntity.setDisplayName("Tk̓emlúps te Secwépemc"); + schoolEntity.setDisplayNameNoSpecialChars("Tkkemlups te Secwepemc"); + schoolEntity.setMincode(mincode); + schoolEntities.add(schoolEntity); + + List schools = new ArrayList<>(); + School school = new School(); + school.setSchoolId("ID"); + school.setDistrictId("DistID"); + school.setSchoolNumber("12345"); + school.setSchoolCategoryCode("SCC"); + school.setEmail("abc@xyz.ca"); + school.setDisplayName("Tk̓emlúps te Secwépemc"); + school.setDisplayNameNoSpecialChars("Tkkemlups te Secwepemc"); + school.setMincode(mincode); + schools.add(school); + + when(this.schoolTransformer.transformToDTO(schoolEntities)).thenReturn(schools); + when(this.restServiceMock.get(constants.getAllSchoolsFromInstituteApiUrl(), + List.class, instWebClient)).thenReturn(schoolEntities); + + List result = schoolService.getSchoolsFromInstituteApi(); + assertEquals(schools, result); + + when(this.schoolRedisRepository.findByMincode(mincode)) + .thenReturn(Optional.empty()); + assertEquals(school, schoolService.getSchoolByMinCodeFromRedisCache(mincode)); + } + + @Test + void whenGetSchoolDetailByschoolIdFromRedisCache_ReturnSchoolDetail() { + List schoolEntities = new ArrayList<>(); + SchoolEntity schoolEntity = new SchoolEntity(); + String mincode = "12345678"; + String schoolId = "ID"; + schoolEntity.setSchoolId(schoolId); + schoolEntity.setDistrictId("DistID"); + schoolEntity.setSchoolNumber("12345"); + schoolEntity.setSchoolCategoryCode("SCC"); + schoolEntity.setEmail("abc@xyz.ca"); + schoolEntity.setDisplayName("Tk̓emlúps te Secwépemc"); + schoolEntity.setDisplayNameNoSpecialChars("Tkkemlups te Secwepemc"); + schoolEntity.setMincode(mincode); + schoolEntities.add(schoolEntity); + + List schools = new ArrayList<>(); + School school = new School(); + school.setSchoolId(schoolId); + school.setDistrictId("DistID"); + school.setSchoolNumber("12345"); + school.setSchoolCategoryCode("SCC"); + school.setEmail("abc@xyz.ca"); + school.setDisplayName("Tk̓emlúps te Secwépemc"); + school.setDisplayNameNoSpecialChars("Tkkemlups te Secwepemc"); + school.setMincode(mincode); + schools.add(school); + + when(this.schoolTransformer.transformToDTO(schoolEntities)).thenReturn(schools); + when(this.restServiceMock.get(constants.getAllSchoolsFromInstituteApiUrl(), + List.class, instWebClient)).thenReturn(schoolEntities); + + List result = schoolService.getSchoolsFromInstituteApi(); + assertEquals(schools, result); + + when(this.schoolRedisRepository.findByMincode(mincode)) + .thenReturn(Optional.empty()); + assertEquals(school, schoolService.getSchoolByMinCodeFromRedisCache(mincode)); + } + @Test void whenGetSchoolDetailsByDistrictFromRedisCache_ReturnSchools() { String districtId = "DistID"; @@ -258,6 +343,47 @@ void whenGetSchoolDetailsByDistrictFromRedisCache_ReturnSchools() { assertEquals(schoolDetails, schoolService.getSchoolDetailsByDistrictFromRedisCache(districtId)); } + @Test + void whenGetSchoolDetailsByDistrictFromRedisCache_NotFound_ReturnSchools() { + String districtId = "DistID"; + String mincode = "12345678"; + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("ID"); + schoolDetail.setDistrictId(districtId); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setMincode(mincode); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); + + List schoolDetails = new ArrayList<>(); + schoolDetails.add(schoolDetail); + + SchoolDetailEntity schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId("ID"); + schoolDetailEntity.setDistrictId(districtId); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setMincode(mincode); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + + List schoolDetailEntities = new ArrayList<>(); + schoolDetailEntities.add(schoolDetailEntity); + + when(this.schoolDetailRedisRepository.findByDistrictId(districtId)) + .thenReturn(Collections.emptyList()); + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntities)) + .thenReturn(schoolDetails); + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntity)).thenReturn(schoolDetail); + when(this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), "1"), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity); + + PaginatedResponse response = new PaginatedResponse<>(schoolDetails); + when(this.restServiceMock.get(constants.getSchoolsPaginatedFromInstituteApiUrl(), PaginatedResponse.class, WebClient.builder().build())) + .thenReturn(response); + assertDoesNotThrow(() -> schoolService.getSchoolDetailsByDistrictFromRedisCache(districtId)); + } + + @Test void whenCheckIfSchoolExists_returnTrue() { String minCode = "12345678"; @@ -335,8 +461,12 @@ void whenGetSchoolDetailsFromInstituteApi_returnsListOfSchoolDetails() { when(this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), "2"), SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity2); - List result = schoolService.getSchoolDetailsFromInstituteApi(); - assertEquals(schoolDetails, result); + PaginatedResponse response = new PaginatedResponse<>(schoolDetails); + when(this.restServiceMock.get(constants.getSchoolsPaginatedFromInstituteApiUrl(), PaginatedResponse.class, WebClient.builder().build())) + .thenReturn(response); + + assertNotNull(response); + assertDoesNotThrow(() -> schoolService.loadSchoolDetailsFromInstituteApiIntoRedisCacheAsync()); } @Test @@ -517,6 +647,99 @@ void testGetSchoolBySchoolId() { assertEquals("1234567", result.get().getMincode()); } + @Test + void testUpdateSchoolCache_BySchoolDetail() { + String schoolId = "1"; + + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId(schoolId); + schoolDetail.setDistrictId("DistID"); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); + schoolDetail.setDisplayName("Stitó:s Lá:lém Totí:lt Elementary"); + schoolDetail.setDisplayNameNoSpecialChars("Stitos Lalem Totilt Elementary"); + + SchoolDetailEntity schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId(schoolId); + schoolDetailEntity.setDistrictId("DistID"); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + schoolDetailEntity.setDisplayName("Stitó:s Lá:lém Totí:lt Elementary"); + schoolDetailEntity.setDisplayNameNoSpecialChars("Stitos Lalem Totilt Elementary"); + + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntity)).thenReturn(schoolDetail); + when(this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolId), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity); + + SchoolDetail result = schoolService.getSchoolDetailByIdFromInstituteApi(schoolId); + assertEquals(schoolDetail, result); + assertDoesNotThrow(() -> schoolService.updateSchoolCache(schoolDetail)); + } + + @Test + void testUpdateSchoolCache_BySchoolId() { + String schoolId = "1"; + + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId(schoolId); + schoolDetail.setDistrictId("DistID"); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); + schoolDetail.setDisplayName("Stitó:s Lá:lém Totí:lt Elementary"); + schoolDetail.setDisplayNameNoSpecialChars("Stitos Lalem Totilt Elementary"); + + SchoolDetailEntity schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId(schoolId); + schoolDetailEntity.setDistrictId("DistID"); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + schoolDetailEntity.setDisplayName("Stitó:s Lá:lém Totí:lt Elementary"); + schoolDetailEntity.setDisplayNameNoSpecialChars("Stitos Lalem Totilt Elementary"); + + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntity)).thenReturn(schoolDetail); + when(this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolId), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity); + + SchoolDetail result = schoolService.getSchoolDetailByIdFromInstituteApi(schoolId); + assertEquals(schoolDetail, result); + assertDoesNotThrow(() -> schoolService.updateSchoolCache(schoolId)); + } + + @Test + void testUpdateSchoolCache_BySchoolIds() { + String schoolId = "1"; + + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId(schoolId); + schoolDetail.setDistrictId("DistID"); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); + schoolDetail.setDisplayName("Stitó:s Lá:lém Totí:lt Elementary"); + schoolDetail.setDisplayNameNoSpecialChars("Stitos Lalem Totilt Elementary"); + + SchoolDetailEntity schoolDetailEntity = new SchoolDetailEntity(); + schoolDetailEntity.setSchoolId(schoolId); + schoolDetailEntity.setDistrictId("DistID"); + schoolDetailEntity.setSchoolNumber("12345"); + schoolDetailEntity.setSchoolCategoryCode("SCC"); + schoolDetailEntity.setEmail("abc@xyz.ca"); + schoolDetailEntity.setDisplayName("Stitó:s Lá:lém Totí:lt Elementary"); + schoolDetailEntity.setDisplayNameNoSpecialChars("Stitos Lalem Totilt Elementary"); + + when(this.schoolDetailTransformer.transformToDTO(schoolDetailEntity)).thenReturn(schoolDetail); + when(this.restServiceMock.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolId), + SchoolDetailEntity.class, instWebClient)).thenReturn(schoolDetailEntity); + + SchoolDetail result = schoolService.getSchoolDetailByIdFromInstituteApi(schoolId); + assertEquals(schoolDetail, result); + assertDoesNotThrow(() -> schoolService.updateSchoolCache(List.of(schoolId))); + } + @Test void testGetSchoolBySchoolId_NotFound() { UUID schoolId = UUID.randomUUID(); diff --git a/api/src/test/resources/application.yaml b/api/src/test/resources/application.yaml index 0c04367a..dcc12f6d 100644 --- a/api/src/test/resources/application.yaml +++ b/api/src/test/resources/application.yaml @@ -144,6 +144,8 @@ endpoint: institute-api: get-all-schools: url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/school + get-schools-paginated: + url: https://institute-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/school/paginated get-school-details-by-id: url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/institute/school/%s get-all-districts: @@ -164,4 +166,4 @@ endpoint: # other properties props: - school-cache-expiry-in-mins: 10 \ No newline at end of file + school-cache-expiry-in-mins: 10