diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/cache/CacheInitializer.java b/api/src/main/java/ca/bc/gov/educ/api/trax/cache/CacheInitializer.java index f99ab607..7bae9c47 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/cache/CacheInitializer.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/cache/CacheInitializer.java @@ -23,24 +23,12 @@ public class CacheInitializer implements CommandLineRunner { @Override public void run(String... args) { - schoolService.loadSchoolsIntoRedisCache( - schoolService.getSchoolsFromInstituteApi() - ); - log.info("Institutes loaded into Redis"); - districtService.loadDistrictsIntoRedisCache( - districtService.getDistrictsFromInstituteApi() - ); - log.info("Districts loaded into Redis"); - - codeService.loadSchoolCategoryCodesIntoRedisCache( - codeService.getSchoolCategoryCodesFromInstituteApi() - ); - log.info("School Category Codes loaded into Redis"); - codeService.loadSchoolFundingGroupCodesIntoRedisCache( - codeService.getSchoolFundingGroupCodesFromInstituteApi() - ); - log.info("School Funding Group Codes loaded into Redis"); + schoolService.initializeSchoolCache(false); + districtService.initializeDistrictCache(false); + codeService.initializeSchoolCategoryCodeCache(false); + codeService.initializeSchoolFundingGroupCodeCache(false); + schoolService.initializeSchoolDetailCache(false); log.info("Redis Cache initialized!"); } 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 0562be1e..9202f87c 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 @@ -16,9 +16,7 @@ import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder; +import org.springframework.security.oauth2.client.*; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; @@ -66,7 +64,23 @@ public WebClient getTraxClientWebClient(OAuth2AuthorizedClientManager authorized .apply(filter.oauth2Configuration()) .build(); } - @Bean + + + @Bean("instituteWebClient") + public WebClient getInstituteWebClient(OAuth2AuthorizedClientManager authorizedClientManager) { + ServletOAuth2AuthorizedClientExchangeFilterFunction filter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); + filter.setDefaultClientRegistrationId("institute-web-client"); + return WebClient.builder() + .exchangeStrategies(ExchangeStrategies + .builder() + .codecs(codecs -> codecs + .defaultCodecs() + .maxInMemorySize(50 * 1024 * 1024)) + .build()) + .apply(filter.oauth2Configuration()) + .build(); + } + /*@Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { @@ -76,9 +90,27 @@ public OAuth2AuthorizedClientManager authorizedClientManager( DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + return authorizedClientManager; + }*/ + + @Bean + public OAuth2AuthorizedClientManager authorizedClientManager( + ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientService clientService) { + + OAuth2AuthorizedClientProvider authorizedClientProvider = + OAuth2AuthorizedClientProviderBuilder.builder() + .clientCredentials() + .build(); + AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = + new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, clientService); + authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + return authorizedClientManager; } + + @Bean public WebClient webClient() { //extend buffer to 50MB diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/config/RedisConfig.java b/api/src/main/java/ca/bc/gov/educ/api/trax/config/RedisConfig.java index f086b3ff..e031932c 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/config/RedisConfig.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/config/RedisConfig.java @@ -4,6 +4,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisClusterConfiguration; +import org.springframework.data.redis.connection.RedisClusterNode; +import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @@ -11,6 +14,7 @@ import org.springframework.data.redis.serializer.GenericToStringSerializer; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; +import redis.clients.jedis.Jedis; @Configuration @EnableRedisRepositories("ca.bc.gov.educ.api.trax.repository.redis") @@ -22,6 +26,15 @@ public JedisConnectionFactory jedisConnectionFactory() { RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName(constants.getRedisUrl()); redisStandaloneConfiguration.setPort(Integer.parseInt(constants.getRedisPort())); + redisStandaloneConfiguration.setPassword(constants.getRedisSecret()); + + //Cluster Configuration + RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(); + RedisNode node0 = new RedisNode(constants.getRedisUrl(), Integer.parseInt(constants.getRedisPort())); + redisClusterConfiguration.addClusterNode(node0); + + RedisClusterNode rcn = new RedisClusterNode(constants.getRedisUrl(), Integer.parseInt(constants.getRedisPort())); + return new JedisConnectionFactory(redisStandaloneConfiguration); } @@ -38,4 +51,5 @@ public RedisTemplate redisTemplate() { return template; } + } 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 new file mode 100644 index 00000000..fc1dd522 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/CacheKey.java @@ -0,0 +1,5 @@ +package ca.bc.gov.educ.api.trax.constant; + +public enum CacheKey { + 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/constant/CacheStatus.java b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/CacheStatus.java new file mode 100644 index 00000000..299dcdb3 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/constant/CacheStatus.java @@ -0,0 +1,5 @@ +package ca.bc.gov.educ.api.trax.constant; + +public enum CacheStatus { + LOADING, READY +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/CodeController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/CodeController.java index ca9bb137..4b7e2670 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/CodeController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/CodeController.java @@ -14,8 +14,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -36,10 +34,6 @@ public class CodeController { @Autowired CodeService codeService; - - @Autowired - GradValidation validation; - @Autowired ResponseHelper response; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/DistrictController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/DistrictController.java index 23ccc07d..4fe63774 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/DistrictController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/DistrictController.java @@ -28,10 +28,6 @@ public class DistrictController { @Autowired DistrictService districtService; - - @Autowired - GradValidation validation; - @Autowired ResponseHelper response; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java index 7bd0a6f7..160f155a 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/controller/v2/SchoolController.java @@ -1,6 +1,7 @@ package ca.bc.gov.educ.api.trax.controller.v2; 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.service.institute.SchoolService; import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; import ca.bc.gov.educ.api.trax.util.GradValidation; @@ -22,7 +23,7 @@ @CrossOrigin @RestController("SchoolControllerV2") @Slf4j -@OpenAPIDefinition(info = @Info(title = "API for School Data.", description = "This Read API is for Reading school data.", version = "1"), +@OpenAPIDefinition(info = @Info(title = "API for School Data.", description = "This Read API is for Reading school data from Redis Cache.", version = "2"), security = {@SecurityRequirement(name = "OAUTH2", scopes = {"READ_GRAD_SCHOOL_DATA"})}) public class SchoolController { @@ -39,11 +40,20 @@ public SchoolController(SchoolService schoolService, GradValidation validation, @GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_URL_MAPPING_V2) @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) - @Operation(summary = "Find All Schools", description = "Get All Schools", tags = { "School" }) + @Operation(summary = "Find All Schools from Cache", description = "Get All Schools from Cache", tags = { "School" }) @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) public List getAllSchools() { log.debug("getAllSchools : "); - return schoolService.getSchoolsFromInstituteApi(); + return schoolService.getSchoolsFromRedisCache(); + } + + @GetMapping(EducGradTraxApiConstants.GRAD_SCHOOL_DETAIL_URL_MAPPING_V2) + @PreAuthorize(PermissionsConstants.READ_SCHOOL_DATA) + @Operation(summary = "Find All School details from Cache", description = "Get All School details from Cache", tags = { "School" }) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) + public List getAllSchoolDetails() { + log.debug("getAllSchoolDetails : "); + return schoolService.getSchoolDetailsFromRedisCache(); } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/exception/ServiceException.java b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/ServiceException.java new file mode 100644 index 00000000..af596043 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/exception/ServiceException.java @@ -0,0 +1,39 @@ +package ca.bc.gov.educ.api.trax.exception; + +import lombok.Data; + +@Data +public class ServiceException extends RuntimeException { + + private int statusCode; + + public ServiceException() { + super(); + } + + public ServiceException(String message) { + super(message); + } + + public ServiceException(String message, Throwable cause) { + super(message, cause); + } + + public ServiceException(Throwable cause) { + super(cause); + } + + protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public ServiceException(String message, int value) { + super(message); + this.statusCode = value; + } + + public ServiceException(String s, int value, Exception e) { + super(s, e); + this.statusCode = value; + } +} \ No newline at end of file diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/SchoolDetail.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/SchoolDetail.java new file mode 100644 index 00000000..4212fc69 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/dto/institute/SchoolDetail.java @@ -0,0 +1,50 @@ +package ca.bc.gov.educ.api.trax.model.dto.institute; + +import ca.bc.gov.educ.api.trax.model.dto.BaseModel; +import ca.bc.gov.educ.api.trax.model.dto.SchoolContact; +import ca.bc.gov.educ.api.trax.model.entity.institute.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.index.Indexed; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@Component("SchoolDetail") +public class SchoolDetail extends BaseModel { + + private String schoolId; + private String districtId; + private String mincode; + private String independentAuthorityId; + private String schoolNumber; + private String faxNumber; + private String phoneNumber; + private String email; + private String website; + private String displayName; + private String displayNameNoSpecialChars; + private String schoolReportingRequirementCode; + private String schoolOrganizationCode; + private String schoolCategoryCode; + private String facilityTypeCode; + private String openedDate; + private String closedDate; + private boolean canIssueTranscripts; + private boolean canIssueCertificates; + private String createUser; + private String updateUser; + private String createDate; + private String updateDate; + List contacts; + List addresses; + List notes; + List grades; + List schoolFundingGroups; + List neighborhoodLearnings; + List schoolMoves; + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolDetailEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolDetailEntity.java index f0edc744..e319c61a 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolDetailEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolDetailEntity.java @@ -21,15 +21,12 @@ public class SchoolDetailEntity { private String districtId; @Indexed private String mincode; - @Indexed private String independentAuthorityId; - @Indexed private String schoolNumber; private String faxNumber; private String phoneNumber; private String email; private String website; - @Indexed private String displayName; private String displayNameNoSpecialChars; private String schoolReportingRequirementCode; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolEntity.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolEntity.java index a8a12f50..16ce9d58 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolEntity.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/entity/institute/SchoolEntity.java @@ -17,15 +17,12 @@ public class SchoolEntity { private String districtId; @Indexed private String mincode; - @Indexed private String independentAuthorityId; - @Indexed private String schoolNumber; private String faxNumber; private String phoneNumber; private String email; private String website; - @Indexed private String displayName; private String displayNameNoSpecialChars; private String schoolReportingRequirementCode; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/DistrictTransformer.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/DistrictTransformer.java index 83e5d44c..f3f49056 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/DistrictTransformer.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/DistrictTransformer.java @@ -2,6 +2,7 @@ import ca.bc.gov.educ.api.trax.model.dto.institute.District; import ca.bc.gov.educ.api.trax.model.entity.institute.DistrictEntity; +import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolDetailEntity; import org.modelmapper.ModelMapper; import org.modelmapper.TypeToken; import org.springframework.beans.factory.annotation.Autowired; @@ -9,6 +10,7 @@ import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Component("InstituteDistrictTransformer") public class DistrictTransformer { @@ -36,7 +38,11 @@ public DistrictEntity transformToEntity(District district) { return modelMapper.map(district, DistrictEntity.class); } - public List transformToEntity (Iterable districts ) { - return modelMapper.map(districts, new TypeToken>(){}.getType()); + public List transformToEntity (List districts ) { + if (districts == null) return null; + return districts + .stream() + .map(district -> modelMapper.map(district, DistrictEntity.class)) + .collect(Collectors.toList()); } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolCategoryCodeTransformer.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolCategoryCodeTransformer.java index 85a3a206..a279a890 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolCategoryCodeTransformer.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolCategoryCodeTransformer.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Component("SchoolCategoryCodeTransformer") public class SchoolCategoryCodeTransformer { @@ -36,7 +37,11 @@ public SchoolCategoryCodeEntity transformToEntity(SchoolCategoryCode schoolCateg return modelMapper.map(schoolCategoryCode, SchoolCategoryCodeEntity.class); } - public List transformToEntity (Iterable schoolCategoryCodes ) { - return modelMapper.map(schoolCategoryCodes, new TypeToken>(){}.getType()); + public List transformToEntity (List schoolCategoryCodes ) { + if (schoolCategoryCodes == null) return null; + return schoolCategoryCodes + .stream() + .map(scc -> modelMapper.map(scc, SchoolCategoryCodeEntity.class)) + .collect(Collectors.toList()); } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolDetailTransformer.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolDetailTransformer.java new file mode 100644 index 00000000..071f1d98 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolDetailTransformer.java @@ -0,0 +1,49 @@ +package ca.bc.gov.educ.api.trax.model.transformer.institute; + +import ca.bc.gov.educ.api.trax.model.dto.institute.School; +import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; +import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolDetailEntity; +import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolEntity; +import org.modelmapper.ModelMapper; +import org.modelmapper.TypeToken; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Component("SchoolDetailTransformer") +public class SchoolDetailTransformer { + + @Autowired + ModelMapper modelMapper; + + public SchoolDetail transformToDTO (SchoolDetailEntity schoolDetailEntity) { + return modelMapper.map(schoolDetailEntity, SchoolDetail.class); + } + + public SchoolDetail transformToDTO (Optional schoolDetailEntity) { + if (schoolDetailEntity.isPresent()) { + SchoolDetailEntity sde = schoolDetailEntity.get(); + return modelMapper.map(sde, SchoolDetail.class); + } + return null; + } + + public List transformToDTO (Iterable schoolDetailEntities ) { + return modelMapper.map(schoolDetailEntities, new TypeToken>(){}.getType()); + } + + public SchoolDetailEntity transformToEntity(SchoolDetail schoolDetail) { + return modelMapper.map(schoolDetail, SchoolDetailEntity.class); + } + + public List transformToEntity (List schoolDetails ) { + if (schoolDetails == null) return null; + return schoolDetails + .stream() + .map(sd -> modelMapper.map(sd, SchoolDetailEntity.class)) + .collect(Collectors.toList()); + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolFundingGroupCodeTransformer.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolFundingGroupCodeTransformer.java index 899b3423..541bc8b8 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolFundingGroupCodeTransformer.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolFundingGroupCodeTransformer.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Component("SchoolFundingGroupCodeTransformer") public class SchoolFundingGroupCodeTransformer { @@ -38,7 +39,11 @@ public SchoolFundingGroupCodeEntity transformToEntity(SchoolFundingGroupCode sch return modelMapper.map(schoolFundingGroupCode, SchoolFundingGroupCodeEntity.class); } - public List transformToEntity (Iterable schoolFundingGroupCodes ) { - return modelMapper.map(schoolFundingGroupCodes, new TypeToken>(){}.getType()); + public List transformToEntity (List schoolFundingGroupCodes ) { + if (schoolFundingGroupCodes == null) return null; + return schoolFundingGroupCodes + .stream() + .map(sfgc -> modelMapper.map(sfgc, SchoolFundingGroupCodeEntity.class)) + .collect(Collectors.toList()); } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolTransformer.java b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolTransformer.java index 8978222b..8a07ad52 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolTransformer.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/model/transformer/institute/SchoolTransformer.java @@ -8,6 +8,7 @@ import org.springframework.stereotype.Component; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Component("InstituteSchoolTransformer") public class SchoolTransformer { @@ -21,8 +22,8 @@ public School transformToDTO (SchoolEntity schoolEntity) { public School transformToDTO (Optional schoolEntity ) { if (schoolEntity.isPresent()) { - SchoolEntity ie = schoolEntity.get(); - return modelMapper.map(ie, School.class); + SchoolEntity se = schoolEntity.get(); + return modelMapper.map(se, School.class); } return null; } @@ -35,7 +36,11 @@ public SchoolEntity transformToEntity(School school) { return modelMapper.map(school, SchoolEntity.class); } - public List transformToEntity (Iterable schools ) { - return modelMapper.map(schools, new TypeToken>(){}.getType()); + public List transformToEntity (List schools ) { + if (schools == null) return null; + return schools + .stream() + .map(school -> modelMapper.map(school, SchoolEntity.class)) + .collect(Collectors.toList()); } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/SchoolDetailRedisRepository.java b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/SchoolDetailRedisRepository.java new file mode 100644 index 00000000..9bc3cc86 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/repository/redis/SchoolDetailRedisRepository.java @@ -0,0 +1,11 @@ +package ca.bc.gov.educ.api.trax.repository.redis; + +import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolDetailEntity; +import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolEntity; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface SchoolDetailRedisRepository extends CrudRepository { + String HASH_KEY = "SchoolDetail"; +} \ No newline at end of file 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 new file mode 100644 index 00000000..36101de3 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/RESTService.java @@ -0,0 +1,162 @@ +package ca.bc.gov.educ.api.trax.service; + + +import ca.bc.gov.educ.api.trax.exception.ServiceException; +import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; +import ca.bc.gov.educ.api.trax.util.ThreadLocalStateUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; + +import java.time.Duration; + +@Service +public class RESTService { + + @Autowired + private WebClient webClient; + + private static final String ERROR_5xx = "5xx error."; + private static final String SERVICE_FAILED_ERROR = "Service failed to process after max retries."; + + public RESTService() { + } + + /** + * Generic GET call out to services. Uses blocking webclient and will throw + * runtime exceptions. Will attempt retries if 5xx errors are encountered. + * You can catch Exception in calling method. + * + * NOTE: Soon to be deprecated in favour of calling get method without access token below. + * + * @param url the url you are calling + * @param clazz the return type you are expecting + * @param accessToken access token + * @return return type + * @param expected return type + */ + public T get(String url, Class clazz, String accessToken) { + T obj; + try { + obj = webClient + .get() + .uri(url) + .headers(h -> { h.setBearerAuth(accessToken); h.set(EducGradTraxApiConstants.CORRELATION_ID, ThreadLocalStateUtil.getCorrelationID()); }) + .retrieve() + // if 5xx errors, throw Service error + .onStatus(HttpStatusCode::is5xxServerError, + clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, ERROR_5xx), 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()), HttpStatus.SERVICE_UNAVAILABLE.value(), e); + } + return obj; + } + + public T get(String url, Class clazz, WebClient webClient) { + T obj; + if (webClient == null) + webClient = this.webClient; + try { + obj = webClient + .get() + .uri(url) + .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, ERROR_5xx), 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()), HttpStatus.SERVICE_UNAVAILABLE.value(), e); + } + return obj; + } + + /** + * NOTE: Soon to be deprecated in favour of calling get method without access token below. + * @param url + * @param body + * @param clazz + * @param accessToken + * @return + * @param + */ + public T post(String url, Object body, Class clazz, String accessToken) { + T obj; + try { + obj = webClient.post() + .uri(url) + .headers(h -> { h.setBearerAuth(accessToken); h.set(EducGradTraxApiConstants.CORRELATION_ID, ThreadLocalStateUtil.getCorrelationID()); }) + .body(BodyInserters.fromValue(body)) + .retrieve() + .onStatus(HttpStatusCode::is5xxServerError, + clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, ERROR_5xx), clientResponse.statusCode().value()))) + .bodyToMono(clazz) + .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) { + throw new ServiceException(getErrorMessage(url, e.getLocalizedMessage()), HttpStatus.SERVICE_UNAVAILABLE.value(), e); + } + return obj; + } + + public T post(String url, Object body, Class clazz, WebClient webClient) { + T obj; + if (webClient == null) + webClient = this.webClient; + try { + obj = webClient.post() + .uri(url) + .headers(h -> { h.set(EducGradTraxApiConstants.CORRELATION_ID, ThreadLocalStateUtil.getCorrelationID()); }) + .body(BodyInserters.fromValue(body)) + .retrieve() + .onStatus(HttpStatusCode::is5xxServerError, + clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, ERROR_5xx), clientResponse.statusCode().value()))) + .bodyToMono(clazz) + .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) { + throw new ServiceException(getErrorMessage(url, e.getLocalizedMessage()), HttpStatus.SERVICE_UNAVAILABLE.value(), e); + } + return obj; + } + + private String getErrorMessage(String url, String errorMessage) { + return "Service failed to process at url: " + url + " due to: " + errorMessage; + } + + +} 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 37b2be65..8c376ab5 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,5 +1,6 @@ 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.institute.SchoolCategoryCode; import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolFundingGroupCode; import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolCategoryCodeEntity; @@ -8,11 +9,11 @@ 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 lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.ParameterizedTypeReference; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; @@ -26,6 +27,7 @@ public class CodeService { @Autowired private EducGradTraxApiConstants constants; @Autowired + @Qualifier("instituteWebClient") private WebClient webClient; @Autowired SchoolCategoryCodeRedisRepository schoolCategoryCodeRedisRepository; @@ -36,26 +38,16 @@ public class CodeService { @Autowired SchoolFundingGroupCodeTransformer schoolFundingGroupCodeTransformer; @Autowired - private RestUtils restUtils; + ServiceHelper serviceHelper; + @Autowired + RESTService restService; public List getSchoolCategoryCodesFromInstituteApi() { try { log.debug("****Before Calling Institute API"); - List schoolCategoryCodes = - webClient.get() - .uri(constants.getAllSchoolCategoryCodesFromInstituteApiUrl()) - .headers(h -> { - h.setBearerAuth(restUtils.getTokenResponseObject( - constants.getInstituteClientId(), - constants.getInstituteClientSecret() - ).getAccess_token()); - }) - .retrieve() - .bodyToMono(new ParameterizedTypeReference>() { - }).block(); - assert schoolCategoryCodes != null; - log.debug("# of School Category Codes: " + schoolCategoryCodes.size()); - return schoolCategoryCodeTransformer.transformToDTO(schoolCategoryCodes); + List response = this.restService.get(constants.getAllSchoolCategoryCodesFromInstituteApiUrl(), + List.class, webClient); + return schoolCategoryCodeTransformer.transformToDTO(response); } catch (WebClientResponseException e) { log.warn(String.format("Error getting School Category Codes: %s", e.getMessage())); } catch (Exception e) { @@ -67,26 +59,24 @@ public List getSchoolCategoryCodesFromInstituteApi() { public void loadSchoolCategoryCodesIntoRedisCache(List schoolCategoryCodes) { schoolCategoryCodeRedisRepository .saveAll(schoolCategoryCodeTransformer.transformToEntity(schoolCategoryCodes)); + log.info(String.format("%s School Category Codes Loaded into cache.", schoolCategoryCodes.size())); + } + + public List getSchoolCategoryCodesFromRedisCache() { + log.debug("**** Getting school category codes from Redis Cache."); + return schoolCategoryCodeTransformer.transformToDTO(schoolCategoryCodeRedisRepository.findAll()); + } + + public void initializeSchoolCategoryCodeCache(boolean force) { + serviceHelper.initializeCache(force, CacheKey.SCHOOL_CATEGORY_CODE_CACHE, this); } public List getSchoolFundingGroupCodesFromInstituteApi() { try { log.debug("****Before Calling Institute API"); - List schoolFundingGroupCodes; - schoolFundingGroupCodes = webClient.get() - .uri(constants.getAllSchoolFundingGroupCodesFromInstituteApiUrl()) - .headers(h -> { - h.setBearerAuth(restUtils.getTokenResponseObject( - constants.getInstituteClientId(), - constants.getInstituteClientSecret() - ).getAccess_token()); - }) - .retrieve() - .bodyToMono(new ParameterizedTypeReference>() { - }).block(); - //assert schoolFundingGroupCodes != null; - //log.debug("# of School Funding Group Codes: " + schoolFundingGroupCodes.size()); - return schoolFundingGroupCodeTransformer.transformToDTO(schoolFundingGroupCodes); + List response = this.restService.get(constants.getAllSchoolFundingGroupCodesFromInstituteApiUrl(), + List.class, webClient); + return schoolFundingGroupCodeTransformer.transformToDTO(response); } catch (WebClientResponseException e) { log.warn(String.format("Error getting School Funding Group Codes: %s", e.getMessage())); } catch (Exception e) { @@ -98,5 +88,15 @@ public List getSchoolFundingGroupCodesFromInstituteApi() public void loadSchoolFundingGroupCodesIntoRedisCache(List schoolFundingGroupCodes) { schoolFundingGroupCodeRedisRepository .saveAll(schoolFundingGroupCodeTransformer.transformToEntity(schoolFundingGroupCodes)); + log.info(String.format("%s School Funding Group Codes Loaded into cache.", schoolFundingGroupCodes.size())); + } + + public List getSchoolFundingGroupCodesFromRedisCache() { + log.debug("**** Getting school funding group codes from Redis Cache."); + return schoolFundingGroupCodeTransformer.transformToDTO(schoolFundingGroupCodeRedisRepository.findAll()); + } + + public void initializeSchoolFundingGroupCodeCache(boolean force) { + serviceHelper.initializeCache(force, CacheKey.SCHOOL_FUNDING_GROUP_CODE_CACHE, this); } } 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 ca5b118c..246b0200 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/DistrictService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/DistrictService.java @@ -1,17 +1,19 @@ 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.institute.District; 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.RestUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.ParameterizedTypeReference; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; + import java.util.List; @Slf4j @@ -21,30 +23,23 @@ public class DistrictService { @Autowired private EducGradTraxApiConstants constants; @Autowired - private RestUtils restUtils; - @Autowired + @Qualifier("instituteWebClient") private WebClient webClient; @Autowired DistrictRedisRepository districtRedisRepository; @Autowired DistrictTransformer districtTransformer; + @Autowired + ServiceHelper serviceHelper; + @Autowired + RESTService restService; public List getDistrictsFromInstituteApi() { try { log.debug("****Before Calling Institute API"); - List districts = - webClient.get() - .uri(constants.getAllDistrictsFromInstituteApiUrl()) - .headers(h -> h.setBearerAuth(restUtils.getTokenResponseObject( - constants.getInstituteClientId(), - constants.getInstituteClientSecret() - ).getAccess_token())) - .retrieve() - .bodyToMono(new ParameterizedTypeReference>() { - }).block(); - //assert districts != null; - //log.debug("# of Districts: " + districts.size()); - return districtTransformer.transformToDTO(districts); + List response = this.restService.get(constants.getAllDistrictsFromInstituteApiUrl(), + List.class, webClient); + return districtTransformer.transformToDTO(response); } catch (WebClientResponseException e) { log.warn(String.format("Error getting Common School List: %s", e.getMessage())); } catch (Exception e) { @@ -56,6 +51,15 @@ public List getDistrictsFromInstituteApi() { public void loadDistrictsIntoRedisCache(List districts) { districtRedisRepository .saveAll(districtTransformer.transformToEntity(districts)); + log.info(String.format("%s Districts Loaded into cache.", districts.size())); } + public List getDistrictsFromRedisCache() { + log.debug("**** Getting districts from Redis Cache."); + return districtTransformer.transformToDTO(districtRedisRepository.findAll()); + } + + public void initializeDistrictCache(boolean force) { + serviceHelper.initializeCache(force, CacheKey.DISTRICT_CACHE, this); + } } 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 b1f12857..6d414f1c 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/SchoolService.java @@ -1,18 +1,24 @@ 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.institute.School; +import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolEntity; +import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolDetailEntity; +import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolDetailTransformer; import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolTransformer; +import ca.bc.gov.educ.api.trax.repository.redis.SchoolDetailRedisRepository; import ca.bc.gov.educ.api.trax.repository.redis.SchoolRedisRepository; +import ca.bc.gov.educ.api.trax.service.RESTService; import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; -import ca.bc.gov.educ.api.trax.util.RestUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.ParameterizedTypeReference; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; +import java.util.ArrayList; import java.util.List; @Slf4j @@ -21,40 +27,32 @@ public class SchoolService { @Autowired private EducGradTraxApiConstants constants; - @Autowired + @Qualifier("instituteWebClient") private WebClient webClient; - @Autowired SchoolRedisRepository schoolRedisRepository; - + @Autowired + SchoolDetailRedisRepository schoolDetailRedisRepository; @Autowired SchoolTransformer schoolTransformer; - @Autowired - private RestUtils restUtils; + SchoolDetailTransformer schoolDetailTransformer; + @Autowired + ServiceHelper serviceHelper; + @Autowired + RESTService restService; public List getSchoolsFromInstituteApi() { try { log.debug("****Before Calling Institute API"); - List schools; - schools = webClient.get() - .uri(constants.getAllSchoolsFromInstituteApiUrl()) - .headers(h -> { - h.setBearerAuth(restUtils.getTokenResponseObject( - constants.getInstituteClientId(), - constants.getInstituteClientSecret() - ).getAccess_token()); - }) - .retrieve() - .bodyToMono(new ParameterizedTypeReference>(){}).block(); - //assert schools != null; - //log.debug("# of Schools: " + schools.size()); - return schoolTransformer.transformToDTO(schools); + List response = this.restService.get(constants.getAllSchoolsFromInstituteApiUrl(), + List.class, webClient); + return schoolTransformer.transformToDTO(response); } catch (WebClientResponseException e) { log.warn(String.format("Error getting Common School List: %s", e.getMessage())); } catch (Exception e) { - log.error(String.format("Error while calling school-api: %s", e.getMessage())); + log.error(String.format("Error getting data from Institute api: %s", e.getMessage())); } return null; } @@ -62,57 +60,59 @@ public List getSchoolsFromInstituteApi() { public void loadSchoolsIntoRedisCache(List schools) { schoolRedisRepository .saveAll(schoolTransformer.transformToEntity(schools)); + log.info(String.format("%s Schools Loaded into cache.", schools.size())); } - /*public SchoolDetail getCommonSchoolDetailById(String schoolId, String accessToken) { + public List getSchoolsFromRedisCache() { + log.debug("**** Getting schools from Redis Cache."); + return schoolTransformer.transformToDTO(schoolRedisRepository.findAll()); + } + + public void initializeSchoolCache(boolean force) { + serviceHelper.initializeCache(force, CacheKey.SCHOOL_CACHE, this); + } + + public SchoolDetail getSchoolDetailByIdFromInstituteApi(String schoolId) { try { - return webClient.get().uri( - String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolId)) - .headers(h -> { - h.setBearerAuth(accessToken); - }) - .retrieve().bodyToMono(SchoolDetail.class).block(); + log.debug("****Before Calling Institute API"); + SchoolDetailEntity sde = this.restService.get(String.format(constants.getSchoolDetailsByIdFromInstituteApiUrl(), schoolId), + SchoolDetailEntity.class, webClient); + return schoolDetailTransformer.transformToDTO(sde); } catch (WebClientResponseException e) { - logger.warn("Error getting Common School Details"); + log.warn("Error getting School Details"); } catch (Exception e) { - logger.error(String.format("Error while calling school-api: %s", e.getMessage())); + log.error(String.format("Error while calling Institute api: %s", e.getMessage())); } return null; - }*/ + } - /*public List getAllSchoolDetails() { + public List getSchoolDetailsFromInstituteApi() { - String accessToken = getAccessToken(); - List schools = getCommonSchools(accessToken); + List schools = getSchoolsFromRedisCache(); List schoolDetails = new ArrayList(); - Address address = new Address(); - int counter = 1; for (School s : schools) { SchoolDetail sd = new SchoolDetail(); - if (counter%100 == 0) - accessToken = getAccessToken(); - sd = getCommonSchoolDetailById(s.getSchoolId(), accessToken); - - address = null; - if (sd.getAddresses() == null || sd.getAddresses().isEmpty()) { - logger.debug("," + sd.getMincode() + "," + "," + "," + "," + "," + "," + "," + ","); - } else { - address = sd.getAddresses().get(0); - logger.debug("," + sd.getMincode() + "," - + sd.getAddresses().get(0).getAddressLine1() + "," - + sd.getAddresses().get(0).getAddressLine2() + "," - + sd.getAddresses().get(0).getCity() + "," - + sd.getAddresses().get(0).getProvinceCode() + "," - + sd.getAddresses().get(0).getCountryCode() + "," - + sd.getAddresses().get(0).getPostal() + "," - + sd.getAddresses().get(0).getAddressTypeCode() + "," - ); - } + sd = getSchoolDetailByIdFromInstituteApi(s.getSchoolId()); schoolDetails.add(sd); - counter++; } return schoolDetails; - }*/ + } + + public void loadSchoolDetailsIntoRedisCache(List schoolDetails) { + schoolDetailRedisRepository + .saveAll(schoolDetailTransformer.transformToEntity(schoolDetails)); + log.info(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()); + } + + public void initializeSchoolDetailCache(boolean force) { + serviceHelper.initializeCache(force, CacheKey.SCHOOL_DETAIL_CACHE, this); + } + } 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 new file mode 100644 index 00000000..72aa7196 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/service/institute/ServiceHelper.java @@ -0,0 +1,83 @@ +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 lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class ServiceHelper { + + @Autowired + RedisTemplate redisTemplate; + + public void initializeCache(boolean force, CacheKey cacheKey, T service) { + String cacheStatus = redisTemplate.opsForValue().get(cacheKey.name()); + cacheStatus = cacheStatus == null ? "" : cacheStatus; + if (CacheStatus.LOADING.name().compareToIgnoreCase(cacheStatus) == 0 + || CacheStatus.READY.name().compareToIgnoreCase(cacheStatus) == 0) { + log.info(String.format("%s status: %s", cacheKey, cacheStatus)); + if (force) { + log.info(String.format("Force Flag is true. Reloading %s...", cacheKey.name())); + loadCache(cacheKey, service); + } else { + log.info(String.format("Force Flag is false. Skipping %s reload", cacheKey.name())); + } + } else { + log.info(String.format("Loading %s...", cacheKey)); + loadCache(cacheKey, service); + } + } + + private void loadCache(CacheKey cacheKey, T service) { + redisTemplate.opsForValue().set(cacheKey.name(), CacheStatus.LOADING.name()); + loadDataIntoRedisCache(cacheKey, service); + redisTemplate.opsForValue().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; + } + case SCHOOL_FUNDING_GROUP_CODE_CACHE -> { + ((CodeService)service).loadSchoolFundingGroupCodesIntoRedisCache( + ((CodeService)service).getSchoolFundingGroupCodesFromInstituteApi() + ); + break; + } + case DISTRICT_CACHE -> { + ((DistrictService)service).loadDistrictsIntoRedisCache( + ((DistrictService)service).getDistrictsFromInstituteApi() + ); + break; + } + case SCHOOL_CACHE -> { + ((SchoolService)service).loadSchoolsIntoRedisCache( + ((SchoolService)service).getSchoolsFromInstituteApi() + ); + break; + } + case SCHOOL_DETAIL_CACHE -> { + ((SchoolService)service).loadSchoolDetailsIntoRedisCache( + ((SchoolService)service).getSchoolDetailsFromInstituteApi() + ); + break; + } + default -> { + log.info(String.format("Invalid Cache Key %s", cacheKey)); + } + } + } catch (Exception e) { + log.info(String.format("Exception thrown while loading cache %s. \n%s", cacheKey, e)); + } + } +} 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 6546dff2..a4ad024f 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 @@ -36,6 +36,7 @@ public class EducGradTraxApiConstants { public static final String GRAD_TRAX_CODE_URL_MAPPING_V2 = GRAD_TRAX_API_ROOT_MAPPING_V2 + "/code"; public static final String GRAD_SCHOOL_URL_MAPPING_V1 = GRAD_TRAX_API_ROOT_MAPPING_V1 + "/school"; public static final String GRAD_SCHOOL_URL_MAPPING_V2 = GRAD_TRAX_API_ROOT_MAPPING_V2 + "/school"; + public static final String GRAD_SCHOOL_DETAIL_URL_MAPPING_V2 = GRAD_TRAX_API_ROOT_MAPPING_V2 + "/school-detail"; public static final String GRAD_DISTRICT_URL_MAPPING_V1 = GRAD_TRAX_API_ROOT_MAPPING_V1 + "/district"; public static final String GRAD_DISTRICT_URL_MAPPING_V2 = GRAD_TRAX_API_ROOT_MAPPING_V2 + "/district"; public static final String GRAD_PSI_URL_MAPPING_V1 = GRAD_TRAX_API_ROOT_MAPPING_V1 + "/psi"; @@ -137,6 +138,9 @@ public class EducGradTraxApiConstants { @Value("${redis.port}") private String redisPort; + @Value("${redis.secret}") + private String redisSecret; + // Incremental Trax Update @Value("${trax.update.enabled}") private boolean traxUpdateEnabled; diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/JsonTransformer.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/JsonTransformer.java new file mode 100644 index 00000000..6ace850e --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/util/JsonTransformer.java @@ -0,0 +1,136 @@ +package ca.bc.gov.educ.api.trax.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.InputStream; + +@Component +public class JsonTransformer implements Transformer { + + private static final Logger log = LoggerFactory.getLogger(JsonTransformer.class); + + final ObjectMapper objectMapper; + + @Autowired + public JsonTransformer(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public Object unmarshall(byte[] input, Class clazz) { + Object result = null; + long start = System.currentTimeMillis(); + try { + result = objectMapper.readValue(input, clazz); + } catch (IOException e) { + log.error(e.getLocalizedMessage(), e); + } + log.debug("Time taken for unmarshalling response from bytes to {} is {} ms", clazz.getName(), (System.currentTimeMillis() - start)); + return result; + } + + public Object unmarshallWithWrapper(String input, Class clazz) { + final ObjectReader reader = objectMapper.readerFor(clazz); + Object result = null; + long start = System.currentTimeMillis(); + try { + result = reader + .with(DeserializationFeature.UNWRAP_ROOT_VALUE) + .with(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY) + .readValue(input); + } catch (IOException e) { + log.error(e.getLocalizedMessage(), e); + } + log.debug("Time taken for unmarshalling response from String to {} is {} ms", clazz.getSimpleName(), (System.currentTimeMillis() - start)); + return result; + } + + public String marshallWithWrapper(Object input) { + ObjectWriter prettyPrinter = objectMapper.writer(); + String result = null; + try { + result = prettyPrinter + .with(SerializationFeature.WRAP_ROOT_VALUE) + .writeValueAsString(input); + } catch (IOException e) { + log.error(e.getLocalizedMessage(), e); + } + + return result; + } + + @Override + public Object unmarshall(String input, Class clazz) { + Object result = null; + long start = System.currentTimeMillis(); + try { + result = objectMapper.readValue(input, clazz); + } catch (IOException e) { + log.error(e.getLocalizedMessage(), e); + } + log.debug("Time taken for unmarshalling response from String to {} is {} ms", clazz.getName(), (System.currentTimeMillis() - start)); + return result; + } + + @Override + public Object unmarshall(String input, TypeReference valueTypeRef) { + Object result = null; + long start = System.currentTimeMillis(); + try { + result = objectMapper.readValue(input, valueTypeRef); + } catch (IOException e) { + log.error(e.getLocalizedMessage(), e); + } + log.debug("Time taken for unmarshalling response from String to {} is {} ms", valueTypeRef.getType().getTypeName(), (System.currentTimeMillis() - start)); + return result; + } + + @Override + public Object unmarshall(InputStream input, Class clazz) { + Object result = null; + long start = System.currentTimeMillis(); + try { + result = objectMapper.readValue(input, clazz); + } catch (IOException e) { + log.error(e.getLocalizedMessage(), e); + } + log.debug("Time taken for unmarshalling response from stream to {} is {} ms", clazz.getName(), (System.currentTimeMillis() - start)); + return result; + } + + @Override + public String marshall(Object input) { + ObjectWriter prettyPrinter = objectMapper.writerWithDefaultPrettyPrinter(); + String result = null; + try { + result = prettyPrinter + .with(SerializationFeature.INDENT_OUTPUT) + .with(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .writeValueAsString(input); + } catch (IOException e) { + log.error(e.getLocalizedMessage(), e); + } + + return result; + } + + @Override + public String getAccept() { + return "application/json"; + } + + @Override + public String getContentType() { + return "application/json"; + } + + public T convertValue(Object fromValue, TypeReference toValueTypeRef) throws IllegalArgumentException { + return objectMapper.convertValue(fromValue, toValueTypeRef); + } +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/trax/util/Transformer.java b/api/src/main/java/ca/bc/gov/educ/api/trax/util/Transformer.java new file mode 100644 index 00000000..5525dd9f --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/trax/util/Transformer.java @@ -0,0 +1,23 @@ +package ca.bc.gov.educ.api.trax.util; + +import com.fasterxml.jackson.core.type.TypeReference; + +import java.io.InputStream; + +public interface Transformer { + + public Object unmarshall(byte[] input, Class clazz); + + public Object unmarshall(String input, Class clazz); + + public Object unmarshall(InputStream input, Class clazz); + + public Object unmarshall(String input, TypeReference valueTypeRef); + + public String marshall(Object input); + + public String getAccept(); + + public String getContentType(); + +} diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml index dd29d4d3..4ba48ea2 100644 --- a/api/src/main/resources/application.yaml +++ b/api/src/main/resources/application.yaml @@ -1,6 +1,4 @@ spring: - #main: - # allow-bean-definition-overriding: true jmx: enabled: false datasource: @@ -48,10 +46,17 @@ spring: client-id: ${TRAX_CLIENT_NAME} client-secret: ${TRAX_CLIENT_SECRET} authorization-grant-type: client_credentials + institute-web-client: + client-id: ${EDX_GRAD_CLIENT_NAME} + client-secret: ${EDX_GRAD_CLIENT_SECRET} + authorization-grant-type: client_credentials provider: traxclient: issuer-uri: ${TOKEN_ISSUER_URL} token-uri: ${TOKEN_ISSUER_URL}/protocol/openid-connect/token + institute-web-client: + issuer-uri: ${TOKEN_ISSUER_URL} + token-uri: ${TOKEN_ISSUER_URL}/protocol/openid-connect/token #Logging properties logging: @@ -129,6 +134,7 @@ nats: redis: url: ${REDIS_URL} port: ${REDIS_PORT} + secret: ${REDIS_SECRET} #Scheduler cron: diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/controller/SchoolControllerTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/controller/SchoolControllerTest.java index b5b3d962..0ad1211a 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/controller/SchoolControllerTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/controller/SchoolControllerTest.java @@ -2,6 +2,7 @@ import ca.bc.gov.educ.api.trax.model.dto.CommonSchool; import ca.bc.gov.educ.api.trax.model.dto.School; +import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; import ca.bc.gov.educ.api.trax.service.SchoolService; import ca.bc.gov.educ.api.trax.util.EducGradTraxApiConstants; import ca.bc.gov.educ.api.trax.util.ResponseHelper; @@ -33,6 +34,8 @@ public class SchoolControllerTest { @Mock private SchoolService schoolService; + @Mock + private ca.bc.gov.educ.api.trax.service.institute.SchoolService schoolServiceV2; @Autowired private EducGradTraxApiConstants constants; @@ -52,6 +55,9 @@ public class SchoolControllerTest { @InjectMocks private SchoolController schoolController; + @InjectMocks + private ca.bc.gov.educ.api.trax.controller.v2.SchoolController schoolControllerV2; + @Test public void testGetAllSchools() { final List gradSchoolList = new ArrayList<>(); @@ -145,4 +151,38 @@ private void mockCommonSchool(String minCode, String schoolName) { when(this.responseMock.bodyToMono(CommonSchool.class)).thenReturn(Mono.just(commonSchool)); } + + @Test + public void whenGetAllSchools_ReturnsListOfSchools() { + final List schools = new ArrayList<>(); + ca.bc.gov.educ.api.trax.model.dto.institute.School school = new ca.bc.gov.educ.api.trax.model.dto.institute.School(); + school.setSchoolId("1234567"); + school.setDistrictId("9876543"); + schools.add(school); + school = new ca.bc.gov.educ.api.trax.model.dto.institute.School(); + school.setSchoolId("1234567"); + school.setDistrictId("9876543"); + schools.add(school); + + Mockito.when(schoolServiceV2.getSchoolsFromRedisCache()).thenReturn(schools); + schoolControllerV2.getAllSchools(); + Mockito.verify(schoolServiceV2).getSchoolsFromRedisCache(); + } + + @Test + public void whenGetAllSchoolDetails_ReturnsListOfSchoolDetails() { + final List schoolDetails = new ArrayList<>(); + SchoolDetail schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("1234567"); + schoolDetail.setDistrictId("9876543"); + schoolDetails.add(schoolDetail); + schoolDetail = new SchoolDetail(); + schoolDetail.setSchoolId("1234567"); + schoolDetail.setDistrictId("9876543"); + schoolDetails.add(schoolDetail); + + Mockito.when(schoolServiceV2.getSchoolDetailsFromRedisCache()).thenReturn(schoolDetails); + schoolControllerV2.getAllSchoolDetails(); + Mockito.verify(schoolServiceV2).getSchoolDetailsFromRedisCache(); + } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java index 8a244580..24dbd242 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/trax/service/BaseReplicationServiceTest.java @@ -13,6 +13,7 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; @@ -39,6 +40,8 @@ public abstract class BaseReplicationServiceTest { @MockBean public OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository; + @MockBean + public OAuth2AuthorizedClientService oAuth2AuthorizedClientService; @Before public void resetState() { 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 9ad65608..bb08b8eb 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 @@ -1,5 +1,7 @@ package ca.bc.gov.educ.api.trax.service.institute; +import ca.bc.gov.educ.api.trax.constant.CacheKey; +import ca.bc.gov.educ.api.trax.constant.CacheStatus; import ca.bc.gov.educ.api.trax.messaging.NatsConnection; import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; @@ -8,8 +10,10 @@ 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.repository.GradCountryRepository; import ca.bc.gov.educ.api.trax.repository.GradProvinceRepository; +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.util.EducGradTraxApiConstants; import ca.bc.gov.educ.api.trax.util.RestUtils; @@ -25,6 +29,8 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; import org.springframework.http.HttpHeaders; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; @@ -36,14 +42,13 @@ 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.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @RunWith(SpringRunner.class) @SpringBootTest @@ -57,16 +62,21 @@ public class InstituteCodeServiceTest { @Autowired private CodeService codeService; @MockBean + private SchoolCategoryCodeRedisRepository schoolCategoryCodeRedisRepository; + @MockBean private SchoolFundingGroupCodeRedisRepository schoolFundingGroupCodeRedisRepository; @MockBean private GradCountryRepository gradCountryRepository; - @MockBean private GradProvinceRepository gradProvinceRepository; @MockBean @Qualifier("default") WebClient webClientMock; + @MockBean + RedisTemplate redisTemplateMock; + @Mock + ValueOperations valueOperationsMock; @Mock private WebClient.RequestHeadersSpec requestHeadersSpecMock; @Mock @@ -78,11 +88,15 @@ public class InstituteCodeServiceTest { @Mock private ResponseObj responseObjectMock; @Mock + private Mono> schoolCategoryCodesMock; + @Mock private Mono> schoolCategoryCodeEntitiesMock; @Mock private Mono> schoolFundingGroupCodeEntitiesMock; + @Mock + SchoolCategoryCodeTransformer schoolCategoryCodeTransformer; @MockBean - private RestUtils restUtilsMock; + private RestUtils restUtils; // NATS @MockBean @@ -108,7 +122,7 @@ public ClientRegistration findByRegistrationId(String registrationId) { } @Test - public void whenGetSchoolCategoryCodesFromInstituteApi_returnsListOfSchoolCategoryCodeEntity() { + public void whenGetSchoolCategoryCodesFromInstituteApi_returnsListOfSchoolCategoryCode() { List schoolCategoryCodes = new ArrayList<>(); SchoolCategoryCodeEntity scce = new SchoolCategoryCodeEntity(); @@ -121,17 +135,16 @@ public void whenGetSchoolCategoryCodesFromInstituteApi_returnsListOfSchoolCatego scce.setDisplayOrder("10"); schoolCategoryCodes.add(scce); - ResponseObj tokenObj = new ResponseObj(); - tokenObj.setAccess_token("123"); - + 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(this.restUtilsMock.getTokenResponseObject(anyString(), anyString())) - .thenReturn(tokenObj); - when(this.responseObjectMock.getAccess_token()) - .thenReturn("accessToken"); + when(requestHeadersSpecMock.headers(any(Consumer.class))) + .thenReturn(requestHeadersSpecMock); when(requestHeadersSpecMock.retrieve()) .thenReturn(responseSpecMock); when(this.responseSpecMock.bodyToMono(new ParameterizedTypeReference>(){})) @@ -144,7 +157,7 @@ public void whenGetSchoolCategoryCodesFromInstituteApi_returnsListOfSchoolCatego } @Test - public void whenGetSchoolFundingGroupCodesFromInstituteApi_returnsListOfSchoolFundingGroupCodeEntity() { + public void whenGetSchoolFundingGroupCodesFromInstituteApi_returnsListOfSchoolFundingGroupCode() { List schoolFundingGroupCodes = new ArrayList<>(); SchoolFundingGroupCodeEntity sfgc = new SchoolFundingGroupCodeEntity(); @@ -156,31 +169,26 @@ public void whenGetSchoolFundingGroupCodesFromInstituteApi_returnsListOfSchoolFu sfgc.setDisplayOrder("10"); schoolFundingGroupCodes.add(sfgc); - ResponseObj tokenObj = new ResponseObj(); - tokenObj.setAccess_token("123"); - - ParameterizedTypeReference> schoolFundingGroupCodeEntityType = - new ParameterizedTypeReference>() {}; - - when(webClientMock.get()) - .thenReturn(this.requestHeadersUriSpecMock); - when(requestHeadersUriSpecMock.uri(anyString())) - .thenReturn(this.requestHeadersSpecMock); - when(this.restUtilsMock.getTokenResponseObject(anyString(), anyString())) - .thenReturn(tokenObj); + when(this.restUtils.getTokenResponseObject(anyString(), anyString())) + .thenReturn(responseObjectMock); when(this.responseObjectMock.getAccess_token()) .thenReturn("accessToken"); - when(this.requestHeadersSpecMock.headers(any(Consumer.class))) - .thenReturn(this.requestHeadersSpecMock); + when(webClientMock.get()) + .thenReturn(requestHeadersUriSpecMock); + when(requestHeadersUriSpecMock.uri(anyString())) + .thenReturn(requestHeadersSpecMock); + when(requestHeadersSpecMock.headers(any(Consumer.class))) + .thenReturn(requestHeadersSpecMock); when(requestHeadersSpecMock.retrieve()) - .thenReturn(this.responseSpecMock); - when(this.responseSpecMock.bodyToMono(schoolFundingGroupCodeEntityType)) + .thenReturn(responseSpecMock); + when(this.responseSpecMock.bodyToMono(new ParameterizedTypeReference>(){})) .thenReturn(Mono.just(schoolFundingGroupCodes)); when(this.schoolFundingGroupCodeEntitiesMock.block()) .thenReturn(schoolFundingGroupCodes); List result = codeService.getSchoolFundingGroupCodesFromInstituteApi(); //assertThat(result).hasSize(1); + } @Test @@ -200,4 +208,232 @@ public void whenLoadSchoolFundingGroupCodesIntoRedisCache_DoesNotThrow() { .thenReturn(schoolFundingGroupCodeEntities); assertDoesNotThrow(() -> codeService.loadSchoolFundingGroupCodesIntoRedisCache(schoolFundingGroupCodes)); } + + @Test + public void whenGetSchoolCategoryCodesFromRedisCache_GetSchoolCategoryCodes() { + 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); + when(schoolCategoryCodeRedisRepository.findAll()).thenReturn(scces); + //assertTrue(codeService.getSchoolCategoryCodesFromRedisCache().size() == 2); + } + + @Test + public void whenInitializeSchoolCategoryCodeCache_WithLoadingAndFalse_DoNotForceLoad() { + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.SCHOOL_CATEGORY_CODE_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.LOADING)); + doNothing().when(valueOperationsMock).set(CacheKey.SCHOOL_CATEGORY_CODE_CACHE.name(), CacheStatus.LOADING.name()); + codeService.initializeSchoolCategoryCodeCache(false); + } + + @Test + public void whenInitializeSchoolCategoryCodeCache_WithReadyAndFalse_DoNotForceLoad() { + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.SCHOOL_CATEGORY_CODE_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.READY)); + doNothing().when(valueOperationsMock).set(CacheKey.SCHOOL_CATEGORY_CODE_CACHE.name(), CacheStatus.READY.name()); + codeService.initializeSchoolCategoryCodeCache(false); + } + + @Test + public void whenInitializeSchoolCategoryCodeCache_WithLoadingAndTrue_ThenForceLoad() { + + 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(webClientMock.get()) + .thenReturn(requestHeadersUriSpecMock); + when(requestHeadersUriSpecMock.uri(anyString())) + .thenReturn(requestHeadersSpecMock); + when(requestHeadersSpecMock.headers(any(Consumer.class))) + .thenReturn(requestHeadersSpecMock); + when(this.restUtils.getTokenResponseObject(anyString(), anyString())) + .thenReturn(responseObjectMock); + when(this.responseObjectMock.getAccess_token()) + .thenReturn("accessToken"); + when(requestHeadersSpecMock.retrieve()) + .thenReturn(responseSpecMock); + when(this.responseSpecMock.bodyToMono(new ParameterizedTypeReference>(){})) + .thenReturn(schoolCategoryCodeEntitiesMock); + when(this.schoolCategoryCodeEntitiesMock.block()) + .thenReturn(scces); + + when(this.restUtils.getTokenResponseObject(anyString(), anyString())) + .thenReturn(responseObjectMock); + when(this.responseObjectMock.getAccess_token()) + .thenReturn("accessToken"); + + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.SCHOOL_CATEGORY_CODE_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.LOADING)); + doNothing().when(valueOperationsMock).set(CacheKey.SCHOOL_CATEGORY_CODE_CACHE.name(), CacheStatus.LOADING.name()); + + CodeService codeServicemock = mock(CodeService.class); + when(codeServicemock.getSchoolCategoryCodesFromInstituteApi()).thenReturn(sccs); + doNothing().when(codeServicemock).loadSchoolCategoryCodesIntoRedisCache(sccs); + + codeService.initializeSchoolCategoryCodeCache(true); + //verify(codeServicemock).loadSchoolCategoryCodesIntoRedisCache(sccs); + } + + @Test + public void whenInitializeSchoolCategoryCodeCache_WithReadyAndTrue_ThenForceLoad() { + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.SCHOOL_CATEGORY_CODE_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.READY)); + doNothing().when(valueOperationsMock).set(CacheKey.SCHOOL_CATEGORY_CODE_CACHE.name(), CacheStatus.READY.name()); + codeService.initializeSchoolCategoryCodeCache(true); + } + + @Test + public void whenGetSchoolFundingGroupCodesFromRedisCache_GetSchoolFundingGroupCodes() { + 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); + //assertTrue(codeService.getSchoolCategoryCodesFromRedisCache().size() == 2); + } + + @Test + public void whenInitializeSchoolFundingGroupCodeCache_WithLoadingAndFalse_DoNotForceLoad() { + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.SCHOOL_FUNDING_GROUP_CODE_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.LOADING)); + doNothing().when(valueOperationsMock).set(CacheKey.SCHOOL_FUNDING_GROUP_CODE_CACHE.name(), CacheStatus.LOADING.name()); + codeService.initializeSchoolFundingGroupCodeCache(false); + } + + @Test + public void whenInitializeSchoolFundingGroupCodeCache_WithReadyAndFalse_DoNotForceLoad() { + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.SCHOOL_FUNDING_GROUP_CODE_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.READY)); + doNothing().when(valueOperationsMock).set(CacheKey.SCHOOL_FUNDING_GROUP_CODE_CACHE.name(), CacheStatus.READY.name()); + codeService.initializeSchoolFundingGroupCodeCache(false); + } + + @Test + public void whenInitializeSchoolFundingGroupCodeCache_WithLoadingAndTrue_ThenForceLoad() { + + SchoolFundingGroupCodeEntity sfgce = new SchoolFundingGroupCodeEntity(); + List sfgces = new ArrayList(); + sfgce.setSchoolFundingGroupCode("SCC1"); + sfgce.setLabel("SCC1-label"); + sfgces.add(sfgce); + sfgce = new SchoolFundingGroupCodeEntity(); + sfgce.setSchoolFundingGroupCode("SCC2"); + sfgce.setLabel("SCC2-label"); + sfgces.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"); + 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(webClientMock.get()) + .thenReturn(requestHeadersUriSpecMock); + when(requestHeadersUriSpecMock.uri(anyString())) + .thenReturn(requestHeadersSpecMock); + when(requestHeadersSpecMock.headers(any(Consumer.class))) + .thenReturn(requestHeadersSpecMock); + when(this.restUtils.getTokenResponseObject(anyString(), anyString())) + .thenReturn(responseObjectMock); + when(this.responseObjectMock.getAccess_token()) + .thenReturn("accessToken"); + when(requestHeadersSpecMock.retrieve()) + .thenReturn(responseSpecMock); + when(this.responseSpecMock.bodyToMono(new ParameterizedTypeReference>(){})) + .thenReturn(schoolFundingGroupCodeEntitiesMock); + when(this.schoolFundingGroupCodeEntitiesMock.block()) + .thenReturn(sfgces); + + when(this.restUtils.getTokenResponseObject(anyString(), anyString())) + .thenReturn(responseObjectMock); + when(this.responseObjectMock.getAccess_token()) + .thenReturn("accessToken"); + + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.SCHOOL_CATEGORY_CODE_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.LOADING)); + doNothing().when(valueOperationsMock).set(CacheKey.SCHOOL_CATEGORY_CODE_CACHE.name(), CacheStatus.LOADING.name()); + + CodeService codeServicemock = mock(CodeService.class); + when(codeServicemock.getSchoolFundingGroupCodesFromInstituteApi()).thenReturn(sfgcs); + doNothing().when(codeServicemock).loadSchoolFundingGroupCodesIntoRedisCache(sfgcs); + + codeService.initializeSchoolFundingGroupCodeCache(true); + //verify(codeServicemock).loadSchoolCategoryCodesIntoRedisCache(sccs); + } + + @Test + public void whenInitializeSchoolFundingGroupCodeCache_WithReadyAndTrue_ThenForceLoad() { + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.SCHOOL_FUNDING_GROUP_CODE_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.READY)); + doNothing().when(valueOperationsMock).set(CacheKey.SCHOOL_FUNDING_GROUP_CODE_CACHE.name(), CacheStatus.READY.name()); + codeService.initializeSchoolFundingGroupCodeCache(true); + } } 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 28395d61..66b9220e 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 @@ -1,11 +1,14 @@ 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.messaging.NatsConnection; import ca.bc.gov.educ.api.trax.messaging.jetstream.Publisher; import ca.bc.gov.educ.api.trax.messaging.jetstream.Subscriber; import ca.bc.gov.educ.api.trax.model.dto.ResponseObj; import ca.bc.gov.educ.api.trax.model.dto.institute.District; import ca.bc.gov.educ.api.trax.model.dto.institute.DistrictContact; +import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolCategoryCode; import ca.bc.gov.educ.api.trax.model.entity.institute.DistrictContactEntity; import ca.bc.gov.educ.api.trax.model.entity.institute.DistrictEntity; import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolCategoryCodeEntity; @@ -28,6 +31,8 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; import org.springframework.http.HttpHeaders; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; @@ -45,7 +50,8 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doNothing; @RunWith(SpringRunner.class) @@ -65,6 +71,10 @@ public class InstituteDistrictServiceTest { @MockBean @Qualifier("default") WebClient webClientMock; + @MockBean + RedisTemplate redisTemplateMock; + @Mock + ValueOperations valueOperationsMock; @Mock private WebClient.RequestHeadersSpec requestHeadersSpecMock; @Mock @@ -80,8 +90,6 @@ public class InstituteDistrictServiceTest { @Mock private List districtsMock; @MockBean - private RestUtils restUtilsMock; - @MockBean private DistrictTransformer districtTransformerMock; // NATS @@ -93,6 +101,8 @@ public class InstituteDistrictServiceTest { @MockBean private Subscriber subscriber; + @MockBean + private RestUtils restUtils; @TestConfiguration static class TestConfigInstitute { @@ -120,26 +130,22 @@ public void whenGetDistrictsFromInstituteApi_returnsListOfDistricts() { districts.add(district); - ResponseObj tokenObj = new ResponseObj(); - tokenObj.setAccess_token("123"); - + 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(this.restUtilsMock.getTokenResponseObject(anyString(), anyString())) - .thenReturn(tokenObj); - when(this.responseObjectMock.getAccess_token()) - .thenReturn("AccessToken"); - when(this.requestHeadersSpecMock.retrieve()) + when(requestHeadersSpecMock.headers(any(Consumer.class))) + .thenReturn(requestHeadersSpecMock); + when(requestHeadersSpecMock.retrieve()) .thenReturn(responseSpecMock); when(this.responseSpecMock.bodyToMono(new ParameterizedTypeReference>(){})) .thenReturn(districtEntitiesMock); when(this.districtEntitiesMock.block()).thenReturn(districts); - when(this.districtTransformerMock.transformToDTO(districts)) - .thenReturn(districtsMock); - List result = districtService.getDistrictsFromInstituteApi(); } @@ -151,4 +157,94 @@ public void whenLoadDistrictsIntoRedisCache_DoesNotThrow() { .thenReturn(districtEntities); assertDoesNotThrow(() -> districtService.loadDistrictsIntoRedisCache(districts)); } + + @Test + public void whenInitializeDistrictCache_WithLoadingAndFalse_DoNotForceLoad() { + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.DISTRICT_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.LOADING)); + doNothing().when(valueOperationsMock).set(CacheKey.DISTRICT_CACHE.name(), CacheStatus.LOADING.name()); + districtService.initializeDistrictCache(false); + } + + @Test + public void whenInitializeDistrictCache_WithReadyAndFalse_DoNotForceLoad() { + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.DISTRICT_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.READY)); + doNothing().when(valueOperationsMock).set(CacheKey.DISTRICT_CACHE.name(), CacheStatus.READY.name()); + districtService.initializeDistrictCache(false); + } + + @Test + public void whenInitializeDistrictCache_WithLoadingAndTrue_ThenForceLoad() { + + DistrictEntity de = new DistrictEntity(); + List des = new ArrayList(); + de.setDistrictId("123"); + de.setDistrictNumber("456"); + des.add(de); + de = new DistrictEntity(); + de.setDistrictId("789"); + de.setDistrictNumber("012"); + des.add(de); + + District d = new District(); + List ds = new ArrayList(); + d.setDistrictId("123"); + d.setDistrictNumber("456"); + ds.add(d); + d = new District(); + d.setDistrictId("789"); + d.setDistrictNumber("012"); + ds.add(d); + + + when(webClientMock.get()) + .thenReturn(requestHeadersUriSpecMock); + when(requestHeadersUriSpecMock.uri(anyString())) + .thenReturn(requestHeadersSpecMock); + when(requestHeadersSpecMock.headers(any(Consumer.class))) + .thenReturn(requestHeadersSpecMock); + when(this.restUtils.getTokenResponseObject(anyString(), anyString())) + .thenReturn(responseObjectMock); + when(this.responseObjectMock.getAccess_token()) + .thenReturn("accessToken"); + when(requestHeadersSpecMock.retrieve()) + .thenReturn(responseSpecMock); + when(this.responseSpecMock.bodyToMono(new ParameterizedTypeReference>(){})) + .thenReturn(districtEntitiesMock); + when(this.districtEntitiesMock.block()) + .thenReturn(des); + + when(this.restUtils.getTokenResponseObject(anyString(), anyString())) + .thenReturn(responseObjectMock); + when(this.responseObjectMock.getAccess_token()) + .thenReturn("accessToken"); + + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.DISTRICT_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.LOADING)); + doNothing().when(valueOperationsMock).set(CacheKey.DISTRICT_CACHE.name(), CacheStatus.LOADING.name()); + + DistrictService districtServiceMock = mock(DistrictService.class); + when(districtServiceMock.getDistrictsFromInstituteApi()).thenReturn(ds); + doNothing().when(districtServiceMock).loadDistrictsIntoRedisCache(ds); + + districtService.initializeDistrictCache(true); + //verify(codeServicemock).loadSchoolCategoryCodesIntoRedisCache(sccs); + } + + @Test + public void whenInitializeDistrictCache_WithReadyAndTrue_ThenForceLoad() { + when(redisTemplateMock.opsForValue()) + .thenReturn(valueOperationsMock); + when(valueOperationsMock.get(CacheKey.DISTRICT_CACHE.name())) + .thenReturn(String.valueOf(CacheStatus.READY)); + doNothing().when(valueOperationsMock).set(CacheKey.DISTRICT_CACHE.name(), CacheStatus.READY.name()); + districtService.initializeDistrictCache(true); + } } 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 138305c5..d1a253b8 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 @@ -6,10 +6,10 @@ import ca.bc.gov.educ.api.trax.model.dto.ResponseObj; import ca.bc.gov.educ.api.trax.model.dto.institute.District; import ca.bc.gov.educ.api.trax.model.dto.institute.School; -import ca.bc.gov.educ.api.trax.model.entity.institute.DistrictContactEntity; -import ca.bc.gov.educ.api.trax.model.entity.institute.DistrictEntity; -import ca.bc.gov.educ.api.trax.model.entity.institute.SchoolEntity; +import ca.bc.gov.educ.api.trax.model.dto.institute.SchoolDetail; +import ca.bc.gov.educ.api.trax.model.entity.institute.*; import ca.bc.gov.educ.api.trax.model.transformer.institute.DistrictTransformer; +import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolDetailTransformer; import ca.bc.gov.educ.api.trax.model.transformer.institute.SchoolTransformer; import ca.bc.gov.educ.api.trax.repository.GradCountryRepository; import ca.bc.gov.educ.api.trax.repository.GradProvinceRepository; @@ -39,8 +39,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.function.Consumer; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @@ -74,11 +76,17 @@ public class InstituteSchoolServiceTest { @Mock private Mono> schoolEntitiesMock; @Mock + private Mono> schoolDetailEntitiesMock; + @Mock private List schoolsMock; + @Mock + private List schoolDetailsMock; @MockBean - private RestUtils restUtilsMock; + private RestUtils restUtils; @MockBean private SchoolTransformer schoolTransformerMock; + @MockBean + private SchoolDetailTransformer schoolDetailTransformerMock; // NATS @MockBean @@ -116,18 +124,17 @@ public void whenGetSchoolsFromInstituteApi_returnsListOfSchools() { schools.add(school); - ResponseObj tokenObj = new ResponseObj(); - tokenObj.setAccess_token("123"); - + 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(this.restUtilsMock.getTokenResponseObject(anyString(), anyString())) - .thenReturn(tokenObj); - when(this.responseObjectMock.getAccess_token()) - .thenReturn("AccessToken"); - when(this.requestHeadersSpecMock.retrieve()) + when(requestHeadersSpecMock.headers(any(Consumer.class))) + .thenReturn(requestHeadersSpecMock); + when(requestHeadersSpecMock.retrieve()) .thenReturn(responseSpecMock); when(this.responseSpecMock.bodyToMono(new ParameterizedTypeReference>(){})) .thenReturn(schoolEntitiesMock); @@ -136,6 +143,7 @@ public void whenGetSchoolsFromInstituteApi_returnsListOfSchools() { when(this.schoolTransformerMock.transformToDTO(schools)) .thenReturn(schoolsMock); + List result = schoolService.getSchoolsFromInstituteApi(); } @@ -147,4 +155,39 @@ public void whenLoadSchoolsIntoRedisCache_DoesNotThrow() { .thenReturn(schoolEntities); assertDoesNotThrow(() -> schoolService.loadSchoolsIntoRedisCache(schools)); } + + @Test + public void whenGetSchoolDetailssFromInstituteApi_returnsListOfSchoolDetails() { + List schoolDetails = new ArrayList<>(); + SchoolDetailEntity schoolDetail = new SchoolDetailEntity(); + + schoolDetail.setSchoolId("ID"); + schoolDetail.setDistrictId("DistID"); + schoolDetail.setSchoolNumber("12345"); + schoolDetail.setSchoolCategoryCode("SCC"); + schoolDetail.setEmail("abc@xyz.ca"); + + schoolDetails.add(schoolDetail); + + when(this.restUtils.getTokenResponseObject(anyString(), anyString())) + .thenReturn(responseObjectMock); + when(this.responseObjectMock.getAccess_token()) + .thenReturn("accessToken"); + when(webClientMock.get()) + .thenReturn(requestHeadersUriSpecMock); + when(requestHeadersUriSpecMock.uri(anyString())) + .thenReturn(requestHeadersSpecMock); + when(requestHeadersSpecMock.headers(any(Consumer.class))) + .thenReturn(requestHeadersSpecMock); + when(requestHeadersSpecMock.retrieve()) + .thenReturn(responseSpecMock); + when(this.responseSpecMock.bodyToMono(new ParameterizedTypeReference>(){})) + .thenReturn(schoolDetailEntitiesMock); + when(this.schoolDetailEntitiesMock.block()).thenReturn(schoolDetails); + + when(this.schoolDetailTransformerMock.transformToDTO(schoolDetails)) + .thenReturn(schoolDetailsMock); + + List result = schoolService.getSchoolDetailsFromInstituteApi(); + } } diff --git a/api/src/test/resources/application.yaml b/api/src/test/resources/application.yaml index 16e0b2bc..ffad5705 100644 --- a/api/src/test/resources/application.yaml +++ b/api/src/test/resources/application.yaml @@ -75,7 +75,8 @@ nats: #Redis redis: url: '127.0.0.1' - port: '6379' + port: '54321' + secret: 'its-a-secret' #Scheduler cron: