diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 6ade109b..87fbb70a 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -58,7 +58,7 @@ jobs: - name: CVE Description escaped extraction and print if: always() run: | - SCAN_RESULTS=$(jq -r '.runs[0].tool.driver.rules | map(.shortDescription.text) | join("\\n")' ${{ steps.scan.outputs.sarif }}) + SCAN_RESULTS=$(jq -r 'try .runs[0].tool.driver.rules | map(.shortDescription.text) | join("\\n")' ${{ steps.scan.outputs.sarif }}) echo "SCAN_RESULTS=$SCAN_RESULTS" >> $GITHUB_ENV echo "CVE_CRITICAL=$(echo $SCAN_RESULTS | grep -o critical | wc -l)" >> $GITHUB_ENV echo "CVE_HIGH=$(echo $SCAN_RESULTS | grep -o high | wc -l)" >> $GITHUB_ENV diff --git a/Dockerfile b/Dockerfile index da26a300..73c386f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,9 @@ RUN mvn clean package -DskipTests # FROM amazoncorretto:17.0.9-alpine3.18@sha256:df48bf2e183230040890460ddb4359a10aa6c7aad24bd88899482c52053c7e17 AS runtime +# security fixes +RUN apk update && apk upgrade --no-cache libcrypto3 libssl3 + RUN apk --no-cache add shadow RUN useradd --uid 10000 runner diff --git a/helm/Chart.lock b/helm/Chart.lock index 00827f96..0de02e6c 100644 --- a/helm/Chart.lock +++ b/helm/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: microservice-chart repository: https://pagopa.github.io/aks-microservice-chart-blueprint - version: 2.8.0 -digest: sha256:379d9a7c312874dd1771386d92d8f597cb3fed497bb80dfde102513b582123d4 -generated: "2023-09-05T10:18:40.513777+02:00" + version: 5.0.0 +digest: sha256:acce690c924529c84ccd541dd046bf2fa8e6d379d0bfe6f2e200d95466cdb253 +generated: "2023-11-29T10:53:28.3550118+01:00" diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 223adaf7..2c4e83c1 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -6,5 +6,5 @@ version: 1.0.0 appVersion: 1.0.0 dependencies: - name: microservice-chart - version: 2.8.0 + version: 5.0.0 repository: "https://pagopa.github.io/aks-microservice-chart-blueprint" diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index 24ea570f..e6930a1e 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -37,7 +37,7 @@ microservice-chart: tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" envConfig: - JAVA_TOOL_OPTIONS: "-Xms128m -Xmx4g -javaagent:/app/applicationinsights-agent.jar -agentlib:jdwp=transport=dt_socket,server=y,address=8001,suspend=n -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=3002 -Dcom.sun.management.jmxremote.rmi.port=3003 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" + JAVA_TOOL_OPTIONS: "-Xms128m -Xmx4g -Djava.util.concurrent.ForkJoinPool.common.parallelism=7 -Dio.netty.eventLoopThreads=100 -javaagent:/app/applicationinsights-agent.jar -Dapplicationinsights.configuration.file=/mnt/file-config-external/appinsights-config/applicationinsights.json -agentlib:jdwp=transport=dt_socket,server=y,address=8001,suspend=n -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=3002 -Dcom.sun.management.jmxremote.rmi.port=3003 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" CACHE_REFRESH_MS_RATE: "60000" REDIS_CACHE_ENABLED: "true" diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index 3995ebbe..20fc9e60 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -36,7 +36,7 @@ microservice-chart: tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" envConfig: - JAVA_TOOL_OPTIONS: "-Xms128m -Xmx4g -javaagent:/app/applicationinsights-agent.jar" + JAVA_TOOL_OPTIONS: "-Xms128m -Xmx4g -Djava.util.concurrent.ForkJoinPool.common.parallelism=7 -Dio.netty.eventLoopThreads=100 -javaagent:/app/applicationinsights-agent.jar -Dapplicationinsights.configuration.file=/mnt/file-config-external/appinsights-config/applicationinsights.json" CACHE_REFRESH_MS_RATE: "60000" REDIS_CACHE_ENABLED: "true" diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index 343a4209..6aaf1545 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -38,8 +38,8 @@ microservice-chart: tenantId: "7788edaf-0346-4068-9d79-c868aed15b3d" envConfig: - JAVA_TOOL_OPTIONS: "-Xms128m -Xmx4g -javaagent:/app/applicationinsights-agent.jar -agentlib:jdwp=transport=dt_socket,server=y,address=8001,suspend=n -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=3002 -Dcom.sun.management.jmxremote.rmi.port=3003 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" - CACHE_REFRESH_MS_RATE: "60000" + JAVA_TOOL_OPTIONS: '-Xms128m -Xmx4g -javaagent:/app/applicationinsights-agent.jar -Dapplicationinsights.configuration.file=/mnt/file-config-external/appinsights-config/applicationinsights.json' + CACHE_REFRESH_MS_RATE: "10000" REDIS_CACHE_ENABLED: "true" #PDND_BASE_URL: https://auth.uat.interop.pagopa.it diff --git a/helm/values.yaml b/helm/values.yaml index aa786ebf..c01f2445 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -65,7 +65,7 @@ microservice-chart: DELETE_PAGINATION_SIZE: "60" DELETE_DELAY_TIME: "1000" - envConfigMapExternals: + externalConfigMapValues: idpay-common: TZ: TZ idpay-eventhub-00: @@ -107,4 +107,10 @@ microservice-chart: # tolerations: [] - # affinity: {} \ No newline at end of file + # affinity: {} + + externalConfigMapFiles: + create: true + configMaps: + - name: appinsights-config + key: applicationinsights.json \ No newline at end of file diff --git a/pom.xml b/pom.xml index ba6dd1fd..c3a2ebd9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ it.gov.pagopa idpay-admissibility-assessor idpay-admissibility-assessor - 1.2.1 + 1.2.3 17 diff --git a/src/main/java/it/gov/pagopa/admissibility/drools/transformer/extra_filter/ExtraFilter2DroolsUtils.java b/src/main/java/it/gov/pagopa/admissibility/drools/transformer/extra_filter/ExtraFilter2DroolsUtils.java index 51ac56e5..d9695788 100644 --- a/src/main/java/it/gov/pagopa/admissibility/drools/transformer/extra_filter/ExtraFilter2DroolsUtils.java +++ b/src/main/java/it/gov/pagopa/admissibility/drools/transformer/extra_filter/ExtraFilter2DroolsUtils.java @@ -72,48 +72,49 @@ private static List buildExtraFilterFields(final Class claz final List out = new ArrayList<>(); final Set fieldsAdded = new HashSet<>(); - ReflectionUtils.doWithMethods(clazz, m -> checkIfGetter2AnalyzeAndRetrieveFieldInfo(m, path, castPath, class2subclass, path2ignore, maxDepth, out, fieldsAdded), + ReflectionUtils.doWithMethods(clazz, m -> checkIfGetter2AnalyzeAndRetrieveFieldInfo(m, class2subclass, path2ignore, maxDepth, out, fieldsAdded, + generateExtraFilterField(path, castPath)), m -> m.getParameterTypes().length == 0 && (m.getName().startsWith("get") || m.getName().startsWith("is"))); return out; } - private static void checkIfGetter2AnalyzeAndRetrieveFieldInfo(Method m, String path, Class castPath, Map, List>> class2subclass, Set path2ignore, int maxDepth, List out, Set fieldsAdded) { - Class fieldType = m.getReturnType(); - if (class2notAnalyze.stream().anyMatch(c -> c.isAssignableFrom(fieldType))) { + private static ExtraFilterField generateExtraFilterField(String path, Class castPath){ + ExtraFilterField eff = new ExtraFilterField(); + eff.setPath(path); + eff.setCastPath(castPath); + return eff; + } + + private static void checkIfGetter2AnalyzeAndRetrieveFieldInfo(Method m, Map, List>> class2subclass, Set path2ignore, int maxDepth, List out, Set fieldsAdded, ExtraFilterField eff) { + eff.setType(m.getReturnType()); + if (class2notAnalyze.stream().anyMatch(c -> c.isAssignableFrom(eff.getType()))) { return; } - String fieldName = StringUtils.uncapitalize(m.getName().replaceFirst("^(?:get|is)", "")); - String fullFieldName = path != null ? String.format("%s.%s", path, fieldName) : fieldName; - if (!Modifier.isStatic(m.getModifiers()) && !fieldsAdded.contains(fullFieldName) && (path2ignore == null || !path2ignore.contains(fullFieldName))) { - extractFieldInfo(path, castPath, class2subclass, path2ignore, maxDepth, out, fieldsAdded, fieldType, fieldName, fullFieldName); + eff.setName(StringUtils.uncapitalize(m.getName().replaceFirst("^(?:get|is)", ""))); + eff.setField(eff.getPath() != null ? String.format("%s.%s", eff.getPath(), eff.getName()) : eff.getName()); + if (!Modifier.isStatic(m.getModifiers()) && !fieldsAdded.contains(eff.getField()) && (path2ignore == null || !path2ignore.contains(eff.getField()))) { + extractFieldInfo(class2subclass, path2ignore, maxDepth, out, fieldsAdded, eff); } } - private static void extractFieldInfo(String path, Class castPath, Map, List>> class2subclass, Set path2ignore, int maxDepth, List out, Set fieldsAdded, Class fieldType, String fieldName, String fullFieldName) { - ExtraFilterField eff = new ExtraFilterField(); - eff.setPath(path); - eff.setName(fieldName); - eff.setField(fullFieldName); - eff.setType(fieldType); - eff.setCastPath(castPath); - + private static void extractFieldInfo(Map, List>> class2subclass, Set path2ignore, int maxDepth, List out, Set fieldsAdded, ExtraFilterField eff) { out.add(eff); - fieldsAdded.add(fullFieldName); + fieldsAdded.add(eff.getField()); - List> subclasses = class2subclass == null ? null : class2subclass.get(fieldType); + List> subclasses = class2subclass == null ? null : class2subclass.get(eff.getType()); if (!CollectionUtils.isEmpty(subclasses)) { eff.setToCast(true); eff.setSubclasses(subclasses); for (Class s : subclasses) { - if (fieldType.isAssignableFrom(s)) { - out.addAll(buildExtraFilterFields(s, String.format("%s(%s)%s", path == null ? "" : String.format("%s.", path), s.getName(), fieldName), s, class2subclass, path2ignore, maxDepth)); + if (eff.getType().isAssignableFrom(s)) { + out.addAll(buildExtraFilterFields(s, String.format("%s(%s)%s", eff.getPath() == null ? "" : String.format("%s.", eff.getPath()), s.getName(), eff.getName()), s, class2subclass, path2ignore, maxDepth)); } else { - throw new IllegalArgumentException(String.format("The configured class '%s' is not a subclass of '%s'", s.getName(), fieldType)); + throw new IllegalArgumentException(String.format("The configured class '%s' is not a subclass of '%s'", s.getName(), eff.getType())); } } - } else if (!fieldType.isPrimitive() && class2notExplore.stream().noneMatch(c -> c.isAssignableFrom(fieldType))) { - out.addAll(buildExtraFilterFields(fieldType, fullFieldName, null, class2subclass, path2ignore, maxDepth)); + } else if (!eff.getType().isPrimitive() && class2notExplore.stream().noneMatch(c -> c.isAssignableFrom(eff.getType()))) { + out.addAll(buildExtraFilterFields(eff.getType(), eff.getField(), null, class2subclass, path2ignore, maxDepth)); } } } diff --git a/src/main/java/it/gov/pagopa/admissibility/dto/onboarding/EvaluationCompletedDTO.java b/src/main/java/it/gov/pagopa/admissibility/dto/onboarding/EvaluationCompletedDTO.java index 25492b46..3e4a2838 100644 --- a/src/main/java/it/gov/pagopa/admissibility/dto/onboarding/EvaluationCompletedDTO.java +++ b/src/main/java/it/gov/pagopa/admissibility/dto/onboarding/EvaluationCompletedDTO.java @@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; import lombok.AllArgsConstructor; import lombok.Data; @@ -27,7 +28,7 @@ public class EvaluationCompletedDTO extends EvaluationDTO{ @NotEmpty private OnboardingEvaluationStatus status; @NotNull - private List onboardingRejectionReasons; + private List onboardingRejectionReasons = new ArrayList<>(); private BigDecimal beneficiaryBudget; @JsonIgnore private Long rankingValue; diff --git a/src/main/java/it/gov/pagopa/admissibility/mapper/Onboarding2EvaluationMapper.java b/src/main/java/it/gov/pagopa/admissibility/mapper/Onboarding2EvaluationMapper.java index be9c29ad..93f9fdd9 100644 --- a/src/main/java/it/gov/pagopa/admissibility/mapper/Onboarding2EvaluationMapper.java +++ b/src/main/java/it/gov/pagopa/admissibility/mapper/Onboarding2EvaluationMapper.java @@ -32,11 +32,16 @@ private EvaluationCompletedDTO getEvaluationCompletedDTO(OnboardingDTO onboardin out.setFamilyId(getFamilyId(onboardingDTO)); out.setMemberIds(getFamilyMembers(onboardingDTO)); out.setInitiativeId(onboardingDTO.getInitiativeId()); - out.setStatus(CollectionUtils.isEmpty(rejectionReasons) ? OnboardingEvaluationStatus.ONBOARDING_OK : OnboardingEvaluationStatus.ONBOARDING_KO); out.setAdmissibilityCheckDate(LocalDateTime.now()); - out.setOnboardingRejectionReasons(rejectionReasons); out.setCriteriaConsensusTimestamp(onboardingDTO.getCriteriaConsensusTimestamp()); + if(CollectionUtils.isEmpty(rejectionReasons)){ + out.setStatus(OnboardingEvaluationStatus.ONBOARDING_OK); + } else { + out.setStatus(OnboardingEvaluationStatus.ONBOARDING_KO); + out.getOnboardingRejectionReasons().addAll(rejectionReasons); + } + if(initiative != null){ out.setInitiativeName(initiative.getInitiativeName()); out.setInitiativeEndDate(initiative.getEndDate()); @@ -78,7 +83,11 @@ private static Set getFamilyMembers(OnboardingDTO onboardingDTO) { private static void setRankingValue(OnboardingDTO onboardingDTO, InitiativeConfig initiative, EvaluationDTO out) { if(initiative.isRankingInitiative() && !initiative.getRankingFields().isEmpty()){ - out.setRankingValue(initiative.getRankingFields().get(0).getFieldCode().equals(OnboardingConstants.CRITERIA_CODE_ISEE) ? CommonUtilities.euroToCents(onboardingDTO.getIsee()) : -1); + long rankingValue = -1L; + if(initiative.getRankingFields().get(0).getFieldCode().equals(OnboardingConstants.CRITERIA_CODE_ISEE) && onboardingDTO.getIsee() != null){ + rankingValue = CommonUtilities.euroToCents(onboardingDTO.getIsee()); + } + out.setRankingValue(rankingValue); } } diff --git a/src/main/java/it/gov/pagopa/admissibility/service/onboarding/AdmissibilityEvaluatorMediatorServiceImpl.java b/src/main/java/it/gov/pagopa/admissibility/service/onboarding/AdmissibilityEvaluatorMediatorServiceImpl.java index 805ce4ee..b880b258 100644 --- a/src/main/java/it/gov/pagopa/admissibility/service/onboarding/AdmissibilityEvaluatorMediatorServiceImpl.java +++ b/src/main/java/it/gov/pagopa/admissibility/service/onboarding/AdmissibilityEvaluatorMediatorServiceImpl.java @@ -8,8 +8,8 @@ import it.gov.pagopa.admissibility.dto.rule.InitiativeGeneralDTO; import it.gov.pagopa.admissibility.enums.OnboardingEvaluationStatus; import it.gov.pagopa.admissibility.exception.OnboardingException; -import it.gov.pagopa.admissibility.exception.WaitingFamilyOnBoardingException; import it.gov.pagopa.admissibility.exception.SkipAlreadyRankingFamilyOnBoardingException; +import it.gov.pagopa.admissibility.exception.WaitingFamilyOnBoardingException; import it.gov.pagopa.admissibility.mapper.Onboarding2EvaluationMapper; import it.gov.pagopa.admissibility.model.InitiativeConfig; import it.gov.pagopa.admissibility.service.AdmissibilityErrorNotifierService; @@ -25,6 +25,7 @@ import it.gov.pagopa.common.utils.CommonUtilities; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; import org.springframework.messaging.Message; import org.springframework.stereotype.Service; @@ -40,6 +41,7 @@ @Service @Slf4j public class AdmissibilityEvaluatorMediatorServiceImpl implements AdmissibilityEvaluatorMediatorService { + private static final List REJECTION_REASON_CHECK_DATE_FAIL = List.of(OnboardingConstants.REJECTION_REASON_TC_CONSENSUS_DATETIME_FAIL, OnboardingConstants.REJECTION_REASON_CRITERIA_CONSENSUS_DATETIME_FAIL); private final int maxOnboardingRequestRetry; @@ -162,10 +164,10 @@ private Mono execute(Message message, OnboardingDTO onboa if (onboardingRequest != null) { EvaluationDTO rejectedRequest = evaluateOnboardingChecks(onboardingRequest, initiativeConfig, onboardingContext); if (rejectedRequest != null) { - return Mono.just(rejectedRequest); + return checkRejectionType(message, onboardingRequest, initiativeConfig, rejectedRequest); } else { log.debug("[ONBOARDING_REQUEST] [ONBOARDING_CHECK] onboarding of user {} into initiative {} resulted into successful preliminary checks", onboardingRequest.getUserId(), onboardingRequest.getInitiativeId()); - return checkOnboardingFamily(onboardingRequest, initiativeConfig, message) + return checkOnboardingFamily(onboardingRequest, initiativeConfig, message, true) .switchIfEmpty(retrieveAuthoritiesDataAndEvaluateRequest(onboardingRequest, initiativeConfig, message)) .onErrorResume(WaitingFamilyOnBoardingException.class, e -> Mono.empty()) @@ -197,6 +199,20 @@ private Mono execute(Message message, OnboardingDTO onboa } } + @NotNull + private Mono checkRejectionType(Message message, OnboardingDTO onboardingRequest, InitiativeConfig initiativeConfig, EvaluationDTO rejectedRequest) { + if(rejectedRequest instanceof EvaluationCompletedDTO completedDTO + && initiativeConfig !=null + && InitiativeGeneralDTO.BeneficiaryTypeEnum.NF.equals(initiativeConfig.getBeneficiaryType()) + && completedDTO.getOnboardingRejectionReasons().stream().anyMatch(o -> REJECTION_REASON_CHECK_DATE_FAIL.contains(o.getCode()))){ + return checkOnboardingFamily(onboardingRequest, initiativeConfig, message, false) + .switchIfEmpty(Mono.just(rejectedRequest)) + .onErrorResume(WaitingFamilyOnBoardingException.class, e -> Mono.empty()) + .onErrorResume(SkipAlreadyRankingFamilyOnBoardingException.class, e -> Mono.empty()); + } + return Mono.just(rejectedRequest); + } + private static String readRetryHeader(Message message) { Object retryHeader = message.getHeaders().get(KafkaConstants.ERROR_MSG_HEADER_RETRY); @@ -223,9 +239,9 @@ private EvaluationDTO evaluateOnboardingChecks(OnboardingDTO onboardingRequest, } else return null; } - private Mono checkOnboardingFamily(OnboardingDTO onboardingRequest, InitiativeConfig initiativeConfig, Message message) { + private Mono checkOnboardingFamily(OnboardingDTO onboardingRequest, InitiativeConfig initiativeConfig, Message message, boolean retrieveFamily) { if(isFamilyInitiative(initiativeConfig)){ - return onboardingFamilyEvaluationService.checkOnboardingFamily(onboardingRequest, initiativeConfig, message); + return onboardingFamilyEvaluationService.checkOnboardingFamily(onboardingRequest, initiativeConfig, message, retrieveFamily); } else { return Mono.empty(); } diff --git a/src/main/java/it/gov/pagopa/admissibility/service/onboarding/check/OnboardingConsensusCheck.java b/src/main/java/it/gov/pagopa/admissibility/service/onboarding/check/OnboardingConsensusCheck.java index 864a3f8c..636f573a 100644 --- a/src/main/java/it/gov/pagopa/admissibility/service/onboarding/check/OnboardingConsensusCheck.java +++ b/src/main/java/it/gov/pagopa/admissibility/service/onboarding/check/OnboardingConsensusCheck.java @@ -37,24 +37,7 @@ private String checkConsensusErrors(OnboardingDTO onboardingRequest) { return OnboardingConstants.REJECTION_REASON_CONSENSUS_PDND_FAIL; } - /* - if (!CollectionUtils.isEmpty(onboardingRequest.getSelfDeclarationList())) { - return selfDeclarationListCheck(onboardingRequest.getSelfDeclarationList()); - } - */ - return null; - } - - //Handle multi and boolean criteria - /* - private String selfDeclarationListCheck(Map selfDeclarationList) { - for (Map.Entry selfDeclaration : selfDeclarationList.entrySet()) { - if (Boolean.FALSE.equals(selfDeclaration.getValue())) { - return String.format(OnboardingConstants.REJECTION_REASON_CONSENSUS_CHECK_SELF_DECLARATION_FAIL_FORMAT, selfDeclaration.getKey()); - } - } return null; } - */ } diff --git a/src/main/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationService.java b/src/main/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationService.java index 58b1a441..0b834991 100644 --- a/src/main/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationService.java +++ b/src/main/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationService.java @@ -12,6 +12,6 @@ * If already exists an onboarding it will demand to {@link ExistentFamilyHandlerService} to handle the request, otherwise to {@link FamilyDataRetrieverFacadeService} * */ public interface OnboardingFamilyEvaluationService { - Mono checkOnboardingFamily(OnboardingDTO onboardingRequest, InitiativeConfig initiativeConfig, Message message); + Mono checkOnboardingFamily(OnboardingDTO onboardingRequest, InitiativeConfig initiativeConfig, Message message, boolean retrieveFamily); Mono updateOnboardingFamilyOutcome(Family family, InitiativeConfig initiativeConfig, EvaluationDTO result); } diff --git a/src/main/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationServiceImpl.java b/src/main/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationServiceImpl.java index ad97e111..7d97bd30 100644 --- a/src/main/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationServiceImpl.java +++ b/src/main/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationServiceImpl.java @@ -1,5 +1,6 @@ package it.gov.pagopa.admissibility.service.onboarding.family; +import it.gov.pagopa.admissibility.connector.repository.OnboardingFamiliesRepository; import it.gov.pagopa.admissibility.dto.onboarding.EvaluationCompletedDTO; import it.gov.pagopa.admissibility.dto.onboarding.EvaluationDTO; import it.gov.pagopa.admissibility.dto.onboarding.OnboardingDTO; @@ -8,7 +9,6 @@ import it.gov.pagopa.admissibility.enums.OnboardingFamilyEvaluationStatus; import it.gov.pagopa.admissibility.model.InitiativeConfig; import it.gov.pagopa.admissibility.model.OnboardingFamilies; -import it.gov.pagopa.admissibility.connector.repository.OnboardingFamiliesRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.Message; import org.springframework.stereotype.Service; @@ -35,14 +35,16 @@ public OnboardingFamilyEvaluationServiceImpl(OnboardingFamiliesRepository onboar } @Override - public Mono checkOnboardingFamily(OnboardingDTO onboardingRequest, InitiativeConfig initiativeConfig, Message message) { + public Mono checkOnboardingFamily(OnboardingDTO onboardingRequest, InitiativeConfig initiativeConfig, Message message, boolean retrieveFamily) { log.debug("[ONBOARDING_REQUEST] Checking if user family has been onboarded: userId {}; initiativeId {}", onboardingRequest.getUserId(), onboardingRequest.getInitiativeId()); return onboardingFamiliesRepository.findByMemberIdsInAndInitiativeId(onboardingRequest.getUserId(), onboardingRequest.getInitiativeId()) .collectSortedList(COMPARATOR_FAMILIES_CREATE_DATE_DESC) .flatMap(f -> { if (f.isEmpty()) { - return familyDataRetrieverFacadeService.retrieveFamily(onboardingRequest, initiativeConfig, message); + return retrieveFamily ? + familyDataRetrieverFacadeService.retrieveFamily(onboardingRequest, initiativeConfig, message) + : Mono.empty(); } else { return existentFamilyHandlerService.handleExistentFamily(onboardingRequest, f.get(0), initiativeConfig, message); } diff --git a/src/main/java/it/gov/pagopa/common/config/HealthIndicatorLogger.java b/src/main/java/it/gov/pagopa/common/config/HealthIndicatorLogger.java index df80a49f..78588ca6 100644 --- a/src/main/java/it/gov/pagopa/common/config/HealthIndicatorLogger.java +++ b/src/main/java/it/gov/pagopa/common/config/HealthIndicatorLogger.java @@ -1,7 +1,6 @@ package it.gov.pagopa.common.config; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.Status; @@ -13,8 +12,11 @@ @Service public class HealthIndicatorLogger implements HealthIndicator { - @Autowired - private List healthIndicatorList; + private final List healthIndicatorList; + + public HealthIndicatorLogger(List healthIndicatorList) { + this.healthIndicatorList = healthIndicatorList; + } @Override public Health health() { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 02207e3a..f365b6c2 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -237,6 +237,7 @@ logging: org.hibernate: ${LOG_LEVEL_ORG_HIBERNATE:INFO} org.kie: ${LOG_LEVEL_ORG_KIE:WARN} org.drools: ${LOG_LEVEL_ORG_DROOLS:WARN} + org.drools.mvel.MVELConstraint: ${LOG_LEVEL_ORG_DROOLS_COMPILE:ERROR} org.mongodb.driver: ${LOG_LEVEL_MONGODB_DRIVER:WARN} app: diff --git a/src/test/java/it/gov/pagopa/admissibility/service/build/BeneficiaryRule2DroolsRuleImplTest.java b/src/test/java/it/gov/pagopa/admissibility/service/build/BeneficiaryRule2DroolsRuleImplTest.java index adfa9bab..c735d979 100644 --- a/src/test/java/it/gov/pagopa/admissibility/service/build/BeneficiaryRule2DroolsRuleImplTest.java +++ b/src/test/java/it/gov/pagopa/admissibility/service/build/BeneficiaryRule2DroolsRuleImplTest.java @@ -192,7 +192,6 @@ void testExecution(List failingCode) { expectedEvaluationResult.setInitiativeEndDate(LocalDate.of(2025, 12, 1)); expectedEvaluationResult.setBeneficiaryBudget(new BigDecimal("1000.00")); expectedEvaluationResult.setIsLogoPresent(Boolean.TRUE); - expectedEvaluationResult.setOnboardingRejectionReasons(new ArrayList<>()); if (expectedIseeFail) { expectedEvaluationResult.getOnboardingRejectionReasons().add(OnboardingRejectionReason.builder() .type(OnboardingRejectionReason.OnboardingRejectionReasonType.AUTOMATED_CRITERIA_FAIL) diff --git a/src/test/java/it/gov/pagopa/admissibility/service/onboarding/AdmissibilityEvaluatorMediatorServiceImplTest.java b/src/test/java/it/gov/pagopa/admissibility/service/onboarding/AdmissibilityEvaluatorMediatorServiceImplTest.java index f2cacb24..2b5d12d1 100644 --- a/src/test/java/it/gov/pagopa/admissibility/service/onboarding/AdmissibilityEvaluatorMediatorServiceImplTest.java +++ b/src/test/java/it/gov/pagopa/admissibility/service/onboarding/AdmissibilityEvaluatorMediatorServiceImplTest.java @@ -12,6 +12,7 @@ import it.gov.pagopa.admissibility.exception.WaitingFamilyOnBoardingException; import it.gov.pagopa.admissibility.mapper.Onboarding2EvaluationMapper; import it.gov.pagopa.admissibility.model.InitiativeConfig; +import it.gov.pagopa.admissibility.model.Order; import it.gov.pagopa.admissibility.service.AdmissibilityErrorNotifierService; import it.gov.pagopa.admissibility.service.onboarding.evaluate.OnboardingRequestEvaluatorService; import it.gov.pagopa.admissibility.service.onboarding.family.OnboardingFamilyEvaluationService; @@ -24,9 +25,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Sort; import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; import org.springframework.messaging.support.MessageBuilder; @@ -264,22 +267,22 @@ void mediatorTestWhenFamilyInitiative() { Mockito.when(onboardingCheckServiceMock.check(Mockito.any(), Mockito.same(initiativeConfig), Mockito.any())).thenReturn(null); - Mockito.when(onboardingFamilyEvaluationServiceMock.checkOnboardingFamily(onboarding_first, initiativeConfig, msgs.get(0))).thenAnswer(i -> { + Mockito.when(onboardingFamilyEvaluationServiceMock.checkOnboardingFamily(onboarding_first, initiativeConfig, msgs.get(0), true)).thenAnswer(i -> { i.getArgument(0, OnboardingDTO.class).setFamily(family1); onboarding_first.setFamily(family1); return Mono.empty(); }); - Mockito.when(onboardingFamilyEvaluationServiceMock.checkOnboardingFamily(onboarding_waitingFirst, initiativeConfig, msgs.get(1))).thenAnswer(i -> { + Mockito.when(onboardingFamilyEvaluationServiceMock.checkOnboardingFamily(onboarding_waitingFirst, initiativeConfig, msgs.get(1), true)).thenAnswer(i -> { i.getArgument(0, OnboardingDTO.class).setFamily(family2); onboarding_waitingFirst.setFamily(family2); return Mono.error(new WaitingFamilyOnBoardingException()); }); - Mockito.when(onboardingFamilyEvaluationServiceMock.checkOnboardingFamily(onboarding_familyOk, initiativeConfig, msgs.get(2))).thenAnswer(i -> { + Mockito.when(onboardingFamilyEvaluationServiceMock.checkOnboardingFamily(onboarding_familyOk, initiativeConfig, msgs.get(2), true)).thenAnswer(i -> { i.getArgument(0, OnboardingDTO.class).setFamily(family3); onboarding_familyOk.setFamily(family3); return Mono.just(expectedEvaluationOnboardingFamilyOk); }); - Mockito.when(onboardingFamilyEvaluationServiceMock.checkOnboardingFamily(onboarding_familyKo, initiativeConfig, msgs.get(3))).thenAnswer(i -> { + Mockito.when(onboardingFamilyEvaluationServiceMock.checkOnboardingFamily(onboarding_familyKo, initiativeConfig, msgs.get(3), true)).thenAnswer(i -> { i.getArgument(0, OnboardingDTO.class).setFamily(family4); onboarding_familyKo.setFamily(family4); return Mono.just(expectedEvaluationOnboardingFamilyKo); @@ -316,6 +319,67 @@ void mediatorTestWhenFamilyInitiative() { checkCommits(checkpointers); } + @Test + void mediatorTestWhenFamilyOuterInitiative() { + // Given + String initiativeId = "INITIATIVEID"; + String userId = "USERID"; + OnboardingDTO onboarding = OnboardingDTO.builder().userId(userId).initiativeId(initiativeId).build(); + + InitiativeConfig initiativeConfig = InitiativeConfig.builder() + .initiativeId(initiativeId) + .rankingInitiative(true) + .rankingFields(List.of(Order.builder().fieldCode(OnboardingConstants.CRITERIA_CODE_ISEE).direction(Sort.Direction.ASC).build())) + .beneficiaryType(InitiativeGeneralDTO.BeneficiaryTypeEnum.NF) + .build(); + + Mockito.when(onboardingContextHolderServiceMock.getInitiativeConfig(initiativeId)).thenReturn(Mono.just(initiativeConfig)); + + List checkpointers= new ArrayList<>(1); + List> msgs = Stream.of(onboarding) + .map(TestUtils::jsonSerializer) + .map(s -> { + Checkpointer checkpointer = Mockito.mock(Checkpointer.class); + Mockito.when(checkpointer.success()).thenReturn(Mono.empty()); + checkpointers.add(checkpointer); + return MessageBuilder.withPayload(s) + .setHeader(AzureHeaders.CHECKPOINTER, checkpointer); + } + ) + .map(MessageBuilder::build).toList(); + + Flux> onboardingFlux = Flux.fromIterable(msgs); + + Mockito.when(onboardingCheckServiceMock.check(Mockito.any(), Mockito.same(initiativeConfig), Mockito.any())) + .thenReturn(OnboardingRejectionReason.builder() + .type(OnboardingRejectionReason.OnboardingRejectionReasonType.INVALID_REQUEST) + .code(OnboardingConstants.REJECTION_REASON_TC_CONSENSUS_DATETIME_FAIL).build()); + + Mockito.when(onboardingFamilyEvaluationServiceMock.checkOnboardingFamily(onboarding, initiativeConfig, msgs.get(0), false)) + .thenReturn(Mono.empty()); + + Mockito.when(onboardingNotifierServiceMock.notify(Mockito.any())).thenReturn(true); + Mockito.when(rankingNotifierServiceMock.notify(Mockito.any())).thenReturn(true); + + // When + admissibilityEvaluatorMediatorService.execute(onboardingFlux); + + // Then + Mockito.verifyNoInteractions(admissibilityErrorNotifierServiceMock); + + Mockito.verify(onboardingCheckServiceMock).check(Mockito.eq(onboarding), Mockito.same(initiativeConfig), Mockito.any()); + + Mockito.verify(onboardingNotifierServiceMock).notify(Mockito.any()); + + ArgumentCaptor argument = ArgumentCaptor.forClass(EvaluationCompletedDTO.class); + Mockito.verify(onboardingNotifierServiceMock).notify(argument.capture()); + Assertions.assertEquals(argument.getValue().getOnboardingRejectionReasons(), List.of(OnboardingRejectionReason.builder() + .type(OnboardingRejectionReason.OnboardingRejectionReasonType.INVALID_REQUEST) + .code(OnboardingConstants.REJECTION_REASON_TC_CONSENSUS_DATETIME_FAIL).build())); + + checkCommits(checkpointers); + } + private static void checkCommits(List checkpointers) { TestUtils.wait(100, TimeUnit.MILLISECONDS); checkpointers.forEach(c -> Mockito.verify(c).success()); diff --git a/src/test/java/it/gov/pagopa/admissibility/service/onboarding/evaluate/RuleEngineServiceImplTest.java b/src/test/java/it/gov/pagopa/admissibility/service/onboarding/evaluate/RuleEngineServiceImplTest.java index 6071178f..fd5d3230 100644 --- a/src/test/java/it/gov/pagopa/admissibility/service/onboarding/evaluate/RuleEngineServiceImplTest.java +++ b/src/test/java/it/gov/pagopa/admissibility/service/onboarding/evaluate/RuleEngineServiceImplTest.java @@ -72,9 +72,9 @@ void applyRules() { expected.setOrganizationId(initiativeConfig.getOrganizationId()); expected.setAdmissibilityCheckDate(result.getAdmissibilityCheckDate()); expected.setStatus(OnboardingEvaluationStatus.ONBOARDING_KO); - expected.setOnboardingRejectionReasons(Collections.singletonList(OnboardingRejectionReason.builder() + expected.getOnboardingRejectionReasons().add(OnboardingRejectionReason.builder() .code("REASON1") - .build())); + .build()); Assertions.assertEquals(expected, result); } diff --git a/src/test/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationServiceTest.java b/src/test/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationServiceTest.java index 4686a939..554b1bb0 100644 --- a/src/test/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationServiceTest.java +++ b/src/test/java/it/gov/pagopa/admissibility/service/onboarding/family/OnboardingFamilyEvaluationServiceTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -66,15 +67,16 @@ void testNewFamily(){ Mockito.when(familyDataRetrieverFacadeServiceMock.retrieveFamily(Mockito.same(request), Mockito.same(initiativeConfig), Mockito.same(expectedMessage))).thenReturn(Mono.just(expectedResult)); // When - EvaluationDTO result = service.checkOnboardingFamily(request, initiativeConfig, expectedMessage).block(); + EvaluationDTO result = service.checkOnboardingFamily(request, initiativeConfig, expectedMessage, true).block(); // Then Assertions.assertEquals(expectedResult, result); } - @Test - void testExistentFamily(){ + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testExistentFamily(boolean retrieveFamily){ // Given OnboardingDTO request = OnboardingDTOFaker.mockInstance(0, "INITIATIVEID"); InitiativeConfig initiativeConfig = new InitiativeConfig(); @@ -93,7 +95,7 @@ void testExistentFamily(){ Mockito.when(existentFamilyHandlerServiceMock.handleExistentFamily(Mockito.same(request), Mockito.same(f2), Mockito.same(initiativeConfig), Mockito.same(expectedMessage))).thenReturn(Mono.just(expectedResult)); // When - EvaluationDTO result = service.checkOnboardingFamily(request, initiativeConfig, expectedMessage).block(); + EvaluationDTO result = service.checkOnboardingFamily(request, initiativeConfig, expectedMessage, retrieveFamily).block(); // Then Assertions.assertEquals(expectedResult, result); @@ -113,7 +115,6 @@ void testUpdateOnboardingFamilyOutcome_Completed(OnboardingEvaluationStatus eval // Given EvaluationCompletedDTO evaluation = new EvaluationCompletedDTO(); evaluation.setStatus(evaluationStatus); - evaluation.setOnboardingRejectionReasons(Collections.emptyList()); InitiativeConfig initiativeConfig = new InitiativeConfig(); Family family = new Family("FAMILYID", Set.of("USERID")); @@ -143,4 +144,22 @@ void testUpdateOnboardingFamilyOutcome_Ranking(){ // Then Assertions.assertSame(result, evaluation); } + + @Test + void testNewFamilyNotRetrieve(){ + // Given + OnboardingDTO request = OnboardingDTOFaker.mockInstance(0, "INITIATIVEID"); + InitiativeConfig initiativeConfig = new InitiativeConfig(); + + @SuppressWarnings("unchecked") Message expectedMessage = Mockito.mock(Message.class); + + Mockito.when(onboardingFamiliesRepositoryMock.findByMemberIdsInAndInitiativeId(request.getUserId(), request.getInitiativeId())) + .thenReturn(Flux.empty()); + + // When + EvaluationDTO result = service.checkOnboardingFamily(request, initiativeConfig, expectedMessage, false).block(); + + // Then + Assertions.assertNull(result); + } } diff --git a/src/test/java/it/gov/pagopa/common/kafka/KafkaTestUtilitiesService.java b/src/test/java/it/gov/pagopa/common/kafka/KafkaTestUtilitiesService.java index 4c2c6c6d..3f05def8 100644 --- a/src/test/java/it/gov/pagopa/common/kafka/KafkaTestUtilitiesService.java +++ b/src/test/java/it/gov/pagopa/common/kafka/KafkaTestUtilitiesService.java @@ -5,14 +5,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import it.gov.pagopa.common.kafka.utils.KafkaConstants; -import it.gov.pagopa.common.mongo.singleinstance.AutoConfigureSingleInstanceMongodb; import it.gov.pagopa.common.reactive.kafka.consumer.BaseKafkaConsumer; import it.gov.pagopa.common.utils.MemoryAppender; import it.gov.pagopa.common.utils.TestUtils; -import org.apache.kafka.clients.consumer.Consumer; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.clients.consumer.ConsumerRecords; -import org.apache.kafka.clients.consumer.OffsetAndMetadata; +import org.apache.kafka.clients.admin.OffsetSpec; +import org.apache.kafka.clients.admin.RecordsToDelete; +import org.apache.kafka.clients.admin.TopicListing; +import org.apache.kafka.clients.consumer.*; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.header.Header; @@ -35,16 +34,19 @@ import org.springframework.kafka.test.utils.KafkaTestUtils; import org.springframework.messaging.Message; import org.springframework.stereotype.Service; +import org.springframework.test.context.event.annotation.AfterTestClass; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.*; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -70,7 +72,6 @@ public class KafkaTestUtilitiesService { private ObjectMapper objectMapper; @TestConfiguration - @AutoConfigureSingleInstanceMongodb static class TestKafkaConfiguration { @Bean public KafkaTemplate testPublisher(ProducerFactory producerFactory) { @@ -82,6 +83,32 @@ public KafkaTemplate testPublisher(ProducerFactory { + try { + Collection topics = admin.listTopics().listings().get(); + admin.deleteRecords( + admin.listOffsets( + topics.stream() + .filter(topicListing -> !topicListing.isInternal()) + .flatMap(t -> IntStream.range(0, kafkaBroker.getPartitionsPerTopic()) + .boxed() + .map(p -> new TopicPartition(t.name(), p))) + .collect(Collectors.toMap(tp -> tp, + tp -> OffsetSpec.latest())) + ).all().get().entrySet().stream() + .filter(e -> e.getValue().offset() > 0) + .collect(Collectors.toMap(Map.Entry::getKey, + e -> RecordsToDelete.beforeOffset(e.getValue().offset())))) + .all().get(); + + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException("Something gone wrong while emptying topics", e); + } + }); + } + /** It will return usefull URLs related to embedded kafka */ public String getKafkaUrls() { return "bootstrapServers: %s, zkNodes: %s".formatted(bootstrapServers, zkNodes); @@ -100,7 +127,7 @@ public Consumer getEmbeddedKafkaConsumer(String topic, String gr } Map consumerProps = KafkaTestUtils.consumerProps(groupId, "true", kafkaBroker); - consumerProps.put("key.deserializer", StringDeserializer.class); + consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); DefaultKafkaConsumerFactory cf = new DefaultKafkaConsumerFactory<>(consumerProps); Consumer consumer = cf.createConsumer(); if(attachToBroker){ @@ -147,13 +174,6 @@ public List> consumeMessages(String topic, String List> payloadConsumed = new ArrayList<>(expectedNumber); while (payloadConsumed.size() < expectedNumber) { if (System.currentTimeMillis() - startTime > maxWaitingMs) { - System.out.println("Current read messages:"); - int[] i={0}; - System.out.println(payloadConsumed.stream() - .map(ConsumerRecord::value) - .sorted() - .map(p -> " " + i[0]++ + ". " + p) - .collect(Collectors.joining("\n"))); Assertions.fail("timeout of %d ms expired. Read %d messages of %d".formatted(maxWaitingMs, payloadConsumed.size(), expectedNumber)); } consumer.poll(Duration.ofMillis(7000)).iterator().forEachRemaining(payloadConsumed::add); @@ -261,7 +281,7 @@ public Map checkPublishedOffsets(String topic, long expect } //endregion - //region check commit by logs +//region check commit by logs protected MemoryAppender commitLogMemoryAppender; /** To be called before each test in order to perform the asserts on {@link #assertCommitOrder(String, int)} */ public void setupCommitLogMemoryAppender() { @@ -300,7 +320,7 @@ public void assertCommitOrder(String flowName, int totalSendMessages) { } //endregion - //region error topic +//region error topic public void checkErrorsPublished(String topicErrors, Pattern errorUseCaseIdPatternMatch, int expectedErrorMessagesNumber, long maxWaitingMs, List, java.util.function.Consumer>>> errorUseCases) { final List> errors = consumeMessages(topicErrors, expectedErrorMessagesNumber, maxWaitingMs); for (final ConsumerRecord record : errors) { diff --git a/src/test/java/it/gov/pagopa/common/reactive/mongo/retry/MongoRequestRateTooLargeRetryerTest.java b/src/test/java/it/gov/pagopa/common/reactive/mongo/retry/MongoRequestRateTooLargeRetryerTest.java index 42781f3f..95ebc986 100644 --- a/src/test/java/it/gov/pagopa/common/reactive/mongo/retry/MongoRequestRateTooLargeRetryerTest.java +++ b/src/test/java/it/gov/pagopa/common/reactive/mongo/retry/MongoRequestRateTooLargeRetryerTest.java @@ -26,8 +26,8 @@ class MongoRequestRateTooLargeRetryerTest { - private static final int REQUEST_RATE_TOO_LARGE_MAX_RETRY = 10; - public static final int REQUEST_RATE_TOO_LARGE_MAX_MILLIS_ELAPSED = 1000; + private static final int REQUEST_RATE_TOO_LARGE_MAX_RETRY = 1; + public static final int REQUEST_RATE_TOO_LARGE_MAX_MILLIS_ELAPSED = 200; private MemoryAppender memoryAppender; @BeforeEach