From 95d0dcaf69a6175f8c49ce1747364e9f41c886db Mon Sep 17 00:00:00 2001 From: arybakov Date: Thu, 23 Nov 2023 13:20:53 -0700 Subject: [PATCH 1/4] Fix school reports performance --- api/pom.xml | 5 ++ .../config/EducGradBusinessApiConfig.java | 2 +- .../gradbusiness/config/TokenUtilsConfig.java | 24 +++++++ .../gradbusiness/model/dto/ResponseObj.java | 10 +++ .../model/dto/ResponseObjCache.java | 42 ++++++++++++ .../service/GradBusinessService.java | 55 ++++++++++++---- .../util/EducGraduationApiConstants.java | 12 +++- .../api/gradbusiness/util/TokenUtils.java | 65 +++++++++++++++++++ api/src/main/resources/application.yaml | 7 ++ api/src/test/resources/application.yaml | 7 ++ 10 files changed, 215 insertions(+), 14 deletions(-) create mode 100644 api/src/main/java/ca/bc/gov/educ/api/gradbusiness/config/TokenUtilsConfig.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/gradbusiness/model/dto/ResponseObj.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/gradbusiness/model/dto/ResponseObjCache.java create mode 100644 api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/TokenUtils.java diff --git a/api/pom.xml b/api/pom.xml index 1078740..ded3762 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -170,6 +170,11 @@ pdfbox 2.0.26 + + org.json + json + 20220320 + diff --git a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/config/EducGradBusinessApiConfig.java b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/config/EducGradBusinessApiConfig.java index 7cd745b..bcb5b39 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/config/EducGradBusinessApiConfig.java +++ b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/config/EducGradBusinessApiConfig.java @@ -40,7 +40,7 @@ public WebClient webClient() { return WebClient.builder().exchangeStrategies(ExchangeStrategies.builder() .codecs(configurer -> configurer .defaultCodecs() - .maxInMemorySize(20 * 1024 * 1024)) + .maxInMemorySize(100 * 1024 * 1024)) .build()).build(); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/config/TokenUtilsConfig.java b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/config/TokenUtilsConfig.java new file mode 100644 index 0000000..84b5c0d --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/config/TokenUtilsConfig.java @@ -0,0 +1,24 @@ +package ca.bc.gov.educ.api.gradbusiness.config; + +import ca.bc.gov.educ.api.gradbusiness.model.dto.ResponseObjCache; +import ca.bc.gov.educ.api.gradbusiness.util.EducGraduationApiConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class TokenUtilsConfig { + + EducGraduationApiConstants constants; + + @Autowired + public TokenUtilsConfig(EducGraduationApiConstants constants) { + this.constants = constants; + } + + @Bean + public ResponseObjCache createResponseObjCache() { + return new ResponseObjCache(constants.getTokenExpiryOffset()); + } + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/model/dto/ResponseObj.java b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/model/dto/ResponseObj.java new file mode 100644 index 0000000..2ca6913 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/model/dto/ResponseObj.java @@ -0,0 +1,10 @@ +package ca.bc.gov.educ.api.gradbusiness.model.dto; + +import lombok.Data; + +@Data +public class ResponseObj { + + private String access_token; + private String refresh_token; +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/model/dto/ResponseObjCache.java b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/model/dto/ResponseObjCache.java new file mode 100644 index 0000000..d414d25 --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/model/dto/ResponseObjCache.java @@ -0,0 +1,42 @@ +package ca.bc.gov.educ.api.gradbusiness.model.dto; + +import org.json.JSONObject; + +import java.util.Base64; + +public class ResponseObjCache { + + private long tokenExpiry = 0; + private ResponseObj responseObj; + + // tokenExpiry-[seconds] provides a slight offset, if token WILL expire in + // [seconds], obtain a new one + private int offset; + + public ResponseObjCache(int offset) { + this.offset = offset; + } + + public ResponseObj getResponseObj() { + return responseObj; + } + + public void setResponseObj(ResponseObj responseObj) { + this.setTokenExpiry(responseObj); + this.responseObj = responseObj; + } + + public boolean isExpired(){ + // tokenExpiry-[seconds] provides a slight offset, if token WILL expire in + // 10 seconds, obtain a new one + return (responseObj == null) || (tokenExpiry-offset) < (System.currentTimeMillis() / 1000); + } + + private void setTokenExpiry(ResponseObj responseObj){ + String[] parts = responseObj.getAccess_token().split("\\."); + JSONObject payload = new JSONObject(new String(Base64.getUrlDecoder().decode(parts[1]))); + this.tokenExpiry = payload.getLong("exp"); + } + + +} diff --git a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java index b740b69..5c87f42 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java @@ -4,6 +4,7 @@ import ca.bc.gov.educ.api.gradbusiness.util.EducGradBusinessApiConstants; import ca.bc.gov.educ.api.gradbusiness.util.EducGradBusinessUtil; import ca.bc.gov.educ.api.gradbusiness.util.EducGraduationApiConstants; +import ca.bc.gov.educ.api.gradbusiness.util.TokenUtils; import io.github.resilience4j.retry.annotation.Retry; import jakarta.transaction.Transactional; import org.apache.commons.lang3.StringUtils; @@ -23,6 +24,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; /** * The type Grad business service. @@ -41,6 +44,12 @@ public class GradBusinessService { */ final WebClient webClient; + /** + * Utility service to obtain access token + * */ + @Autowired + TokenUtils tokenUtils; + /** * The Educ grad student api constants. */ @@ -234,17 +243,7 @@ public ResponseEntity getAmalgamatedSchoolReportPDFByMincode(String minc logger.debug("******** Fetched Student List ******"); List locations = new ArrayList<>(); if (studentList != null && !studentList.isEmpty()) { - for (UUID studentID : studentList) { - InputStreamResource result = webClient.get().uri(String.format(educGraduationApiConstants.getStudentCredentialByType(), studentID, "ACHV")).headers(h -> h.setBearerAuth(accessToken)).retrieve().bodyToMono(InputStreamResource.class).block(); - if (result != null) { - try { - locations.add(result.getInputStream()); - } catch (IOException e) { - logger.debug("Error {}",e.getLocalizedMessage()); - } - } - } - logger.debug("******** Fetched Achievement Reports ******"); + getStudentAchievementReports(studentList, locations); Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("PST"), Locale.CANADA); int year = cal.get(Calendar.YEAR); String month = "00"; @@ -284,7 +283,6 @@ public ResponseEntity getStudentCredentialPDFByType(String pen, String t } } - @Transactional public ResponseEntity getStudentTranscriptPDFByType(String pen, String type, String interim, String accessToken) { try { @@ -316,4 +314,37 @@ public ResponseEntity getStudentTranscriptPDFByType(String pen, String t return getInternalServerErrorResponse(e); } } + + private void getStudentAchievementReports(List studentList, List locations) { + logger.debug("******** Getting Achievement Reports ******"); + List> futures = studentList.stream() + .map(studentGuid -> CompletableFuture.supplyAsync(() -> getStudentAchievementReport(studentGuid))) + .collect(Collectors.toList()); + CompletableFuture allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])); + CompletableFuture> result = allFutures.thenApply(v -> futures.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList())); + locations.addAll(result.join()); +// for (UUID studentID : studentList) { +// InputStream result = getStudentAchievementReport(studentID); +// if (result != null) { +// locations.add(result); +// } +// } + logger.debug("******** Fetched Achievement Reports ******"); + } + + private InputStream getStudentAchievementReport(UUID studentGuid) { + String accessTokenNext = tokenUtils.getAccessToken(); + InputStreamResource result = webClient.get().uri(String.format(educGraduationApiConstants.getStudentCredentialByType(), studentGuid, "ACHV")).headers(h -> h.setBearerAuth(accessTokenNext)).retrieve().bodyToMono(InputStreamResource.class).block(); + if (result != null) { + try { + logger.debug("******** Fetched Achievement Report for {} ******", studentGuid); + return result.getInputStream(); + } catch (IOException e) { + logger.debug("Error extracting bytes from binary stream: {}", e.getLocalizedMessage()); + } + } + return null; + } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/EducGraduationApiConstants.java b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/EducGraduationApiConstants.java index 4539f55..703e4ec 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/EducGraduationApiConstants.java +++ b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/EducGraduationApiConstants.java @@ -54,5 +54,15 @@ public class EducGraduationApiConstants { @Value("${endpoint.grad-report-api.transcript-by-request.url}") private String studentTranscriptReportByRequest; - + @Value("${authorization.user}") + private String userName; + + @Value("${authorization.password}") + private String password; + + @Value("${endpoint.keycloak.getToken}") + private String tokenUrl; + + @Value("${authorization.token-expiry-offset}") + private int tokenExpiryOffset; } diff --git a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/TokenUtils.java b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/TokenUtils.java new file mode 100644 index 0000000..8ffc42c --- /dev/null +++ b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/TokenUtils.java @@ -0,0 +1,65 @@ +package ca.bc.gov.educ.api.gradbusiness.util; + +import ca.bc.gov.educ.api.gradbusiness.model.dto.ResponseObj; +import ca.bc.gov.educ.api.gradbusiness.model.dto.ResponseObjCache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +public class TokenUtils { + private static Logger logger = LoggerFactory.getLogger(TokenUtils.class); + + private final EducGraduationApiConstants constants; + private final WebClient webClient; + private final ResponseObjCache responseObjCache; + + @Autowired + public TokenUtils(EducGraduationApiConstants constants, WebClient webClient, ResponseObjCache responseObjCache) { + this.constants = constants; + this.webClient = webClient; + this.responseObjCache = responseObjCache; + } + + private String fetchAccessToken() { + return this.getTokenResponseObject().getAccess_token(); + } + + public String getAccessToken() { + return this.fetchAccessToken(); + } + + private ResponseObj getTokenResponseObject() { + if(responseObjCache.isExpired()){ + responseObjCache.setResponseObj(getResponseObj()); + } + return responseObjCache.getResponseObj(); + } + + private ResponseObj getResponseObj() { + HttpHeaders httpHeaders = getHeaders( + constants.getUserName(), constants.getPassword()); + MultiValueMap map= new LinkedMultiValueMap<>(); + map.add("grant_type", "client_credentials"); + return this.webClient.post().uri(constants.getTokenUrl()) + .headers(h -> h.addAll(httpHeaders)) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(BodyInserters.fromFormData(map)) + .retrieve() + .bodyToMono(ResponseObj.class).block(); + } + + private HttpHeaders getHeaders (String username,String password) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + httpHeaders.setBasicAuth(username, password); + return httpHeaders; + } +} diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml index 24257d0..3b122d0 100644 --- a/api/src/main/resources/application.yaml +++ b/api/src/main/resources/application.yaml @@ -49,6 +49,11 @@ logging: autoconfigure: logging: ${SPRING_BOOT_AUTOCONFIG_LOG_LEVEL} +authorization: + user: ${STS_CLIENT_NAME} + password: ${STS_CLIENT_SECRET} + token-expiry-offset: '30' + #API Documentation springdoc: api-docs: @@ -98,6 +103,8 @@ resilience4j.retry: #Endpoint properties endpoint: + keycloak: + getToken: ${KEYCLOAK_TOKEN_URL}auth/realms/master/protocol/openid-connect/token pen-student-api: by-studentid: url: ${PEN_API}api/v1/student/%s diff --git a/api/src/test/resources/application.yaml b/api/src/test/resources/application.yaml index 35f1c1d..89040b7 100644 --- a/api/src/test/resources/application.yaml +++ b/api/src/test/resources/application.yaml @@ -51,6 +51,11 @@ logging: autoconfigure: logging: INFO +authorization: + user: grad-business-api + password: password + token-expiry-offset: '30' + #API Documentation springdoc: api-docs: @@ -62,6 +67,8 @@ springdoc: #Endpoints #Endpoint properties endpoint: + keycloak: + getToken: https://soam-dev.apps.silver.devops.gov.bc.ca/auth/realms/master/protocol/openid-connect/token grad-graduation-report-api: school-report-by-mincode: url: https://educ-grad-graduation-report-api-77c02f-dev.apps.silver.devops.gov.bc.ca/api/v1/graduationreports/schoolreport?mincode=%s&reportType=%s From 49aedbeac77c2d7ef859c149c4a4d86d8fd49795 Mon Sep 17 00:00:00 2001 From: arybakov Date: Thu, 23 Nov 2023 14:37:14 -0700 Subject: [PATCH 2/4] Fix school reports performance --- .../service/GradBusinessService.java | 102 +++++++++--------- .../api/gradbusiness/util/TokenUtils.java | 8 +- api/src/main/resources/application.yaml | 17 ++- .../EducGradBusinessApiApplicationTests.java | 27 +++++ .../EducGradBusinessApiControllerTests.java | 9 +- 5 files changed, 103 insertions(+), 60 deletions(-) diff --git a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java index 5c87f42..c5ea2d9 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java @@ -175,48 +175,6 @@ public ResponseEntity getStudentDemographicsByPen(String pen, String acc } } - /** - * Gets internal server error response. - * - * @param t the t - * @return the internal server error response - */ - protected ResponseEntity getInternalServerErrorResponse(Throwable t) { - ResponseEntity result; - - Throwable tmp = t; - String message; - if (tmp.getCause() != null) { - tmp = tmp.getCause(); - message = tmp.getMessage(); - } else { - message = tmp.getMessage(); - } - if(message == null) { - message = tmp.getClass().getName(); - } - - result = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(message.getBytes()); - return result; - } - - private ResponseEntity handleBinaryResponse(byte[] resultBinary, String reportFile, MediaType contentType) { - ResponseEntity response; - - if(resultBinary.length > 0) { - HttpHeaders headers = new HttpHeaders(); - headers.add("Content-Disposition", "inline; filename=" + reportFile); - response = ResponseEntity - .ok() - .headers(headers) - .contentType(contentType) - .body(resultBinary); - } else { - response = ResponseEntity.status(HttpStatus.NO_CONTENT).build(); - } - return response; - } - public ResponseEntity getSchoolReportPDFByMincode(String mincode, String type, String accessToken) { Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("PST"), Locale.CANADA); int year = cal.get(Calendar.YEAR); @@ -238,9 +196,10 @@ public ResponseEntity getSchoolReportPDFByMincode(String mincode, String } public ResponseEntity getAmalgamatedSchoolReportPDFByMincode(String mincode, String type, String accessToken) { + logger.debug("******** Retrieve List of Students for Amalgamated School Report ******"); List studentList = webClient.get().uri(String.format(educGradStudentApiConstants.getStudentsForAmalgamatedReport(), mincode, type)).headers(h -> h.setBearerAuth(accessToken)).retrieve().bodyToMono(new ParameterizedTypeReference>() { }).block(); - logger.debug("******** Fetched Student List ******"); + logger.debug("******** Fetched {} students ******", studentList.size()); List locations = new ArrayList<>(); if (studentList != null && !studentList.isEmpty()) { getStudentAchievementReports(studentList, locations); @@ -249,8 +208,9 @@ public ResponseEntity getAmalgamatedSchoolReportPDFByMincode(String minc String month = "00"; String fileName = EducGradBusinessUtil.getFileNameSchoolReports(mincode, year, month, type); try { - logger.debug("******** Merged Documents ******"); + logger.debug("******** Merging Documents Started ******"); byte[] res = EducGradBusinessUtil.mergeDocuments(locations); + logger.debug("******** Merged {} Documents ******", locations.size()); HttpHeaders headers = new HttpHeaders(); headers.put(HttpHeaders.AUTHORIZATION, Collections.singletonList(BEARER + accessToken)); headers.put(HttpHeaders.ACCEPT, Collections.singletonList(APPLICATION_PDF)); @@ -316,7 +276,7 @@ public ResponseEntity getStudentTranscriptPDFByType(String pen, String t } private void getStudentAchievementReports(List studentList, List locations) { - logger.debug("******** Getting Achievement Reports ******"); + logger.debug("******** Getting Student Achievement Reports ******"); List> futures = studentList.stream() .map(studentGuid -> CompletableFuture.supplyAsync(() -> getStudentAchievementReport(studentGuid))) .collect(Collectors.toList()); @@ -325,13 +285,7 @@ private void getStudentAchievementReports(List studentList, List getInternalServerErrorResponse(Throwable t) { + ResponseEntity result; + + Throwable tmp = t; + String message; + if (tmp.getCause() != null) { + tmp = tmp.getCause(); + message = tmp.getMessage(); + } else { + message = tmp.getMessage(); + } + if(message == null) { + message = tmp.getClass().getName(); + } + + result = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(message.getBytes()); + return result; + } + + private ResponseEntity handleBinaryResponse(byte[] resultBinary, String reportFile, MediaType contentType) { + ResponseEntity response; + + if(resultBinary.length > 0) { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Disposition", "inline; filename=" + reportFile); + response = ResponseEntity + .ok() + .headers(headers) + .contentType(contentType) + .body(resultBinary); + } else { + response = ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + return response; + } } diff --git a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/TokenUtils.java b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/TokenUtils.java index 8ffc42c..87e8088 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/TokenUtils.java +++ b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/util/TokenUtils.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; @@ -42,7 +43,7 @@ private ResponseObj getTokenResponseObject() { } return responseObjCache.getResponseObj(); } - + private ResponseObj getResponseObj() { HttpHeaders httpHeaders = getHeaders( constants.getUserName(), constants.getPassword()); @@ -62,4 +63,9 @@ private HttpHeaders getHeaders (String username,String password) { httpHeaders.setBasicAuth(username, password); return httpHeaders; } + + public ResponseObj getTokenFallback(HttpServerErrorException exception){ + logger.error("{} NOT REACHABLE after many attempts.", constants.getTokenUrl(), exception); + return null; + } } diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml index 3b122d0..98d4fd5 100644 --- a/api/src/main/resources/application.yaml +++ b/api/src/main/resources/application.yaml @@ -65,36 +65,43 @@ springdoc: #Resilience resilience4j.retry: instances: + gettoken: + max-attempts: ${MAX_RETRY_ATTEMPTS} + waitDuration: 5s + retryExceptions: + - org.springframework.web.client.HttpServerErrorException + ignoreExceptions: + - java.lang.NullPointerException searchbypen: - maxRetryAttempts: ${MAX_RETRY_ATTEMPTS} + max-attempts: ${MAX_RETRY_ATTEMPTS} waitDuration: 5s retryExceptions: - org.springframework.web.client.HttpServerErrorException ignoreExceptions: - java.lang.NullPointerException advancedsearch: - maxRetryAttempts: ${MAX_RETRY_ATTEMPTS} + max-attempts: ${MAX_RETRY_ATTEMPTS} waitDuration: 30s retryExceptions: - org.springframework.web.client.HttpServerErrorException ignoreExceptions: - java.lang.NullPointerException searchbyid: - maxRetryAttempts: ${MAX_RETRY_ATTEMPTS} + max-attempts: ${MAX_RETRY_ATTEMPTS} waitDuration: 5s retryExceptions: - org.springframework.web.client.HttpServerErrorException ignoreExceptions: - java.lang.NullPointerException generalpostcall: - maxRetryAttempts: ${MAX_RETRY_ATTEMPTS} + max-attempts: ${MAX_RETRY_ATTEMPTS} waitDuration: 3s retryExceptions: - org.springframework.web.client.HttpServerErrorException ignoreExceptions: - java.lang.NullPointerException generalgetcall: - maxRetryAttempts: ${MAX_RETRY_ATTEMPTS} + max-attempts: ${MAX_RETRY_ATTEMPTS} waitDuration: 3s retryExceptions: - org.springframework.web.client.HttpServerErrorException diff --git a/api/src/test/java/ca/bc/gov/educ/api/gradbusiness/EducGradBusinessApiApplicationTests.java b/api/src/test/java/ca/bc/gov/educ/api/gradbusiness/EducGradBusinessApiApplicationTests.java index fdaf090..733c306 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/gradbusiness/EducGradBusinessApiApplicationTests.java +++ b/api/src/test/java/ca/bc/gov/educ/api/gradbusiness/EducGradBusinessApiApplicationTests.java @@ -4,6 +4,7 @@ import ca.bc.gov.educ.api.gradbusiness.service.GradBusinessService; import ca.bc.gov.educ.api.gradbusiness.util.EducGradBusinessApiConstants; import ca.bc.gov.educ.api.gradbusiness.util.EducGraduationApiConstants; +import ca.bc.gov.educ.api.gradbusiness.util.TokenUtils; import org.junit.FixMethodOrder; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -46,6 +47,8 @@ class EducGradBusinessApiApplicationTests { @MockBean WebClient webClient; + @MockBean + private TokenUtils tokenUtils; @Mock private WebClient.RequestHeadersSpec requestHeadersMock; @@ -88,6 +91,8 @@ void testReportDataByPen() throws Exception { String reportData = readFile("json/studentTranscriptReportData.json"); assertNotNull(reportData); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); when(this.requestHeadersUriMock.uri(String.format(educGraduationApiConstants.getGraduateReportDataByPenUrl(),"128385861") + "?type=")).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.headers(any(Consumer.class))).thenReturn(this.requestHeadersMock); @@ -146,6 +151,8 @@ void testReportDataByGraduationData() throws Exception { String reportData = readFile("json/studentTranscriptReportData.json"); assertNotNull(reportData); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + when(this.webClient.post()).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.uri(educGraduationApiConstants.getGraduateReportDataByGraduation() + "?type=")).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.headers(any(Consumer.class))).thenReturn(this.requestBodyMock); @@ -226,6 +233,8 @@ void testXmlTranscriptReportData() throws Exception { String xmlTranscriptReportData = readFile("json/xml_report_sample.xml"); assertNotNull(xmlTranscriptReportData); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + when(this.webClient.post()).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.uri(educGraduationApiConstants.getXmlTranscriptReportData())).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.headers(any(Consumer.class))).thenReturn(this.requestBodyMock); @@ -264,6 +273,8 @@ void testSchoolReportPDFByMincode() throws Exception { byte[] samplePdf = readBinaryFile("data/sample.pdf"); InputStreamResource pdf = new InputStreamResource(new ByteArrayInputStream(samplePdf)); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); when(this.requestHeadersUriMock.uri(String.format(educGraduationApiConstants.getSchoolReportByMincode(),mincode,type))).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.headers(any(Consumer.class))).thenReturn(this.requestHeadersMock); @@ -284,6 +295,8 @@ void testgetAmalgamatedSchoolReportPDFByMincode() throws Exception { byte[] samplePdf = readBinaryFile("data/sample.pdf"); InputStreamResource pdf = new InputStreamResource(new ByteArrayInputStream(samplePdf)); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + UUID studentID = UUID.randomUUID(); when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); when(this.requestHeadersUriMock.uri(String.format(educGradStudentApiConstants.getStudentsForAmalgamatedReport(),mincode,type))).thenReturn(this.requestHeadersMock); @@ -325,6 +338,7 @@ void testSchoolReportPDFByMincode_NotFound() throws Exception { byte[] samplePdf = new byte[0]; InputStreamResource pdf = new InputStreamResource(new ByteArrayInputStream(samplePdf)); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); when(this.requestHeadersUriMock.uri(String.format(educGraduationApiConstants.getSchoolReportByMincode(),mincode,type))).thenReturn(this.requestHeadersMock); @@ -344,6 +358,7 @@ void testSchoolReportPDFByMincode_Error500() throws Exception { String type = "NONGRADPRJ"; InputStream is = getClass().getClassLoader().getResourceAsStream("json/xmlTranscriptReportRequest.json"); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); when(this.requestHeadersUriMock.uri(String.format(educGraduationApiConstants.getSchoolReportByMincode(),mincode,type))).thenReturn(this.requestHeadersMock); @@ -364,6 +379,8 @@ void testGetStudentDemographicsByPen() throws Exception { InputStream is = getClass().getClassLoader().getResourceAsStream("json/xmlTranscriptReportRequest.json"); byte[] barr = is.readAllBytes(); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); when(this.requestHeadersUriMock.uri(String.format(educGradStudentApiConstants.getPenDemographicStudentApiUrl(),pen))).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.headers(any(Consumer.class))).thenReturn(this.requestHeadersMock); @@ -410,6 +427,8 @@ void testStudentCredentialPDFByType() throws Exception { sObj.setPen(pen); sObj.setMincode("123123112"); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); when(this.requestHeadersUriMock.uri(String.format(educGraduationApiConstants.getStudentCredentialByType(),sObj.getStudentID(),type))).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.headers(any(Consumer.class))).thenReturn(this.requestHeadersMock); @@ -438,6 +457,8 @@ void testStudentTranscriptPDFByTypeByPen() throws Exception { String reportData = readFile("json/studentTranscriptReportData.json"); assertNotNull(reportData); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); when(this.requestHeadersUriMock.uri(String.format(educGraduationApiConstants.getGraduateReportDataByPenUrl(),"128385861") + "?type=")).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.headers(any(Consumer.class))).thenReturn(this.requestHeadersMock); @@ -460,6 +481,8 @@ void testStudentTranscriptPDFByTypeByPen() throws Exception { when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); when(this.responseMock.bodyToMono(byte[].class)).thenReturn(Mono.just(transcriptPdfSample)); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + ResponseEntity transcriptPdf = gradBusinessService.getStudentTranscriptPDFByType(pen, "xml", null,"accessToken"); assertNotNull(transcriptPdf.getBody()); assertEquals(transcriptPdfSample,transcriptPdf.getBody()); @@ -479,6 +502,8 @@ void testStudentCredentialPDFByType_NotFound() throws Exception { sObj.setPen(pen); sObj.setMincode("123123112"); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); when(this.requestHeadersUriMock.uri(String.format(educGraduationApiConstants.getStudentCredentialByType(),sObj.getStudentID(),type))).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.headers(any(Consumer.class))).thenReturn(this.requestHeadersMock); @@ -518,6 +543,8 @@ void testStudentCredentialPDFByType_Error500() throws Exception { final ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() { }; + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); when(this.requestHeadersUriMock.uri(String.format(educGradStudentApiConstants.getPenStudentApiByPenUrl(),pen))).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.headers(any(Consumer.class))).thenReturn(this.requestHeadersMock); diff --git a/api/src/test/java/ca/bc/gov/educ/api/gradbusiness/EducGradBusinessApiControllerTests.java b/api/src/test/java/ca/bc/gov/educ/api/gradbusiness/EducGradBusinessApiControllerTests.java index fbe0ade..e40b22b 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/gradbusiness/EducGradBusinessApiControllerTests.java +++ b/api/src/test/java/ca/bc/gov/educ/api/gradbusiness/EducGradBusinessApiControllerTests.java @@ -3,6 +3,7 @@ import ca.bc.gov.educ.api.gradbusiness.controller.GradBusinessController; import ca.bc.gov.educ.api.gradbusiness.model.dto.Student; import ca.bc.gov.educ.api.gradbusiness.service.GradBusinessService; +import ca.bc.gov.educ.api.gradbusiness.util.TokenUtils; import org.junit.FixMethodOrder; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; @@ -11,6 +12,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -25,6 +27,7 @@ import java.util.UUID; import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RunWith(SpringRunner.class) @@ -32,13 +35,15 @@ @ActiveProfiles("test") class EducGradBusinessApiControllerTests { + @MockBean + private TokenUtils tokenUtils; + @Mock private GradBusinessService gradBusinessService; @InjectMocks private GradBusinessController gradBusinessController; - @Test void testReportDataByPen() throws Exception { @@ -234,6 +239,8 @@ void testStudentTranscriptPDFByType() throws Exception { .contentType(MediaType.APPLICATION_XML) .body(greBPack); + when(this.tokenUtils.getAccessToken()).thenReturn("accessToken"); + Mockito.when(gradBusinessService.getStudentTranscriptPDFByType("12312321","xml", null,"accessToken")).thenReturn(response); gradBusinessController.studentTranscriptByType("12312321", "xml", null, "accessToken"); Mockito.verify(gradBusinessService).getStudentTranscriptPDFByType("12312321","xml", null,"accessToken"); From c959cd88b667a88d5fb59f02c4c8163539944896 Mon Sep 17 00:00:00 2001 From: arybakov Date: Thu, 23 Nov 2023 14:48:27 -0700 Subject: [PATCH 3/4] Code smells fix --- .../service/GradBusinessService.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java index c5ea2d9..6e34e2d 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/gradbusiness/service/GradBusinessService.java @@ -25,7 +25,6 @@ import java.io.InputStream; import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; /** * The type Grad business service. @@ -47,20 +46,17 @@ public class GradBusinessService { /** * Utility service to obtain access token * */ - @Autowired - TokenUtils tokenUtils; + final TokenUtils tokenUtils; /** * The Educ grad student api constants. */ - @Autowired - EducGradBusinessApiConstants educGradStudentApiConstants; + final EducGradBusinessApiConstants educGradStudentApiConstants; /** * The Educ graduation api constants. */ - @Autowired - EducGraduationApiConstants educGraduationApiConstants; + final EducGraduationApiConstants educGraduationApiConstants; /** * Instantiates a new Grad business service. @@ -68,8 +64,11 @@ public class GradBusinessService { * @param webClient the web client */ @Autowired - public GradBusinessService(WebClient webClient) { + public GradBusinessService(WebClient webClient, TokenUtils tokenUtils, EducGradBusinessApiConstants educGradStudentApiConstants, EducGraduationApiConstants educGraduationApiConstants) { this.webClient = webClient; + this.tokenUtils = tokenUtils; + this.educGradStudentApiConstants = educGradStudentApiConstants; + this.educGraduationApiConstants = educGraduationApiConstants; } /** @@ -199,9 +198,9 @@ public ResponseEntity getAmalgamatedSchoolReportPDFByMincode(String minc logger.debug("******** Retrieve List of Students for Amalgamated School Report ******"); List studentList = webClient.get().uri(String.format(educGradStudentApiConstants.getStudentsForAmalgamatedReport(), mincode, type)).headers(h -> h.setBearerAuth(accessToken)).retrieve().bodyToMono(new ParameterizedTypeReference>() { }).block(); - logger.debug("******** Fetched {} students ******", studentList.size()); List locations = new ArrayList<>(); if (studentList != null && !studentList.isEmpty()) { + logger.debug("******** Fetched {} students ******", studentList.size()); getStudentAchievementReports(studentList, locations); Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("PST"), Locale.CANADA); int year = cal.get(Calendar.YEAR); @@ -279,11 +278,11 @@ private void getStudentAchievementReports(List studentList, List> futures = studentList.stream() .map(studentGuid -> CompletableFuture.supplyAsync(() -> getStudentAchievementReport(studentGuid))) - .collect(Collectors.toList()); + .toList(); CompletableFuture allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])); CompletableFuture> result = allFutures.thenApply(v -> futures.stream() .map(CompletableFuture::join) - .collect(Collectors.toList())); + .toList()); locations.addAll(result.join()); logger.debug("******** Fetched All Student Achievement Reports ******"); } From 811945eaa7cfe2b3a4142a676f108c5006acd580 Mon Sep 17 00:00:00 2001 From: Kamal Mohammed Date: Thu, 23 Nov 2023 14:58:00 -0700 Subject: [PATCH 4/4] Update pom.xml --- api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/pom.xml b/api/pom.xml index ded3762..ecb4c05 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -11,7 +11,7 @@ ca.bc.gov educ-grad-business-api - 1.8.25 + 1.8.26 educ-grad-business-api GRAD Business API for external clients