diff --git a/.github/workflows/build.from.developer.branch.deploy.to.dev.yml b/.github/workflows/build.from.developer.branch.deploy.to.dev.yml index c13c46c1..89cae1fd 100644 --- a/.github/workflows/build.from.developer.branch.deploy.to.dev.yml +++ b/.github/workflows/build.from.developer.branch.deploy.to.dev.yml @@ -29,6 +29,7 @@ env: MAX_MEM: "1500Mi" MIN_REPLICAS: "3" MAX_REPLICAS: "7" + SOAM_KC_REALM_ID: "master" on: # https://docs.github.com/en/actions/reference/events-that-trigger-workflows @@ -145,6 +146,16 @@ jobs: ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ ${{ vars.APP_LOG_LEVEL }} + + # UPDATE KC Client + curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ env.BRANCH }}/tools/config/update-kc-client.sh \ + | bash /dev/stdin \ + dev \ + ${{ env.COMMON_NAMESPACE }} \ + ${{ env.SOAM_KC_REALM_ID }} \ + ${{ secrets.CLIENT_ID }} \ + ${{ env.BRANCH }} \ + ${{ env.REPO_NAME }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.github/workflows/build.from.main.branch.deploy.to.dev.yml b/.github/workflows/build.from.main.branch.deploy.to.dev.yml index fb5ef3e5..b81ace7f 100644 --- a/.github/workflows/build.from.main.branch.deploy.to.dev.yml +++ b/.github/workflows/build.from.main.branch.deploy.to.dev.yml @@ -29,6 +29,7 @@ env: MAX_MEM: "1500Mi" MIN_REPLICAS: "3" MAX_REPLICAS: "7" + SOAM_KC_REALM_ID: "master" on: # https://docs.github.com/en/actions/reference/events-that-trigger-workflows @@ -127,6 +128,16 @@ jobs: ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ ${{ vars.APP_LOG_LEVEL }} + + # UPDATE KC Client + curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ env.BRANCH }}/tools/config/update-kc-client.sh \ + | bash /dev/stdin \ + dev \ + ${{ env.COMMON_NAMESPACE }} \ + ${{ env.SOAM_KC_REALM_ID }} \ + ${{ secrets.CLIENT_ID }} \ + ${{ env.BRANCH }} \ + ${{ env.REPO_NAME }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.github/workflows/build.from.release.branch.deploy.to.dev.yml b/.github/workflows/build.from.release.branch.deploy.to.dev.yml index 82d03e56..ab09e3a3 100644 --- a/.github/workflows/build.from.release.branch.deploy.to.dev.yml +++ b/.github/workflows/build.from.release.branch.deploy.to.dev.yml @@ -28,6 +28,7 @@ env: MAX_MEM: "1500Mi" MIN_REPLICAS: "3" MAX_REPLICAS: "7" + SOAM_KC_REALM_ID: "master" on: # https://docs.github.com/en/actions/reference/events-that-trigger-workflows @@ -134,6 +135,16 @@ jobs: ${{ env.BUSINESS_NAMESPACE }} \ ${{ secrets.SPLUNK_TOKEN }} \ ${{ vars.APP_LOG_LEVEL }} + + # UPDATE KC Client + curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ env.BRANCH }}/tools/config/update-kc-client.sh \ + | bash /dev/stdin \ + dev \ + ${{ env.COMMON_NAMESPACE }} \ + ${{ env.SOAM_KC_REALM_ID }} \ + ${{ secrets.CLIENT_ID }} \ + ${{ env.BRANCH }} \ + ${{ env.REPO_NAME }} # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ diff --git a/.github/workflows/deploy_prod.yml b/.github/workflows/deploy_prod.yml index 7c3a3839..101357b6 100644 --- a/.github/workflows/deploy_prod.yml +++ b/.github/workflows/deploy_prod.yml @@ -23,6 +23,7 @@ env: MAX_MEM: "1500Mi" MIN_REPLICAS: "3" MAX_REPLICAS: "7" + SOAM_KC_REALM_ID: "master" on: # https://docs.github.com/en/actions/reference/events-that-trigger-workflows @@ -83,6 +84,16 @@ jobs: ${{ secrets.SPLUNK_TOKEN }} \ ${{ vars.APP_LOG_LEVEL }} + # UPDATE KC Client + curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ env.BRANCH }}/tools/config/update-kc-client.sh \ + | bash /dev/stdin \ + dev \ + ${{ env.COMMON_NAMESPACE }} \ + ${{ env.SOAM_KC_REALM_ID }} \ + ${{ secrets.CLIENT_ID }} \ + ${{ env.BRANCH }} \ + ${{ env.REPO_NAME }} + # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ || true && echo "Rollout in progress" diff --git a/.github/workflows/deploy_test.yml b/.github/workflows/deploy_test.yml index b6110d15..88a5c595 100644 --- a/.github/workflows/deploy_test.yml +++ b/.github/workflows/deploy_test.yml @@ -23,6 +23,7 @@ env: MAX_MEM: "1500Mi" MIN_REPLICAS: "3" MAX_REPLICAS: "7" + SOAM_KC_REALM_ID: "master" on: # https://docs.github.com/en/actions/reference/events-that-trigger-workflows @@ -83,6 +84,16 @@ jobs: ${{ secrets.SPLUNK_TOKEN }} \ ${{ vars.APP_LOG_LEVEL }} + # UPDATE KC Client + curl -s https://raw.githubusercontent.com/bcgov/${{ env.REPO_NAME }}/${{ env.BRANCH }}/tools/config/update-kc-client.sh \ + | bash /dev/stdin \ + dev \ + ${{ env.COMMON_NAMESPACE }} \ + ${{ env.SOAM_KC_REALM_ID }} \ + ${{ secrets.CLIENT_ID }} \ + ${{ env.BRANCH }} \ + ${{ env.REPO_NAME }} + # Start rollout (if necessary) and follow it oc rollout latest dc/${{ env.SPRING_BOOT_IMAGE_NAME }} 2> /dev/null \ || true && echo "Rollout in progress" diff --git a/api/pom.xml b/api/pom.xml index 28b90f83..463b5867 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ ca.bc.gov.educ educ-grad-graduation-api - 1.8.55 + 1.8.56 educ-grad-graduation-api Ministry of Education GRAD GRADUATION API diff --git a/api/src/main/java/ca/bc/gov/educ/api/graduation/config/RestWebClient.java b/api/src/main/java/ca/bc/gov/educ/api/graduation/config/RestWebClient.java index 61acb3d3..a8deb96a 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/graduation/config/RestWebClient.java +++ b/api/src/main/java/ca/bc/gov/educ/api/graduation/config/RestWebClient.java @@ -7,6 +7,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +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.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; @@ -18,20 +25,48 @@ @Profile("!test") public class RestWebClient { - @Autowired EducGraduationApiConstants constants; - @Autowired LogHelper logHelper; - private final HttpClient httpClient; + @Autowired + public RestWebClient(EducGraduationApiConstants constants, LogHelper logHelper) { + this.constants = constants; + this.logHelper = logHelper; + } - public RestWebClient() { - this.httpClient = HttpClient.create().compress(true) - .resolver(spec -> spec.queryTimeout(Duration.ofMillis(200)).trace("DNS", LogLevel.TRACE)); - this.httpClient.warmup().block(); + @Bean("graduationClient") + public WebClient getGraduationClientWebClient(OAuth2AuthorizedClientManager authorizedClientManager) { + ServletOAuth2AuthorizedClientExchangeFilterFunction filter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); + filter.setDefaultClientRegistrationId("graduationclient"); + return WebClient.builder() + .exchangeStrategies(ExchangeStrategies + .builder() + .codecs(codecs -> codecs + .defaultCodecs() + .maxInMemorySize(50 * 1024 * 1024)) + .build()) + .apply(filter.oauth2Configuration()) + .filter(this.log()) + .build(); + } + @Bean + public OAuth2AuthorizedClientManager authorizedClientManager( + ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientRepository authorizedClientRepository) { + OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() + .clientCredentials() + .build(); + DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( + clientRegistrationRepository, authorizedClientRepository); + authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + return authorizedClientManager; } + /** + * Old web client. You can use a @Qualifier('default') to summon it. + * @return + */ @Bean public WebClient webClient() { return WebClient.builder().exchangeStrategies(ExchangeStrategies.builder() diff --git a/api/src/main/java/ca/bc/gov/educ/api/graduation/service/GradStatusService.java b/api/src/main/java/ca/bc/gov/educ/api/graduation/service/GradStatusService.java index 542197b0..c17c33bb 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/graduation/service/GradStatusService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/graduation/service/GradStatusService.java @@ -131,8 +131,7 @@ public void restoreStudentGradStatus(String studentID, String accessToken,boolea public List getStudentListByMinCode(String schoolOfRecord, String accessToken) { List response = this.restService.get(String.format(educGraduationApiConstants.getGradStudentListSchoolReport(),schoolOfRecord), - List.class, - accessToken); + List.class); return jsonTransformer.convertValue(response, new TypeReference<>(){}); } diff --git a/api/src/main/java/ca/bc/gov/educ/api/graduation/service/GraduationService.java b/api/src/main/java/ca/bc/gov/educ/api/graduation/service/GraduationService.java index f721ca3a..a51ad91c 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/graduation/service/GraduationService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/graduation/service/GraduationService.java @@ -248,17 +248,8 @@ public byte[] getSchoolReports(List uniqueSchoolList, String type, Strin public Integer createAndStoreSchoolReports(List uniqueSchoolList, String type, String accessToken) { int numberOfReports = 0; - Pair res = Pair.of(accessToken, System.currentTimeMillis()); ExceptionMessage exception = new ExceptionMessage(); - int i = 0; for (String usl : uniqueSchoolList) { - if (i == 0) { - res = getAccessToken(accessToken); - } else { - res = checkAndGetAccessToken(res); - } - accessToken = res.getLeft(); - try { List stdList = gradStatusService.getStudentListByMinCode(usl, accessToken); if(logger.isDebugEnabled()) { @@ -281,8 +272,6 @@ public Integer createAndStoreSchoolReports(List uniqueSchoolList, String List gradRegStudents = processStudentList(filterStudentList(stdList, GRADREG), type); logger.debug("*** Process processGradRegReport {} for {} students", schoolObj.getMincode(), gradRegStudents.size()); numberOfReports = processGradRegReport(schoolObj, gradRegStudents, usl, accessToken, numberOfReports); - res = checkAndGetAccessToken(res); - accessToken = res.getLeft(); List nonGradRegStudents = processStudentList(filterStudentList(stdList, NONGRADREG), type); logger.debug("*** Process processNonGradRegReport {} for {} students", schoolObj.getMincode(), nonGradRegStudents.size()); numberOfReports = processNonGradRegReport(schoolObj, nonGradRegStudents, usl, accessToken, numberOfReports); @@ -292,7 +281,6 @@ public Integer createAndStoreSchoolReports(List uniqueSchoolList, String } catch (Exception e) { logger.error("Failed to generate {} report for mincode: {} due to: {}", type, usl, e.getLocalizedMessage()); } - i++; } return numberOfReports; } @@ -423,8 +411,7 @@ private byte[] getSchoolReportGradRegReport(ReportData data, String mincode, Str return this.restService.post(educGraduationApiConstants.getSchoolGraduation(), reportParams, - byte[].class, - accessToken); + byte[].class); } @@ -437,16 +424,15 @@ private byte[] createAndSaveSchoolReportGradRegReport(ReportData data, String mi SchoolReports requestObj = getSchoolReports(mincode, encodedPdf, GRADREG); - updateSchoolReport(accessToken, requestObj); + updateSchoolReport(requestObj); return bytesSAR; } - private void updateSchoolReport(String accessToken, SchoolReports requestObj) { + private void updateSchoolReport(SchoolReports requestObj) { this.restService.post(educGraduationApiConstants.getUpdateSchoolReport(), requestObj, - SchoolReports.class, - accessToken); + SchoolReports.class); } private String getEncodedPdfFromBytes(byte[] bytesSAR) { @@ -474,7 +460,7 @@ private void createAndSaveSchoolReportNonGradRegReport(ReportData data, String m byte[] bytesSAR = getSchoolReportNonGradRegReport(data, mincode, accessToken); String encodedPdf = getEncodedPdfFromBytes(bytesSAR); SchoolReports requestObj = getSchoolReports(mincode, encodedPdf, NONGRADREG); - updateSchoolReport(accessToken, requestObj); + updateSchoolReport(requestObj); } private byte[] getSchoolReportStudentNonGradPrjReport(ReportData data, String mincode, String accessToken) { @@ -489,8 +475,7 @@ private byte[] getSchoolReportStudentNonGradPrjReport(ReportData data, String mi return this.restService.post(educGraduationApiConstants.getStudentNonGradProjected(), reportParams, - byte[].class, - accessToken); + byte[].class); } @Generated @@ -514,7 +499,7 @@ private void createAndSaveSchoolReportStudentNonGradPrjReport(ReportData data, S byte[] bytesSAR = getSchoolReportStudentNonGradPrjReport(data, mincode, accessToken); String encodedPdf = getEncodedPdfFromBytes(bytesSAR); SchoolReports requestObj = getSchoolReports(mincode, encodedPdf, NONGRADPRJ); - updateSchoolReport(accessToken, requestObj); + updateSchoolReport(requestObj); } /** diff --git a/api/src/main/java/ca/bc/gov/educ/api/graduation/service/RESTService.java b/api/src/main/java/ca/bc/gov/educ/api/graduation/service/RESTService.java index d6a8d7b0..90d67580 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/graduation/service/RESTService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/graduation/service/RESTService.java @@ -4,6 +4,7 @@ import ca.bc.gov.educ.api.graduation.util.EducGraduationApiConstants; import ca.bc.gov.educ.api.graduation.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; @@ -18,16 +19,24 @@ public class RESTService { private final WebClient webClient; + private final WebClient graduationServiceWebClient; + + private static final String ERROR_5xx = "5xx error."; + private static final String SERVICE_FAILED_ERROR = "Service failed to process after max retries."; @Autowired - public RESTService(WebClient webClient) { + public RESTService(@Qualifier("graduationClient") WebClient graduationServiceWebClient, WebClient webClient) { this.webClient = webClient; + this.graduationServiceWebClient = graduationServiceWebClient; } /** * 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 @@ -44,14 +53,14 @@ public T get(String url, Class clazz, String accessToken) { .retrieve() // if 5xx errors, throw Service error .onStatus(HttpStatusCode::is5xxServerError, - clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, "5xx error."), clientResponse.statusCode().value()))) + 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 to process after max retries."), HttpStatus.SERVICE_UNAVAILABLE.value()); + throw new ServiceException(getErrorMessage(url, SERVICE_FAILED_ERROR), HttpStatus.SERVICE_UNAVAILABLE.value()); })) .block(); } catch (Exception e) { @@ -61,6 +70,42 @@ public T get(String url, Class clazz, String accessToken) { return obj; } + public T get(String url, Class clazz) { + T obj; + try { + obj = graduationServiceWebClient + .get() + .uri(url) + .headers(h -> { h.set(EducGraduationApiConstants.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 { @@ -70,12 +115,35 @@ public T post(String url, Object body, Class clazz, String accessToken) { .body(BodyInserters.fromValue(body)) .retrieve() .onStatus(HttpStatusCode::is5xxServerError, - clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, "5xx error."), clientResponse.statusCode().value()))) + 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) { + T obj; + try { + obj = graduationServiceWebClient.post() + .uri(url) + .headers(h -> { h.set(EducGraduationApiConstants.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 to process after max retries."), HttpStatus.SERVICE_UNAVAILABLE.value()); + throw new ServiceException(getErrorMessage(url, SERVICE_FAILED_ERROR), HttpStatus.SERVICE_UNAVAILABLE.value()); })) .block(); } catch (Exception e) { diff --git a/api/src/main/java/ca/bc/gov/educ/api/graduation/service/SchoolReportsService.java b/api/src/main/java/ca/bc/gov/educ/api/graduation/service/SchoolReportsService.java index 1bf76609..22e65722 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/graduation/service/SchoolReportsService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/graduation/service/SchoolReportsService.java @@ -43,20 +43,25 @@ public class SchoolReportsService { private static final String SCHOOL_DISTRICT_REPORTS_CREATED = "***** {} of School Districts Reports Created *****"; private static final String SCHOOL_LABEL_REPORTS_CREATED = "***** {} of School Labels Reports Created *****"; - @Autowired + WebClient webClient; - @Autowired ReportService reportService; - @Autowired SchoolService schoolService; - @Autowired TokenUtils tokenUtils; - - @Autowired EducGraduationApiConstants educGraduationApiConstants; + RESTService restService; + JsonTransformer jsonTransformer; @Autowired - JsonTransformer jsonTransformer; + public SchoolReportsService(WebClient webClient, ReportService reportService, SchoolService schoolService, TokenUtils tokenUtils, EducGraduationApiConstants educGraduationApiConstants, RESTService restService, JsonTransformer jsonTransformer) { + this.webClient = webClient; + this.reportService = reportService; + this.schoolService = schoolService; + this.tokenUtils = tokenUtils; + this.educGraduationApiConstants = educGraduationApiConstants; + this.restService = restService; + this.jsonTransformer = jsonTransformer; + } public byte[] getSchoolDistrictYearEndReports(String accessToken, String slrt, String drt, String srt) { List reportGradStudentDataList = reportService.getStudentsForSchoolYearEndReport(accessToken); @@ -349,7 +354,7 @@ private Integer createAndStoreDistrictReports(String reportType, List { - h.setBearerAuth(accessToken); - h.set(EducGraduationApiConstants.CORRELATION_ID, ThreadLocalStateUtil.getCorrelationID()); - } - ).body(BodyInserters.fromValue(requestObj)).retrieve().bodyToMono(SchoolReports.class).block(); + private void updateSchoolReport(SchoolReports requestObj) { + this.restService.post(educGraduationApiConstants.getUpdateSchoolReport(), + requestObj, + SchoolReports.class); } private String getEncodedPdfFromBytes(byte[] bytesSAR) { diff --git a/api/src/main/java/ca/bc/gov/educ/api/graduation/service/SchoolService.java b/api/src/main/java/ca/bc/gov/educ/api/graduation/service/SchoolService.java index 76cca761..eeb1a6f7 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/graduation/service/SchoolService.java +++ b/api/src/main/java/ca/bc/gov/educ/api/graduation/service/SchoolService.java @@ -23,8 +23,7 @@ public SchoolService(EducGraduationApiConstants educGraduationApiConstants, REST public SchoolTrax getTraxSchoolDetails(String mincode, String accessToken, ExceptionMessage message) { return this.restService.get(String.format(educGraduationApiConstants.getSchoolDetails(),mincode, accessToken), - SchoolTrax.class, - accessToken); + SchoolTrax.class); } public SchoolTrax getTraxSchoolDetails(String mincode) { diff --git a/api/src/main/java/ca/bc/gov/educ/api/graduation/util/EducGraduationApiConstants.java b/api/src/main/java/ca/bc/gov/educ/api/graduation/util/EducGraduationApiConstants.java index c5bc3b7a..00e70adc 100644 --- a/api/src/main/java/ca/bc/gov/educ/api/graduation/util/EducGraduationApiConstants.java +++ b/api/src/main/java/ca/bc/gov/educ/api/graduation/util/EducGraduationApiConstants.java @@ -140,7 +140,7 @@ public class EducGraduationApiConstants { @Value("${endpoint.grad-student-graduation-api.get-special-cases.url}") private String specialCase; - @Value("${endpoint.educ-school-api.url}") + @Value("${endpoint.grad-trax-api.commonschool-by-mincode.url}") private String schoolCategoryCode; @Value("${endpoint.grad-student-api.update-grad-status-projected}") diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml index 1ecbfb58..d3fb4cf3 100644 --- a/api/src/main/resources/application.yaml +++ b/api/src/main/resources/application.yaml @@ -14,6 +14,16 @@ spring: jwt: issuer-uri: ${TOKEN_ISSUER_URL} jwk-set-uri: ${TOKEN_ISSUER_URL}/protocol/openid-connect/certs + client: + registration: + graduationclient: + client-id: ${GRAD_GRADUATION_API_CLIENT_NAME} + client-secret: ${GRAD_GRADUATION_API_CLIENT_SECRET} + authorization-grant-type: client_credentials + provider: + graduationclient: + issuer-uri: ${TOKEN_ISSUER_URL} + token-uri: ${TOKEN_ISSUER_URL}/protocol/openid-connect/token #Logging properties logging: @@ -121,8 +131,7 @@ endpoint: grad-student-graduation-api: get-special-cases: url: ${GRAD_STUDENT_GRADUATION_API}api/v1/studentgraduation/lgSc/specialcase/%s - educ-school-api: - url: ${EDUC_SCHOOL_API}api/v1/schools/%s + pen-student-api: by-studentid: url: ${PEN_API}api/v1/student/%s @@ -130,7 +139,11 @@ endpoint: url: ${PEN_API}api/v1/student/paginated by-pen: url: ${PEN_API}api/v1/student?pen=%s + + grad-trax-api: + commonschool-by-mincode: + url: ${GRAD_TRAX_API}api/v1/trax/school/common/%s school-by-min-code: url: ${GRAD_TRAX_API}api/v1/trax/school/%s district-by-min-code: diff --git a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GradAlgorithmServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GradAlgorithmServiceTest.java index 6a63f3dc..48be388b 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GradAlgorithmServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GradAlgorithmServiceTest.java @@ -16,8 +16,13 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.reactive.function.client.WebClient; @@ -46,7 +51,21 @@ public class GradAlgorithmServiceTest { private ExceptionMessage exception; @MockBean - WebClient webClient; + @Qualifier("graduationClient") + WebClient webClient; + + @TestConfiguration + static class TestConfig { + @Bean + public ClientRegistrationRepository clientRegistrationRepository() { + return new ClientRegistrationRepository() { + @Override + public ClientRegistration findByRegistrationId(String registrationId) { + return null; + } + }; + } + } @Autowired private EducGraduationApiConstants constants; diff --git a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GradStatusServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GradStatusServiceTest.java index 3e4eb434..eb0374da 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GradStatusServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GradStatusServiceTest.java @@ -374,7 +374,7 @@ public void testGetStudentsbyMincode() { }; GraduationStudentRecord gsr = new GraduationStudentRecord(); gsr.setLegalLastName("qweqw"); - when(this.restService.get(any(String.class), any(), any())).thenReturn(List.of(gsr)); + when(this.restService.get(any(String.class), any())).thenReturn(List.of(gsr)); List res = gradStatusService.getStudentListByMinCode(mincode,"accessToken"); assertThat(res).isNotEmpty().hasSize(1); diff --git a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GraduationServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GraduationServiceTest.java index a44d93a7..55f54e74 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GraduationServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/GraduationServiceTest.java @@ -11,12 +11,18 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.tuple.Pair; import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.reactive.function.BodyInserter; @@ -40,6 +46,7 @@ @RunWith(SpringRunner.class) @SpringBootTest @ActiveProfiles("test") +@ExtendWith(MockitoExtension.class) public class GraduationServiceTest { @Autowired @@ -75,12 +82,21 @@ public class GraduationServiceTest { @MockBean private TokenUtils tokenUtils; + @MockBean + private ClientRegistrationRepository clientRegistrationRepository; + + @MockBean + private OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository; + @Autowired GradValidation validation; - @MockBean + @MockBean(name = "webClient") WebClient webClient; + @MockBean(name = "graduationServiceWebClient") + WebClient graduationServiceWebClient; + @Autowired JsonTransformer jsonTransformer; @@ -1998,23 +2014,17 @@ public void testCreateAndStoreSchoolReports() { byte[] bytesSAR1 = "Any String you want".getBytes(); when(this.webClient.post()).thenReturn(this.requestBodyUriMock); + when(this.graduationServiceWebClient.post()).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.uri(String.format(constants.getSchoolGraduation()))).thenReturn(this.requestBodyUriMock); - when(this.requestBodyUriMock.headers(any(Consumer.class))).thenReturn(this.requestBodyMock); - when(this.requestBodyMock.contentType(any())).thenReturn(this.requestBodyMock); - when(this.requestBodyMock.body(any(BodyInserter.class))).thenReturn(this.requestHeadersMock); - when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); - when(this.responseMock.bodyToMono(byte[].class)).thenReturn(Mono.just(bytesSAR1)); - - byte[] bytesSAR2 = "Just another string".getBytes(); - when(this.webClient.post()).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.uri(String.format(constants.getSchoolNonGraduation()))).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.headers(any(Consumer.class))).thenReturn(this.requestBodyMock); when(this.requestBodyMock.contentType(any())).thenReturn(this.requestBodyMock); when(this.requestBodyMock.body(any(BodyInserter.class))).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); - when(this.responseMock.bodyToMono(byte[].class)).thenReturn(Mono.just(bytesSAR2)); + when(this.responseMock.bodyToMono(byte[].class)).thenReturn(Mono.just(bytesSAR1)); - when(this.restService.post(any(String.class), any(), any(), any())).thenReturn(bytesSAR2); + when(this.restService.post(any(String.class), any(), any(), any())).thenReturn(bytesSAR1); + when(this.restService.post(any(String.class), any(), any())).thenReturn(bytesSAR1); when(this.webClient.post()).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.uri(String.format(constants.getUpdateSchoolReport()))).thenReturn(this.requestBodyUriMock); @@ -2155,33 +2165,18 @@ public void testGetSchoolReports() { byte[] bytesSAR1 = "Any String you want".getBytes(); when(this.webClient.post()).thenReturn(this.requestBodyUriMock); + when(this.graduationServiceWebClient.post()).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.uri(String.format(constants.getStudentNonGradProjected()))).thenReturn(this.requestBodyUriMock); - when(this.requestBodyUriMock.headers(any(Consumer.class))).thenReturn(this.requestBodyMock); - when(this.requestBodyMock.contentType(any())).thenReturn(this.requestBodyMock); - when(this.requestBodyMock.body(any(BodyInserter.class))).thenReturn(this.requestHeadersMock); - when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); - when(this.responseMock.bodyToMono(byte[].class)).thenReturn(Mono.just(bytesSAR1)); - - byte[] bytesSAR2 = "Any String you want".getBytes(); - - when(this.webClient.post()).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.uri(String.format(constants.getSchoolGraduation()))).thenReturn(this.requestBodyUriMock); - when(this.requestBodyUriMock.headers(any(Consumer.class))).thenReturn(this.requestBodyMock); - when(this.requestBodyMock.contentType(any())).thenReturn(this.requestBodyMock); - when(this.requestBodyMock.body(any(BodyInserter.class))).thenReturn(this.requestHeadersMock); - when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); - when(this.responseMock.bodyToMono(byte[].class)).thenReturn(Mono.just(bytesSAR2)); - - byte[] bytesSAR3 = "Just another string".getBytes(); - when(this.webClient.post()).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.uri(String.format(constants.getSchoolNonGraduation()))).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.headers(any(Consumer.class))).thenReturn(this.requestBodyMock); when(this.requestBodyMock.contentType(any())).thenReturn(this.requestBodyMock); when(this.requestBodyMock.body(any(BodyInserter.class))).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); - when(this.responseMock.bodyToMono(byte[].class)).thenReturn(Mono.just(bytesSAR3)); + when(this.responseMock.bodyToMono(byte[].class)).thenReturn(Mono.just(bytesSAR1)); when(this.webClient.post()).thenReturn(this.requestBodyUriMock); + when(this.graduationServiceWebClient.post()).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.uri(String.format(constants.getUpdateSchoolReport()))).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.headers(any(Consumer.class))).thenReturn(this.requestBodyMock); when(this.requestBodyMock.contentType(any())).thenReturn(this.requestBodyMock); @@ -2189,7 +2184,8 @@ public void testGetSchoolReports() { when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); when(this.responseMock.bodyToMono(SchoolReports.class)).thenReturn(Mono.just(new SchoolReports())); - when(this.restService.post(any(String.class), any(), any(), any())).thenReturn(bytesSAR3); + when(this.restService.post(any(String.class), any(), any(), any())).thenReturn(bytesSAR1); + when(this.restService.post(any(String.class), any(), any())).thenReturn(bytesSAR1); when(gradStatusService.getStudentListByMinCode(mincode, "accessToken")).thenReturn(sList); when(schoolService.getTraxSchoolDetails(mincode, "accessToken", exception)).thenReturn(sTrax); @@ -2253,9 +2249,9 @@ public void testCreateAndStoreSchoolReports_TVR() { byte[] bytesSAR = "Any String you want".getBytes(); - when(this.restService.post(any(String.class), any(), any(), any())).thenReturn(bytesSAR); + when(this.restService.post(any(String.class), any(), any())).thenReturn(bytesSAR); - when(this.webClient.post()).thenReturn(this.requestBodyUriMock); + when(this.graduationServiceWebClient.post()).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.uri(String.format(constants.getUpdateSchoolReport()))).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.headers(any(Consumer.class))).thenReturn(this.requestBodyMock); when(this.requestBodyMock.contentType(any())).thenReturn(this.requestBodyMock); diff --git a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/OptionalProgramServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/OptionalProgramServiceTest.java index ea98622e..39a02465 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/OptionalProgramServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/OptionalProgramServiceTest.java @@ -16,8 +16,13 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.reactive.function.BodyInserter; @@ -44,7 +49,21 @@ public class OptionalProgramServiceTest { GradValidation validation; @MockBean - WebClient webClient; + @Qualifier("graduationClient") + WebClient webClient; + + @TestConfiguration + static class TestConfig { + @Bean + public ClientRegistrationRepository clientRegistrationRepository() { + return new ClientRegistrationRepository() { + @Override + public ClientRegistration findByRegistrationId(String registrationId) { + return null; + } + }; + } + } @Autowired private EducGraduationApiConstants constants; diff --git a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/RESTServiceGETTest.java b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/RESTServiceGETTest.java index a1e83248..7333db62 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/RESTServiceGETTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/RESTServiceGETTest.java @@ -2,12 +2,19 @@ import ca.bc.gov.educ.api.graduation.exception.ServiceException; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; @@ -24,56 +31,80 @@ public class RESTServiceGETTest { @Autowired private RESTService restService; - @Mock + @MockBean private WebClient.RequestHeadersSpec requestHeadersMock; - @Mock + @MockBean private WebClient.RequestHeadersUriSpec requestHeadersUriMock; - @Mock + @MockBean private WebClient.RequestBodySpec requestBodyMock; - @Mock + @MockBean private WebClient.RequestBodyUriSpec requestBodyUriMock; - @Mock - private WebClient.ResponseSpec responseMock; @MockBean + private WebClient.ResponseSpec responseMock; + @MockBean(name = "webClient") WebClient webClient; - private static final byte[] TEST_BYTES = "The rain in Spain stays mainly on the plain.".getBytes(); + @MockBean(name = "graduationServiceWebClient") + @Qualifier("graduationClient") + WebClient graduationServiceWebClient; - @Test - public void testGet_GivenProperData_Expect200Response(){ + @MockBean + private ClientRegistrationRepository clientRegistrationRepositoryMock; + + @MockBean + private OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepositoryMock; + + private static final String TEST_URL_200 = "https://httpstat.us/200"; + private static final String TEST_URL_403 = "https://httpstat.us/403"; + private static final String TEST_URL_503 = "https://httpstat.us/503"; + private static final String OK_RESPONSE = "200 OK"; + + @Before + public void setUp(){ when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); + when(this.graduationServiceWebClient.get()).thenReturn(this.requestHeadersUriMock); when(this.requestHeadersUriMock.uri(any(String.class))).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.headers(any(Consumer.class))).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); when(this.responseMock.onStatus(any(), any())).thenReturn(this.responseMock); - when(this.responseMock.bodyToMono(String.class)).thenReturn(Mono.just("200 OK")); + } - String response = this.restService.get("https://httpstat.us/200", String.class, "1234"); + @Test + public void testGet_GivenProperData_Expect200Response(){ + when(this.responseMock.bodyToMono(String.class)).thenReturn(Mono.just(OK_RESPONSE)); + String response = this.restService.get(TEST_URL_200, String.class, "1234"); Assert.assertEquals("200 OK", response); } + @Test + public void testGetOverride_GivenProperData_Expect200Response(){ + when(this.responseMock.bodyToMono(String.class)).thenReturn(Mono.just(OK_RESPONSE)); + String response = this.restService.get(TEST_URL_200, String.class); + Assert.assertEquals(OK_RESPONSE, response); + } + @Test(expected = ServiceException.class) public void testGet_Given5xxErrorFromService_ExpectServiceError(){ - when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); - when(this.requestHeadersUriMock.uri(any(String.class))).thenReturn(this.requestHeadersMock); - when(this.requestHeadersMock.headers(any(Consumer.class))).thenReturn(this.requestHeadersMock); - when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); - when(this.responseMock.onStatus(any(), any())).thenReturn(this.responseMock); when(this.responseMock.bodyToMono(ServiceException.class)).thenReturn(Mono.just(new ServiceException())); + this.restService.get(TEST_URL_503, String.class, "1234"); + } - this.restService.get("https://httpstat.us/503", String.class, "1234"); + @Test(expected = ServiceException.class) + public void testGetOverride_Given5xxErrorFromService_ExpectServiceError(){ + when(this.responseMock.bodyToMono(ServiceException.class)).thenReturn(Mono.just(new ServiceException())); + this.restService.get(TEST_URL_503, String.class); } @Test(expected = ServiceException.class) public void testGet_Given4xxErrorFromService_ExpectServiceError(){ - when(this.webClient.get()).thenReturn(this.requestHeadersUriMock); - when(this.requestHeadersUriMock.uri(any(String.class))).thenReturn(this.requestHeadersMock); - when(this.requestHeadersMock.headers(any(Consumer.class))).thenReturn(this.requestHeadersMock); - when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); - when(this.responseMock.onStatus(any(), any())).thenReturn(this.responseMock); when(this.responseMock.bodyToMono(ServiceException.class)).thenReturn(Mono.just(new ServiceException())); + this.restService.get(TEST_URL_403, String.class, "1234"); + } - this.restService.get("https://httpstat.us/403", String.class, "1234"); + @Test(expected = ServiceException.class) + public void testGetOverride_Given4xxErrorFromService_ExpectServiceError(){ + when(this.responseMock.bodyToMono(ServiceException.class)).thenReturn(Mono.just(new ServiceException())); + this.restService.get(TEST_URL_403, String.class); } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/RESTServicePOSTTest.java b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/RESTServicePOSTTest.java index 56cfb242..dae657d4 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/RESTServicePOSTTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/RESTServicePOSTTest.java @@ -2,6 +2,7 @@ import ca.bc.gov.educ.api.graduation.exception.ServiceException; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.runner.RunWith; @@ -9,8 +10,14 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.client.WebClient; @@ -27,49 +34,70 @@ public class RESTServicePOSTTest { @Autowired - @InjectMocks private RESTService restService; - @Mock + @MockBean private WebClient.RequestHeadersSpec requestHeadersMock; - @Mock + @MockBean private WebClient.RequestBodySpec requestBodyMock; - @Mock + @MockBean private WebClient.RequestBodyUriSpec requestBodyUriMock; - @Mock - private WebClient.ResponseSpec responseMock; @MockBean + private WebClient.ResponseSpec responseMock; + @MockBean(name = "webClient") WebClient webClient; + @MockBean(name = "graduationServiceWebClient") + @Qualifier("graduationClient") + WebClient graduationServiceWebClient; + + @MockBean + ClientRegistrationRepository clientRegistrationRepository; + + @MockBean + OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository; + private static final byte[] TEST_BYTES = "The rain in Spain stays mainly on the plain.".getBytes(); + private static final String TEST_BODY = "{test:test}"; + private static final String ACCESS_TOKEN = "123"; + private static final String TEST_URL = "https://fake.url.com"; - @Test - public void testPost_GivenProperData_Expect200Response(){ - String testBody = "test"; + @Before + public void setUp(){ when(this.webClient.post()).thenReturn(this.requestBodyUriMock); + when(this.graduationServiceWebClient.post()).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.uri(any(String.class))).thenReturn(this.requestBodyUriMock); when(this.requestBodyUriMock.headers(any(Consumer.class))).thenReturn(this.requestBodyMock); when(this.requestBodyMock.contentType(any())).thenReturn(this.requestBodyMock); when(this.requestBodyMock.body(any(BodyInserter.class))).thenReturn(this.requestHeadersMock); when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); - when(this.responseMock.onStatus(any(), any())).thenReturn(this.responseMock); when(this.responseMock.bodyToMono(byte[].class)).thenReturn(Mono.just(TEST_BYTES)); - byte[] response = this.restService.post("https://fake.url.com", testBody, byte[].class, "1234"); + } + + @Test + public void testPost_GivenProperData_Expect200Response(){ + when(this.responseMock.onStatus(any(), any())).thenReturn(this.responseMock); + byte[] response = this.restService.post(TEST_URL, TEST_BODY, byte[].class, ACCESS_TOKEN); + Assert.assertArrayEquals(TEST_BYTES, response); + } + + @Test + public void testPostOverride_GivenProperData_Expect200Response(){ + when(this.responseMock.onStatus(any(), any())).thenReturn(this.responseMock); + byte[] response = this.restService.post(TEST_URL, TEST_BODY, byte[].class); Assert.assertArrayEquals(TEST_BYTES, response); } @Test(expected = ServiceException.class) public void testPost_Given4xxErrorFromService_ExpectServiceError() { - String testBody = "test"; - when(this.webClient.post()).thenReturn(this.requestBodyUriMock); - when(this.requestBodyUriMock.uri(any(String.class))).thenReturn(this.requestBodyUriMock); - when(this.requestBodyUriMock.headers(any(Consumer.class))).thenReturn(this.requestBodyMock); - when(this.requestBodyMock.contentType(any())).thenReturn(this.requestBodyMock); - when(this.requestBodyMock.body(any(BodyInserter.class))).thenReturn(this.requestHeadersMock); - when(this.requestHeadersMock.retrieve()).thenReturn(this.responseMock); when(this.responseMock.onStatus(any(), any())).thenThrow(new ServiceException()); - when(this.responseMock.bodyToMono(byte[].class)).thenReturn(Mono.just(TEST_BYTES)); - this.restService.post("https://fake.url.com", testBody, byte[].class, "1234"); + this.restService.post(TEST_URL, TEST_BODY, byte[].class, ACCESS_TOKEN); + } + + @Test(expected = ServiceException.class) + public void testPostOverride_Given4xxErrorFromService_ExpectServiceError() { + when(this.responseMock.onStatus(any(), any())).thenThrow(new ServiceException()); + this.restService.post(TEST_URL, TEST_BODY, byte[].class); } } diff --git a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/SchoolServiceTest.java b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/SchoolServiceTest.java index e1304588..82bc04a5 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/SchoolServiceTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/SchoolServiceTest.java @@ -76,7 +76,7 @@ public void testGetSchoolDetails() { SchoolTrax schtrax = new SchoolTrax(); schtrax.setMinCode(mincode); schtrax.setAddress1("1231"); - when(this.restService.get(any(String.class), any(), any())).thenReturn(schtrax); + when(this.restService.get(any(String.class), any())).thenReturn(schtrax); SchoolTrax res = schoolService.getTraxSchoolDetails(mincode, accessToken, new ExceptionMessage()); assertNotNull(res); assertEquals(res.getMinCode(),mincode); diff --git a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/TokenUtilsTest.java b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/TokenUtilsTest.java index 548798f6..7be2cef7 100644 --- a/api/src/test/java/ca/bc/gov/educ/api/graduation/service/TokenUtilsTest.java +++ b/api/src/test/java/ca/bc/gov/educ/api/graduation/service/TokenUtilsTest.java @@ -8,8 +8,13 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.reactive.function.BodyInserter; @@ -31,8 +36,22 @@ public class TokenUtilsTest { TokenUtils restUtils; @MockBean + @Qualifier("graduationClient") WebClient webClient; + @TestConfiguration + static class TestConfig { + @Bean + public ClientRegistrationRepository clientRegistrationRepository() { + return new ClientRegistrationRepository() { + @Override + public ClientRegistration findByRegistrationId(String registrationId) { + return null; + } + }; + } + } + @Autowired EducGraduationApiConstants educDistributionApiConstants; diff --git a/api/src/test/resources/application.yaml b/api/src/test/resources/application.yaml index 3277d80e..996af522 100644 --- a/api/src/test/resources/application.yaml +++ b/api/src/test/resources/application.yaml @@ -25,6 +25,16 @@ spring: jwt: issuer-uri: https://soam-dev.apps.silver.devops.gov.bc.ca/auth/realms/master jwk-set-uri: https://soam-dev.apps.silver.devops.gov.bc.ca/auth/realms/master/protocol/openid-connect/certs + client: + registration: + graduationclient: + client-id: my-client + client-secret: 123abc + authorization-grant-type: client_credentials + provider: + graduationclient: + issuer-uri: https://soam-dev.apps.silver.devops.gov.bc.ca/auth/realms/master + token-uri: https://soam-dev.apps.silver.devops.gov.bc.ca/auth/realms/master/protocol/openid-connect/token #Logging properties logging: @@ -105,8 +115,7 @@ endpoint: get-certificate-name: https://educ-grad-graduation-report-api-77c02f-test.apps.silver.devops.gov.bc.ca/api/v1/graduationreports/certificatetype/%s get-cert-list: https://educ-grad-graduation-report-api-77c02f-test.apps.silver.devops.gov.bc.ca/api/v1/graduationreports/programcertificates get-transcript: https://educ-grad-graduation-report-api-77c02f-test.apps.silver.devops.gov.bc.ca/api/v1/graduationreports/programtranscripts - educ-school-api: - url: https://school-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/schools/%s + report-api: achievement_report: https://report-api-77c02f-test.apps.silver.devops.gov.bc.ca/api/v1/reports/achievementreport transcript_report: https://report-api-77c02f-test.apps.silver.devops.gov.bc.ca/api/v1/reports/transcriptreport @@ -130,6 +139,8 @@ endpoint: by-pen: url: https://student-api-75e61b-dev.apps.silver.devops.gov.bc.ca/api/v1/student?pen=%s grad-trax-api: + commonschool-by-mincode: + url: https://educ-grad-trax-api-77c02f-dev.apps.silver.devops.gov.bc.ca/api/v1/trax/school/common/%s school-by-min-code: url: https://educ-grad-trax-api-77c02f-dev.apps.silver.devops.gov.bc.ca/api/v1/trax/school/%s district-by-min-code: diff --git a/tools/config/educ-grad-graduation-api-service.json b/tools/config/educ-grad-graduation-api-service.json new file mode 100644 index 00000000..8983fa7e --- /dev/null +++ b/tools/config/educ-grad-graduation-api-service.json @@ -0,0 +1,219 @@ +{ + "clientId": "educ-grad-graduation-api-service", + "name": "", + "description": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.multivalued.roles": "false", + "saml.force.post.binding": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "client_credentials.use_refresh_token": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "CREATE_GRAD_SPECIAL_PROGRAM_RULES_DATA", + "READ_GRAD_STUDENT_NOTES_DATA", + "UPDATE_GRAD_GRADUATION_STATUS", + "READ_GRAD_CAREER_PROGRAM_CODE_DATA", + "READ_GRAD_STUDENT_EXAM_DATA", + "READ_GRAD_SCHOOL_DATA", + "UPDATE_GRAD_TRAX_STUDENT_DATA", + "CREATE_GRAD_STUDENT_STATUS_CODE_DATA", + "WRITE_STUDENT", + "READ_GRAD_PROGRAM_RULES_DATA", + "READ_GRAD_DOCUMENT_STATUS_CODE_DATA", + "RUN_RULE_ENGINE", + "READ_GRAD_AND_PEN_STUDENT_DATA", + "READ_GRAD_COURSE_REQUIREMENT_DATA", + "CREATE_SCHOOL_NON_GRADUATION", + "READ_SIGNATURE_IMAGE_BY_CODE", + "READ_SCHOOL", + "READ_GRAD_CERTIFICATE_CODE_DATA", + "UPDATE_GRAD_PROGRAM_SETS_DATA", + "DELETE_GRAD_UNGRAD_CODE_DATA", + "READ_GRAD_MESSAGING_CODE_DATA", + "CREATE_GRAD_SPECIAL_PROGRAM_CODE_DATA", + "CREATE_GRAD_ASSESSMENT_REQUIREMENT_DATA", + "profile", + "DELETE_GRAD_BATCH_JOB_CODE_DATA", + "READ_GRAD_ALGORITHM_RULES_DATA", + "READ_GRAD_COUNTRY_CODE_DATA", + "UPDATE_GRAD_STUDENT_REPORT_DATA", + "READ_GRAD_SPECIAL_CASE_DATA", + "READ_GRAD_GRADUATION_STATUS", + "CREATE_STUDENT_NON_GRAD_REQ", + "CREATE_GRAD_STUDENT_NOTES_DATA", + "GET_GRADUATION_TRANSCRIPT", + "DELETE_GRAD_PROGRAM_CODE_DATA", + "DELETE_GRAD_STUDENT_STATUS_CODE_DATA", + "READ_GRAD_PROGRAM_CODE_DATA", + "LOAD_BATCH_DASHBOARD", + "READ_SIGNATURE_BLOCK_TYPE_CODE", + "UPDATE_GRAD_STUDENT_SPECIAL_DATA", + "READ_GRAD_STUDENT_UNGRAD_REASONS_DATA", + "CREATE_PACKING_SLIP", + "LOAD_STUDENT_IDS", + "CREATE_GRAD_STUDENT_UNGRAD_REASONS_DATA", + "GET_GRADUATION_DATA", + "DELETE_GRAD_SPECIAL_PROGRAM_RULES_DATA", + "READ_GRAD_STUDENT_COURSE_DATA", + "email", + "DELETE_GRAD_REQUIREMENT_TYPE_CODE_DATA", + "CREATE_STUDENT_TRANSCRIPT_REPORT", + "READ_GRAD_UNGRAD_CODE_DATA", + "CREATE_STUDENT_NON_GRAD", + "CREATE_GRAD_UNGRAD_CODE_DATA", + "READ_GRAD_STUDENT_DATA", + "READ_GRAD_REQUIREMENT_TYPE_CODE_DATA", + "UPDATE_GRAD_BATCH_JOB_CODE_DATA", + "UPDATE_GRAD_PROGRAM_RULES_DATA", + "DELETE_GRAD_CERTIFICATE_CODE_DATA", + "CREATE_GRAD_PROGRAM_TYPE_CODE_DATA", + "READ_GRAD_ASSESSMENT_REQUIREMENT_DATA", + "CREATE_SCHOOL_DISTRIBUTION", + "CREATE_GRAD_PROGRAM_CODE_DATA", + "READ_GRAD_LETTER_GRADE_DATA", + "DELETE_GRAD_REPORT_CODE_DATA", + "READ_GRAD_TRAX_COURSE_DATA", + "READ_GRAD_ASSESSMENT_DATA", + "CREATE_STUDENT_CERTIFICATE", + "GET_GRADUATION_ACHIEVEMENT", + "READ_GRAD_STUDENT_STATUS_CODE_DATA", + "UPDATE_GRAD_SPECIAL_PROGRAM_RULES_DATA", + "UPDATE_GRAD_COURSE_RESTRICTION_DATA", + "CREATE_GRAD_CAREER_PROGRAM_CODE_DATA", + "READ_GRAD_PROGRAM_TYPE_CODE_DATA", + "UPDATE_GRAD_STUDENT_CERTIFICATE_DATA", + "UPDATE_GRAD_PROGRAM_TYPE_CODE_DATA", + "CREATE_STUDENT_XML_TRANSCRIPT_REPORT", + "READ_GRAD_SPECIAL_PROGRAM_RULES_DATA", + "READ_GRAD_PROVINCE_CODE_DATA", + "DELETE_GRAD_CAREER_PROGRAM_CODE_DATA", + "CREATE_STUDENT_ACHIEVEMENT_REPORT", + "READ_GRAD_STUDENT_REPORT_DATA", + "UPDATE_GRAD_UNGRAD_CODE_DATA", + "CREATE_SCHOOL_LABEL", + "GRAD_BUSINESS_R", + "DELETE_GRAD_PROGRAM_TYPE_CODE_DATA", + "READ_GRAD_STUDENT_ASSESSMENT_DATA", + "UPDATE_GRAD_REPORT_CODE_DATA", + "DELETE_GRAD_PROGRAM_RULES_DATA", + "READ_GRAD_TRANSCRIPT_CODE_DATA", + "READ_GRAD_STUDENT_CERTIFICATE_DATA", + "UPDATE_GRAD_STUDENT_NOTES_DATA", + "CREATE_SCHOOL_GRADUATION", + "DELETE_GRAD_STUDENT_DATA", + "READ_GRAD_HISTORY_ACTIVITY_CODE_DATA", + "UPDATE_GRAD_PROGRAM_CODE_DATA", + "READ_GRAD_PSI_DATA", + "role_list", + "UPDATE_GRAD_SPECIAL_PROGRAM_CODE_DATA", + "CREATE_GRAD_PROGRAM_RULES_DATA", + "READ_GRAD_STUDENT_SPECIAL_DATA", + "READ_GRAD_TRAX_STUDENT_DATA", + "roles", + "READ_GRAD_STUDENT_CAREER_DATA", + "DELETE_GRAD_STUDENT_NOTES_DATA", + "READ_STUDENT", + "READ_GRAD_REPORT_CODE_DATA", + "CREATE_GRAD_CERTIFICATE_CODE_DATA", + "CREATE_OR_UPDATE_SIGNATURE_BLOCK_TYPE_CODE", + "READ_GRAD_BATCH_JOB_CODE_DATA", + "UPDATE_GRAD_CAREER_PROGRAM_CODE_DATA", + "UPDATE_GRAD_CERTIFICATE_CODE_DATA", + "CREATE_OR_UPDATE_SIGNATURE_IMAGE", + "READ_GRAD_SPECIAL_PROGRAM_CODE_DATA", + "CREATE_GRAD_REQUIREMENT_TYPE_CODE_DATA", + "READ_GRAD_COURSE_RESTRICTION_DATA", + "UPDATE_GRAD_REQUIREMENT_TYPE_CODE_DATA", + "READ_GRAD_COURSE_DATA", + "RUN_GRAD_ALGORITHM", + "GET_GRADUATION_CERTIFICATE", + "web-origins", + "DELETE_GRAD_SPECIAL_PROGRAM_CODE_DATA", + "CREATE_GRAD_REPORT_CODE_DATA", + "CREATE_GRAD_BATCH_JOB_CODE_DATA", + "DELETE_GRAD_STUDENT_REPORTS", + "UPDATE_GRAD_STUDENT_STATUS_CODE_DATA" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "access": { + "view": true, + "configure": true, + "manage": true + } +} \ No newline at end of file diff --git a/tools/config/update-kc-client.sh b/tools/config/update-kc-client.sh new file mode 100644 index 00000000..55e0796a --- /dev/null +++ b/tools/config/update-kc-client.sh @@ -0,0 +1,95 @@ +# IAC script for KC Client +# ENVS +CLIENT_SECRET_NAME=grad-graduation-api-client-secret +ENV=$1 +COMMON_NAMESPACE=$2 +SOAM_KC_REALM_ID=$3 +CLIENT_ID=$4 +BRANCH=$5 +REPO_NAME=$6 +SOAM_KC=soam-$ENV.apps.silver.devops.gov.bc.ca + +SOAM_KC_LOAD_USER_ADMIN=$(oc -n $COMMON_NAMESPACE-$ENV -o json get secret sso-admin-${ENV} | sed -n 's/.*"username": "\(.*\)"/\1/p' | base64 --decode) +SOAM_KC_LOAD_USER_PASS=$(oc -n $COMMON_NAMESPACE-$ENV -o json get secret sso-admin-${ENV} | sed -n 's/.*"password": "\(.*\)",/\1/p' | base64 --decode) + +#### Function declarations +# Retrieves the keycloak client UUID +function fetchClientUUID() { + echo Retrieving client UUID for $CLIENT_ID + CLIENT_UUID=$(curl -sX GET "https://$SOAM_KC/auth/admin/realms/$SOAM_KC_REALM_ID/clients" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TKN" \ + | jq '.[] | select(.clientId=="'"$CLIENT_ID"'")' | jq -r '.id') +} + +# Fetch client credentials +function fetchClientCredentials() { + echo Fetching client credentials... + SERVICE_CLIENT_SECRET=$(curl -sX GET "https://$SOAM_KC/auth/admin/realms/$SOAM_KC_REALM_ID/clients/$CLIENT_UUID/client-secret" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TKN" \ + | jq -r '.value') +} + +# Creates the oc client secret +function createClientSecret() { + echo Creating secret for client + oc create secret generic $CLIENT_SECRET_NAME \ + --from-literal=GRAD_GRADUATION_API_CLIENT_NAME=$CLIENT_ID \ + --from-literal=GRAD_GRADUATION_API_CLIENT_SECRET=$SERVICE_CLIENT_SECRET \ + --dry-run=client -o yaml | oc apply -f - +} + +#### Begin +echo Fetching SOAM token +TKN=$(curl -s \ + -d "client_id=admin-cli" \ + -d "username=$SOAM_KC_LOAD_USER_ADMIN" \ + -d "password=$SOAM_KC_LOAD_USER_PASS" \ + -d "grant_type=password" \ + "https://$SOAM_KC/auth/realms/$SOAM_KC_REALM_ID/protocol/openid-connect/token" | jq -r '.access_token') + +# Try getting the UUID +fetchClientUUID + +if [ "$CLIENT_UUID" = "" ] +then + # Client not found + echo "$CLIENT_ID DOES NOT EXIST IN KEYCLOAK! A new client with be created with a new access key. MAKE SURE TO ADD GRAD_SYSTEM_COORDINATOR Role to new client! Creating..." + # Retrieve json, remove secret field if exists (shouldn't) + CLIENT_JSON=$(curl -s https://raw.githubusercontent.com/bcgov/$REPO_NAME/$BRANCH/tools/config/$CLIENT_ID.json | jq -c 'del(.secret)') + # Create client + curl -sX POST "https://$SOAM_KC/auth/admin/realms/$SOAM_KC_REALM_ID/clients" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TKN" \ + -d "$CLIENT_JSON" + # Get the UUID + fetchClientUUID + # Fetch generated credentials + fetchClientCredentials + # Create or update client secret on OS + createClientSecret +else + # Get Credentials + fetchClientCredentials + # Ensure secret + createClientSecret + ##### IMPORTANT + ## No longer removing old client until clients can be freed from roles + + # Turf the old client + #echo Removing existing client... + #curl -sX DELETE "https://$SOAM_KC/auth/admin/realms/$SOAM_KC_REALM_ID/clients/$CLIENT_UUID" \ + # -H "Authorization: Bearer $TKN" + # Recreate the client wth updated info + #echo Creating new client with credentials + # Get JSON and inject secret + #CLIENT_JSON=$(curl -s https://raw.githubusercontent.com/bcgov/$REPO_NAME/$BRANCH/tools/config/$CLIENT_ID.json | jq -c --arg secret "$SERVICE_CLIENT_SECRET" '.secret = $secret') + #curl -sX POST "https://$SOAM_KC/auth/admin/realms/$SOAM_KC_REALM_ID/clients" \ + # -H "Content-Type: application/json" \ + # -H "Authorization: Bearer $TKN" \ + # -d "$CLIENT_JSON" +fi + + + diff --git a/tools/openshift/api.dc.yaml b/tools/openshift/api.dc.yaml index 2e0173d5..b56285e3 100644 --- a/tools/openshift/api.dc.yaml +++ b/tools/openshift/api.dc.yaml @@ -66,6 +66,8 @@ objects: name: api-student-graduation-db-secret - secretRef: name: grad-client-secret + - secretRef: + name: grad-graduation-api-client-secret resources: requests: cpu: "${MIN_CPU}"