From 47285aa0d38079bae25b27298f958169b8a0912e Mon Sep 17 00:00:00 2001 From: dsabacn <118284405+dsabacn@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:50:08 +0200 Subject: [PATCH] feat: Added event bus integration for sending payment transaction status changes to mil-preset (#11) Co-authored-by: fabrizio.guerrini Co-authored-by: antoniotarricone --- .github/workflows/pr-validation.yml | 62 +++-- .github/workflows/release.yml | 100 +++++--- pom.xml | 77 +++--- .../swclient/mil/paymentnotice/ErrorCode.java | 8 + .../paymentnotice/bean/PreCloseRequest.java | 49 +++- .../mil/paymentnotice/bean/Preset.java | 94 ++++++++ .../paymentnotice/dao/PaymentTransaction.java | 73 ++++-- .../resource/BasePaymentResource.java | 44 ++-- .../resource/PaymentResource.java | 101 +++++--- src/main/resources/application.properties | 37 ++- .../ActivatePaymentNoticeResourceTest.java | 3 + .../AsyncClosePaymentProcessorTest.java | 5 +- .../ClosePaymentResourceTest.java | 224 +++++++++++++++++- .../ManagePaymentResultTest.java | 123 +++++++++- .../paymentnotice/PreCloseResourceTest.java | 115 +++++++-- .../mil/paymentnotice/QrCodeParserTest.java | 3 + .../paymentnotice/StAmountFormatterTest.java | 3 + .../VerifyPaymentNoticeResourceTest.java | 3 + .../it/ClosePaymentResourceTestIT.java | 62 +++-- .../it/CustomRedpandaContainer.java | 37 +++ .../it/IntegrationTestProfile.java | 13 +- .../it/ManagePaymentResultTestIT.java | 118 ++++++--- .../it/PreClosePaymentResourceTestIT.java | 62 ++++- .../it/resource/KafkaTestResource.java | 148 ++++++++++++ .../it/resource/MongoTestResource.java | 16 +- .../it/resource/RedisTestResource.java | 2 +- .../it/resource/WiremockTestResource.java | 2 +- .../resource/KafkaInMemoryTestResource.java | 27 +++ .../resource/UnitTestProfile.java | 30 +++ .../mil/paymentnotice/util/KafkaUtils.java | 83 +++++++ .../paymentnotice/util/PaymentTestData.java | 28 ++- .../mil/paymentnotice/util/TestUtils.java | 72 +++--- 32 files changed, 1501 insertions(+), 323 deletions(-) create mode 100644 src/main/java/it/pagopa/swclient/mil/paymentnotice/bean/Preset.java create mode 100644 src/test/java/it/pagopa/swclient/mil/paymentnotice/it/CustomRedpandaContainer.java create mode 100644 src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/KafkaTestResource.java create mode 100644 src/test/java/it/pagopa/swclient/mil/paymentnotice/resource/KafkaInMemoryTestResource.java create mode 100644 src/test/java/it/pagopa/swclient/mil/paymentnotice/resource/UnitTestProfile.java create mode 100644 src/test/java/it/pagopa/swclient/mil/paymentnotice/util/KafkaUtils.java diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 875c1fb..415899e 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -12,10 +12,10 @@ on: jobs: pr-validation: runs-on: ubuntu-latest - + steps: - name: PR title validation - uses: amannn/action-semantic-pull-request@v5 + uses: amannn/action-semantic-pull-request@c3cd5d1ea3580753008872425915e343e351ab54 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -34,25 +34,59 @@ jobs: starts with an uppercase character. wip: false - - name: Checkout the repository - uses: actions/checkout@v3 + - name: Checkout the source code + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab with: + token: ${{ secrets.GIT_PAT }} fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@v3 + - name: Cache JDK + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 + id: cache-jdk + with: + key: OpenJDK17U-jdk_x64_linux_hotspot_17.0.7_7.tar.gz + path: | + ${{ runner.temp }}/jdk_setup.tar.gz + ${{ runner.temp }}/jdk_setup.sha256 + + - name: Download JDK and verify its hash + if: steps.cache-jdk.outputs.cache-hit != 'true' + run: | + echo "e9458b38e97358850902c2936a1bb5f35f6cffc59da9fcd28c63eab8dbbfbc3b ${{ runner.temp }}/jdk_setup.tar.gz" >> ${{ runner.temp }}/jdk_setup.sha256 + curl -L "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.7%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.7_7.tar.gz" -o "${{ runner.temp }}/jdk_setup.tar.gz" + sha256sum --check --status "${{ runner.temp }}/jdk_setup.sha256" + + - name: Setup JDK + uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 with: - java-version: '17' - distribution: 'temurin' + distribution: "jdkfile" + jdkFile: "${{ runner.temp }}/jdk_setup.tar.gz" + java-version: "17" cache: maven + + - name: Cache Maven + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 + id: cache-maven + with: + key: apache-maven-3.9.2-bin.tar.gz + path: | + ${{ runner.temp }}/maven_setup.tar.gz + ${{ runner.temp }}/maven_setup.sha256 - - name: Build dependencies with Maven - run: mvn clean validate -Pbootstrap + - name: Download Maven and verify its hash + if: steps.cache-maven.outputs.cache-hit != 'true' + run: | + echo "809ef3220c6d179195c06c324cb9a6d34d8ecba566c5cfd8eb83167bc034117d ${{ runner.temp }}/maven_setup.tar.gz" >> ${{ runner.temp }}/maven_setup.sha256 + curl -L "https://dlcdn.apache.org/maven/maven-3/3.9.2/binaries/apache-maven-3.9.2-bin.tar.gz" -o "${{ runner.temp }}/maven_setup.tar.gz" + sha256sum --check --status "${{ runner.temp }}/maven_setup.sha256" + + - name: Setup Maven + run: | + mkdir ${{ runner.temp }}/maven + tar -xvf ${{ runner.temp }}/maven_setup.tar.gz -C ${{ runner.temp }}/maven --strip-components=1 + echo "github${{ secrets.GIT_USER }}${{ secrets.GIT_PAT }}" >> ${{ runner.temp }}/settings.xml - #- name: Execute unit-testing - # run: mvn clean test - - name: Execute unit-test + Calculate test coverage + SCA with Sonar env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn verify -Pvalidate --no-transfer-progress \ No newline at end of file + run: ${{ runner.temp }}/maven/bin/mvn verify -Pvalidate -s ${{ runner.temp }}/settings.xml --no-transfer-progress \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 58c7828..18a1575 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: # Checkout the source code. # - name: Checkout the source code - uses: actions/checkout@v3 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab with: token: ${{ secrets.GIT_PAT }} fetch-depth: 0 @@ -31,7 +31,7 @@ jobs: # Calculation of the new version (dry-run). # - name: Calculation of the new version (dry-run) - uses: cycjimmy/semantic-release-action@v3 + uses: cycjimmy/semantic-release-action@8e58d20d0f6c8773181f43eb74d6a05e3099571d id: semantic env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -44,42 +44,88 @@ jobs: dry_run: true # - # Setup the JDK. + # Cache JDK. # - - name: Setup the JDK - if: steps.semantic.outputs.new_release_published == 'true' - uses: actions/setup-java@v3 + - name: Cache JDK + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 + id: cache-jdk + with: + key: OpenJDK17U-jdk_x64_linux_hotspot_17.0.7_7.tar.gz + path: | + ${{ runner.temp }}/jdk_setup.tar.gz + ${{ runner.temp }}/jdk_setup.sha256 + + # + # Download JDK and verify its hash. + # + - name: Download JDK and verify its hash + if: steps.cache-jdk.outputs.cache-hit != 'true' + run: | + echo "e9458b38e97358850902c2936a1bb5f35f6cffc59da9fcd28c63eab8dbbfbc3b ${{ runner.temp }}/jdk_setup.tar.gz" >> ${{ runner.temp }}/jdk_setup.sha256 + curl -L "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.7%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.7_7.tar.gz" -o "${{ runner.temp }}/jdk_setup.tar.gz" + sha256sum --check --status "${{ runner.temp }}/jdk_setup.sha256" + + # + # Setup JDK. + # + - name: Setup JDK + uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 with: - java-version: '17' - distribution: 'temurin' + distribution: "jdkfile" + jdkFile: "${{ runner.temp }}/jdk_setup.tar.gz" + java-version: "17" cache: maven + # + # Cache Maven. + # + - name: Cache Maven + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 + id: cache-maven + with: + key: apache-maven-3.9.2-bin.tar.gz + path: | + ${{ runner.temp }}/maven_setup.tar.gz + ${{ runner.temp }}/maven_setup.sha256 + + # + # Download Maven and verify its hash. + # + - name: Download Maven and verify its hash + if: steps.cache-maven.outputs.cache-hit != 'true' + run: | + echo "809ef3220c6d179195c06c324cb9a6d34d8ecba566c5cfd8eb83167bc034117d ${{ runner.temp }}/maven_setup.tar.gz" >> ${{ runner.temp }}/maven_setup.sha256 + curl -L "https://dlcdn.apache.org/maven/maven-3/3.9.2/binaries/apache-maven-3.9.2-bin.tar.gz" -o "${{ runner.temp }}/maven_setup.tar.gz" + sha256sum --check --status "${{ runner.temp }}/maven_setup.sha256" + + # + # Setup Maven. + # + - name: Setup Maven + run: | + mkdir ${{ runner.temp }}/maven + tar -xvf ${{ runner.temp }}/maven_setup.tar.gz -C ${{ runner.temp }}/maven --strip-components=1 + echo "github${{ secrets.GIT_USER }}${{ secrets.GIT_PAT }}" >> ${{ runner.temp }}/settings.xml + # # Update of pom.xml with the new version + Git add + commit + push of the updated pom.xml. # - name: Update of pom.xml with the new version + Git add + commit + push of the updated pom.xml if: steps.semantic.outputs.new_release_published == 'true' run: | - mvn versions:set -DnewVersion=${{ steps.semantic.outputs.new_release_version }} --no-transfer-progress + ${{ runner.temp }}/maven/bin/mvn versions:set -DnewVersion=${{ steps.semantic.outputs.new_release_version }} -s ${{ runner.temp }}/settings.xml --no-transfer-progress git config user.name "GitHub Workflow" git config user.email "<>" git add pom.xml git commit -m "pom.xml updated with new version ${{ steps.semantic.outputs.new_release_version }}" git push origin main - # - # Sleep for 60s to avoid this issue: https://github.com/semantic-release/semantic-release/issues/2204 - # - - name: Sleep for 60s to avoid the issue 2204 - if: steps.semantic.outputs.new_release_published == 'true' - run: sleep 60s - # # Calculation of the new version (again) with tagging + releasing + etc. # - name: Calculation of the new version (again) with tagging + releasing + etc if: steps.semantic.outputs.new_release_published == 'true' - uses: cycjimmy/semantic-release-action@v3 + uses: cycjimmy/semantic-release-action@8e58d20d0f6c8773181f43eb74d6a05e3099571d env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -91,16 +137,15 @@ jobs: dry_run: false # - # Build dependencies + Execute unit-test + Calculate test coverage + SCA with Sonar + Build native image + Docker build + Docker login + Docker push + # Execute unit-test + Calculate test coverage + SCA with Sonar + Build native image + Docker build + Docker login + Docker push # - - name: Build dependencies + Execute unit-test + Calculate test coverage + SCA with Sonar + Build native image + Docker build + Docker login + Docker push + - name: Execute unit-test + Calculate test coverage + SCA with Sonar + Build native image + Docker build + Docker login + Docker push if: steps.semantic.outputs.new_release_published == 'true' env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | - mvn clean validate -Pbootstrap --no-transfer-progress - mvn verify -Pvalidate --no-transfer-progress - mvn clean package -Pnative -Dmaven.test.skip=true --no-transfer-progress + ${{ runner.temp }}/maven/bin/mvn verify -Pvalidate -s ${{ runner.temp }}/settings.xml --no-transfer-progress + ${{ runner.temp }}/maven/bin/mvn clean package -Pnative -Dmaven.test.skip=true -Dquarkus.native.container-build=true -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image@sha256:05baf3fd2173f6f25ad35216b6b066c35fbfb97f06daba75efb5b22bc0a85b9c -s ${{ runner.temp }}/settings.xml --no-transfer-progress docker build -f src/main/docker/Dockerfile.native-micro -t ghcr.io/${{ github.repository }}:latest -t ghcr.io/${{ github.repository }}:${{ steps.semantic.outputs.new_release_version }} . echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin docker push -a ghcr.io/${{ github.repository }} @@ -116,27 +161,24 @@ jobs: permissions: id-token: write - + steps: # # Login to Azure. # - name: Login to Azure - uses: azure/login@v1 + uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - + # # Update Container App # - name: Update Container App - uses: azure/CLI@v1 + uses: azure/CLI@fa0f960f00db49b95fdb54328a767aee31e80105 with: inlineScript: | az config set extension.use_dynamic_install=yes_without_prompt az containerapp update -n ${{ secrets.AZURE_CONTAINER_APP_NAME }} -g ${{ secrets.AZURE_RESOURCE_GROUP_NAME }} --image ghcr.io/${{ github.repository }}:${{ needs.release.outputs.new_release_version }} - - # TODO: Run Integration Test - # TODO: Run Performance Test diff --git a/pom.xml b/pom.xml index 895bd70..eb117e9 100644 --- a/pom.xml +++ b/pom.xml @@ -12,15 +12,15 @@ ${java.version} ${java.version} 3.11.0 - 3.0.0-M9 + 3.1.2 3.9.1.2184 UTF-8 UTF-8 quarkus-bom io.quarkus.platform - 3.0.2.Final + 3.1.1.Final true - 2.0.1 + 2.0.2 https://sonarcloud.io:443/ pagopa @@ -41,7 +41,7 @@ io.quarkiverse.cxf quarkus-cxf-bom - 2.0.2 + 2.1.0 pom import @@ -111,6 +111,15 @@ org.apache.commons commons-lang3 + + io.quarkus + quarkus-smallrye-reactive-messaging-kafka + + + it.pagopa.swclient.mil + common + ${common.version} + io.quarkus quarkus-junit5 @@ -126,11 +135,6 @@ quarkus-junit5-mockito test - - it.pagopa.swclient.mil - common - ${common.version} - io.quarkus quarkus-jacoco @@ -157,13 +161,29 @@ awaitility test + + io.smallrye.reactive + smallrye-reactive-messaging-in-memory + test + + + org.testcontainers + redpanda + test + + + + github + https://maven.pkg.github.com/pagopa/mil-common + + org.apache.cxf cxf-codegen-plugin - 4.0.0 + 4.0.1 generate-sources @@ -246,43 +266,6 @@ - - - - bootstrap - - - bootstrap - - - - - - org.apache.maven.plugins - maven-scm-plugin - 2.0.0-M3 - - ${maven.home} - install - - - - clone_and_install_mil-common - validate - - scm:git:https://github.com/pagopa/mil-common.git - tag - ${common.version} - - - bootstrap - - - - - - - native diff --git a/src/main/java/it/pagopa/swclient/mil/paymentnotice/ErrorCode.java b/src/main/java/it/pagopa/swclient/mil/paymentnotice/ErrorCode.java index 862e2d1..9a11753 100644 --- a/src/main/java/it/pagopa/swclient/mil/paymentnotice/ErrorCode.java +++ b/src/main/java/it/pagopa/swclient/mil/paymentnotice/ErrorCode.java @@ -77,6 +77,14 @@ public final class ErrorCode { public static final String CACHED_NOTICE_NOT_FOUND = MODULE_ID + "000032"; public static final String ERROR_TOTAL_AMOUNT_MUST_MATCH_TOTAL_CACHED_VALUE = MODULE_ID + "000033"; + + public static final String PA_TAX_CODE_MUST_NOT_BE_NULL = MODULE_ID + "000034"; + + public static final String SUBSCRIBER_ID_CODE_MUST_NOT_BE_NULL = MODULE_ID + "000035"; + public static final String SUBSCRIBER_ID_MUST_MATCH_REGEXP = MODULE_ID + "000036"; + + public static final String PRESET_ID_MUST_NOT_BE_NULL = MODULE_ID + "000037"; + public static final String PRESET_ID_MUST_MATCH_REGEXP = MODULE_ID + "000038"; private ErrorCode() { } diff --git a/src/main/java/it/pagopa/swclient/mil/paymentnotice/bean/PreCloseRequest.java b/src/main/java/it/pagopa/swclient/mil/paymentnotice/bean/PreCloseRequest.java index 47cb6a0..af72b92 100644 --- a/src/main/java/it/pagopa/swclient/mil/paymentnotice/bean/PreCloseRequest.java +++ b/src/main/java/it/pagopa/swclient/mil/paymentnotice/bean/PreCloseRequest.java @@ -1,15 +1,16 @@ package it.pagopa.swclient.mil.paymentnotice.bean; +import java.util.List; + import it.pagopa.swclient.mil.paymentnotice.ErrorCode; import it.pagopa.swclient.mil.paymentnotice.utils.PaymentNoticeConstants; - +import jakarta.validation.Valid; import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import java.util.List; /** * Request of the preClose API @@ -50,6 +51,12 @@ public class PreCloseRequest { @Max(value = 99999999999L, message = "[" + ErrorCode.ERROR_FEE_MUST_BE_LESS_THAN + "] fee must less than {value}") private Long fee; + /** + * Preset information + */ + @Valid + private Preset preset; + @AssertFalse(message = "[" + ErrorCode.ERROR_TRANSACTION_ID_MUST_NOT_BE_NULL + "] transactionId must not be null when outcome is PRE_CLOSE") private boolean isTransactionIdNullForPreClose() { return PaymentTransactionOutcome.PRE_CLOSE.name().equals(outcome) && transactionId == null; @@ -146,15 +153,37 @@ public void setFee(Long fee) { this.fee = fee; } + /** + * @return the preset + */ + public Preset getPreset() { + return preset; + } + + /** + * @param preset the preset to set + */ + public void setPreset(Preset preset) { + this.preset = preset; + } + @Override public String toString() { - final StringBuilder sb = new StringBuilder("PreCloseRequest{"); - sb.append("outcome='").append(outcome).append('\''); - sb.append(", paymentTokens=").append(paymentTokens); - sb.append(", transactionId='").append(transactionId).append('\''); - sb.append(", totalAmount=").append(totalAmount); - sb.append(", fee=").append(fee); - sb.append('}'); - return sb.toString(); + StringBuilder builder = new StringBuilder(); + builder.append("PreCloseRequest [outcome="); + builder.append(outcome); + builder.append(", paymentTokens="); + builder.append(paymentTokens); + builder.append(", transactionId="); + builder.append(transactionId); + builder.append(", totalAmount="); + builder.append(totalAmount); + builder.append(", fee="); + builder.append(fee); + builder.append(", preset="); + builder.append(preset); + builder.append("]"); + return builder.toString(); } + } diff --git a/src/main/java/it/pagopa/swclient/mil/paymentnotice/bean/Preset.java b/src/main/java/it/pagopa/swclient/mil/paymentnotice/bean/Preset.java new file mode 100644 index 0000000..3645b74 --- /dev/null +++ b/src/main/java/it/pagopa/swclient/mil/paymentnotice/bean/Preset.java @@ -0,0 +1,94 @@ +/** + * + */ +package it.pagopa.swclient.mil.paymentnotice.bean; + +import java.io.Serializable; + +import it.pagopa.swclient.mil.paymentnotice.ErrorCode; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; + +public class Preset implements Serializable{ + + /** + *Preset.java + */ + private static final long serialVersionUID = -8846393398844116002L; + + /* + * Tax code of the creditor company + */ + @NotNull(message = "[" + ErrorCode.PA_TAX_CODE_MUST_NOT_BE_NULL + "] paTaxCode must not be null") + @Pattern(regexp = "^[0-9]{11}$", message = "[" + ErrorCode.PA_TAX_CODE_MUST_MATCH_REGEXP + "] paTaxCode must match \"{regexp}\"") + private String paTaxCode; + + /* + * Subscriber ID + */ + @NotNull(message = "[" + ErrorCode.SUBSCRIBER_ID_CODE_MUST_NOT_BE_NULL + "] paTaxCode must not be null") + @Pattern(regexp = "^[0-9a-z]{6}$", message = "[" + ErrorCode.SUBSCRIBER_ID_MUST_MATCH_REGEXP + "] subscriberId must match \"{regexp}\"") + private String subscriberId; + + /* + * Preset Id + */ + @NotNull(message = "[" + ErrorCode.PRESET_ID_MUST_NOT_BE_NULL + "] presetId must not be null") + @Pattern(regexp = "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$", message = "[" + ErrorCode.PRESET_ID_MUST_MATCH_REGEXP + "] presetId must match \"{regexp}\"") + private String presetId; + + /** + * @return the paTaxCode + */ + public String getPaTaxCode() { + return paTaxCode; + } + + /** + * @param paTaxCode the paTaxCode to set + */ + public void setPaTaxCode(String paTaxCode) { + this.paTaxCode = paTaxCode; + } + + /** + * @return the subscriberId + */ + public String getSubscriberId() { + return subscriberId; + } + + /** + * @param subscriberId the subscriberId to set + */ + public void setSubscriberId(String subscriberId) { + this.subscriberId = subscriberId; + } + + /** + * @return the presetId + */ + public String getPresetId() { + return presetId; + } + + /** + * @param presetId the presetId to set + */ + public void setPresetId(String presetId) { + this.presetId = presetId; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Preset [paTaxCode="); + builder.append(paTaxCode); + builder.append(", subscriberId="); + builder.append(subscriberId); + builder.append(", presetId="); + builder.append(presetId); + builder.append("]"); + return builder.toString(); + } +} diff --git a/src/main/java/it/pagopa/swclient/mil/paymentnotice/dao/PaymentTransaction.java b/src/main/java/it/pagopa/swclient/mil/paymentnotice/dao/PaymentTransaction.java index 6ef11a7..a3dd6bd 100644 --- a/src/main/java/it/pagopa/swclient/mil/paymentnotice/dao/PaymentTransaction.java +++ b/src/main/java/it/pagopa/swclient/mil/paymentnotice/dao/PaymentTransaction.java @@ -1,6 +1,7 @@ package it.pagopa.swclient.mil.paymentnotice.dao; import io.quarkus.runtime.annotations.RegisterForReflection; +import it.pagopa.swclient.mil.paymentnotice.bean.Preset; import java.util.List; @@ -85,6 +86,11 @@ public class PaymentTransaction { */ private String callbackTimestamp; + /** + * Preset information + */ + private Preset preset; + /** * Gets transactionId * @return value of transactionId @@ -325,25 +331,56 @@ public void setCallbackTimestamp(String callbackTimestamp) { this.callbackTimestamp = callbackTimestamp; } + /** + * @return the preset + */ + public Preset getPreset() { + return preset; + } + + /** + * @param preset the preset to set + */ + public void setPreset(Preset preset) { + this.preset = preset; + } + @Override public String toString() { - final StringBuilder sb = new StringBuilder("PaymentTransaction{"); - sb.append("transactionId='").append(transactionId).append('\''); - sb.append(", acquirerId='").append(acquirerId).append('\''); - sb.append(", channel='").append(channel).append('\''); - sb.append(", merchantId='").append(merchantId).append('\''); - sb.append(", terminalId='").append(terminalId).append('\''); - sb.append(", insertTimestamp='").append(insertTimestamp).append('\''); - sb.append(", notices=").append(notices); - sb.append(", totalAmount=").append(totalAmount); - sb.append(", fee=").append(fee); - sb.append(", status='").append(status).append('\''); - sb.append(", paymentMethod='").append(paymentMethod).append('\''); - sb.append(", paymentTimestamp='").append(paymentTimestamp).append('\''); - sb.append(", closeTimestamp='").append(closeTimestamp).append('\''); - sb.append(", paymentDate='").append(paymentDate).append('\''); - sb.append(", callbackTimestamp='").append(callbackTimestamp).append('\''); - sb.append('}'); - return sb.toString(); + StringBuilder builder = new StringBuilder(); + builder.append("PaymentTransaction [transactionId="); + builder.append(transactionId); + builder.append(", acquirerId="); + builder.append(acquirerId); + builder.append(", channel="); + builder.append(channel); + builder.append(", merchantId="); + builder.append(merchantId); + builder.append(", terminalId="); + builder.append(terminalId); + builder.append(", insertTimestamp="); + builder.append(insertTimestamp); + builder.append(", notices="); + builder.append(notices); + builder.append(", totalAmount="); + builder.append(totalAmount); + builder.append(", fee="); + builder.append(fee); + builder.append(", status="); + builder.append(status); + builder.append(", paymentMethod="); + builder.append(paymentMethod); + builder.append(", paymentTimestamp="); + builder.append(paymentTimestamp); + builder.append(", closeTimestamp="); + builder.append(closeTimestamp); + builder.append(", paymentDate="); + builder.append(paymentDate); + builder.append(", callbackTimestamp="); + builder.append(callbackTimestamp); + builder.append(", preset="); + builder.append(preset); + builder.append("]"); + return builder.toString(); } } diff --git a/src/main/java/it/pagopa/swclient/mil/paymentnotice/resource/BasePaymentResource.java b/src/main/java/it/pagopa/swclient/mil/paymentnotice/resource/BasePaymentResource.java index b6745bd..d06f847 100644 --- a/src/main/java/it/pagopa/swclient/mil/paymentnotice/resource/BasePaymentResource.java +++ b/src/main/java/it/pagopa/swclient/mil/paymentnotice/resource/BasePaymentResource.java @@ -1,5 +1,24 @@ package it.pagopa.swclient.mil.paymentnotice.resource; +import java.math.BigDecimal; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.resteasy.reactive.ClientWebApplicationException; + import io.quarkus.logging.Log; import io.smallrye.mutiny.Uni; import it.pagopa.swclient.mil.bean.CommonHeader; @@ -8,6 +27,7 @@ import it.pagopa.swclient.mil.paymentnotice.bean.Outcome; import it.pagopa.swclient.mil.paymentnotice.bean.PaymentTransactionOutcome; import it.pagopa.swclient.mil.paymentnotice.bean.PreCloseRequest; +import it.pagopa.swclient.mil.paymentnotice.bean.Preset; import it.pagopa.swclient.mil.paymentnotice.client.MilRestService; import it.pagopa.swclient.mil.paymentnotice.client.NodeForPspWrapper; import it.pagopa.swclient.mil.paymentnotice.client.bean.AdditionalPaymentInformations; @@ -17,29 +37,11 @@ import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransaction; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionEntity; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionStatus; -import it.pagopa.swclient.mil.paymentnotice.utils.NodeErrorMapping; import it.pagopa.swclient.mil.paymentnotice.utils.NodeApi; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.jboss.resteasy.reactive.ClientWebApplicationException; - +import it.pagopa.swclient.mil.paymentnotice.utils.NodeErrorMapping; import jakarta.inject.Inject; import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.core.Response; -import java.math.BigDecimal; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; public class BasePaymentResource { @@ -142,7 +144,8 @@ protected static PaymentTransactionEntity createPaymentTransactionEntity(CommonH String transactionId, Long fees, List notices, - String outcome) { + String outcome, + Preset preset) { PaymentTransaction paymentTransaction = new PaymentTransaction(); paymentTransaction.setTransactionId(transactionId); @@ -157,6 +160,7 @@ protected static PaymentTransactionEntity createPaymentTransactionEntity(CommonH paymentTransaction.setStatus(PaymentTransactionOutcome.PRE_CLOSE.name().equals(outcome) ? PaymentTransactionStatus.PRE_CLOSE.name() : PaymentTransactionStatus.ABORTED.name()); + paymentTransaction.setPreset(preset); PaymentTransactionEntity entity = new PaymentTransactionEntity(); entity.transactionId = transactionId; entity.paymentTransaction = paymentTransaction; diff --git a/src/main/java/it/pagopa/swclient/mil/paymentnotice/resource/PaymentResource.java b/src/main/java/it/pagopa/swclient/mil/paymentnotice/resource/PaymentResource.java index 463763a..02d8b48 100644 --- a/src/main/java/it/pagopa/swclient/mil/paymentnotice/resource/PaymentResource.java +++ b/src/main/java/it/pagopa/swclient/mil/paymentnotice/resource/PaymentResource.java @@ -13,13 +13,50 @@ import java.util.Optional; import java.util.concurrent.TimeoutException; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.reactive.messaging.Channel; +import org.eclipse.microprofile.reactive.messaging.Emitter; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.resteasy.reactive.ClientWebApplicationException; + +import com.fasterxml.jackson.core.JsonParseException; +import com.mongodb.ErrorCategory; +import com.mongodb.MongoWriteException; + +import io.quarkus.logging.Log; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; import io.smallrye.mutiny.Context; import io.smallrye.mutiny.ItemWithContext; +import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.unchecked.Unchecked; +import io.vertx.core.eventbus.EventBus; +import it.pagopa.swclient.mil.bean.CommonHeader; +import it.pagopa.swclient.mil.bean.Errors; import it.pagopa.swclient.mil.paymentnotice.ErrorCode; +import it.pagopa.swclient.mil.paymentnotice.bean.ClosePaymentRequest; +import it.pagopa.swclient.mil.paymentnotice.bean.ClosePaymentResponse; +import it.pagopa.swclient.mil.paymentnotice.bean.GetPaymentsResponse; +import it.pagopa.swclient.mil.paymentnotice.bean.Outcome; +import it.pagopa.swclient.mil.paymentnotice.bean.PaymentMethod; import it.pagopa.swclient.mil.paymentnotice.bean.PaymentTransactionOutcome; +import it.pagopa.swclient.mil.paymentnotice.bean.PreCloseRequest; import it.pagopa.swclient.mil.paymentnotice.bean.PreCloseResponse; +import it.pagopa.swclient.mil.paymentnotice.bean.ReceivePaymentStatusRequest; +import it.pagopa.swclient.mil.paymentnotice.bean.ReceivePaymentStatusResponse; +import it.pagopa.swclient.mil.paymentnotice.client.NodeRestService; +import it.pagopa.swclient.mil.paymentnotice.client.bean.NodeClosePaymentRequest; +import it.pagopa.swclient.mil.paymentnotice.client.bean.PspConfiguration; +import it.pagopa.swclient.mil.paymentnotice.dao.Notice; +import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransaction; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionEntity; +import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionRepository; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionStatus; +import it.pagopa.swclient.mil.paymentnotice.redis.PaymentNoticeService; +import it.pagopa.swclient.mil.paymentnotice.utils.NodeApi; +import it.pagopa.swclient.mil.paymentnotice.utils.PaymentNoticeConstants; import jakarta.inject.Inject; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; @@ -39,43 +76,12 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; -import com.fasterxml.jackson.core.JsonParseException; -import com.mongodb.ErrorCategory; -import com.mongodb.MongoWriteException; -import io.quarkus.panache.common.Page; -import io.quarkus.panache.common.Sort; -import io.smallrye.mutiny.unchecked.Unchecked; -import io.vertx.core.eventbus.EventBus; -import it.pagopa.swclient.mil.paymentnotice.bean.GetPaymentsResponse; -import it.pagopa.swclient.mil.paymentnotice.bean.PaymentMethod; -import it.pagopa.swclient.mil.paymentnotice.bean.PreCloseRequest; -import it.pagopa.swclient.mil.paymentnotice.client.bean.NodeClosePaymentRequest; -import it.pagopa.swclient.mil.paymentnotice.client.bean.PspConfiguration; -import it.pagopa.swclient.mil.paymentnotice.dao.Notice; -import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransaction; -import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionRepository; -import it.pagopa.swclient.mil.paymentnotice.redis.PaymentNoticeService; -import it.pagopa.swclient.mil.paymentnotice.utils.NodeApi; -import it.pagopa.swclient.mil.paymentnotice.utils.PaymentNoticeConstants; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipse.microprofile.rest.client.inject.RestClient; - -import io.quarkus.logging.Log; -import io.smallrye.mutiny.Uni; -import it.pagopa.swclient.mil.bean.CommonHeader; -import it.pagopa.swclient.mil.bean.Errors; -import it.pagopa.swclient.mil.paymentnotice.bean.ReceivePaymentStatusResponse; -import it.pagopa.swclient.mil.paymentnotice.bean.ClosePaymentResponse; -import it.pagopa.swclient.mil.paymentnotice.bean.Outcome; -import it.pagopa.swclient.mil.paymentnotice.bean.ReceivePaymentStatusRequest; -import it.pagopa.swclient.mil.paymentnotice.bean.ClosePaymentRequest; -import it.pagopa.swclient.mil.paymentnotice.client.NodeRestService; -import org.jboss.resteasy.reactive.ClientWebApplicationException; - @Path("/payments") public class PaymentResource extends BasePaymentResource { + + @Inject + @Channel("presets") + Emitter emitter; /** * The reactive REDIS client for retrieving the activated payment notices @@ -167,7 +173,7 @@ public Uni preClose( .call(notices -> { if (!notices.isEmpty()) { var transactionEntity = createPaymentTransactionEntity(headers, transactionId, - null, notices, preCloseRequest.getOutcome()); + null, notices, preCloseRequest.getOutcome(), preCloseRequest.getPreset()); var nodeClosePaymentRequest = createNodeClosePaymentRequest(PaymentMethod.PAYMENT_CARD.name(), LocalDateTime.ofInstant(Instant.now(), ZoneOffset.UTC).toString(), @@ -262,7 +268,8 @@ private Uni storePaymentTransaction(CommonHeader headers, PreCloseRequ preCloseRequest.getTransactionId(), preCloseRequest.getFee(), notices, - preCloseRequest.getOutcome()); + preCloseRequest.getOutcome(), + preCloseRequest.getPreset()); // store the payment transaction in the db Log.debugf("Storing payment transaction %s on DB", entity.paymentTransaction); @@ -286,6 +293,8 @@ private Uni storePaymentTransaction(CommonHeader headers, PreCloseRequ }) // return OK to the caller .map(e -> { + sendToQueue(e.paymentTransaction); + PreCloseResponse okResponse = new PreCloseResponse(); okResponse.setOutcome(Outcome.OK.name()); Log.debugf("preClose - Response: %s", okResponse); @@ -298,6 +307,18 @@ private Uni storePaymentTransaction(CommonHeader headers, PreCloseRequ } + /** + * Sends the payment transaction data to the preset topic, if the preset info is present + * + * @param paymentTransaction the payment transaction data to be sent to the topic + */ + private void sendToQueue(PaymentTransaction paymentTransaction) { + if (paymentTransaction.getPreset() != null) { + Log.debugf("Send to queue %s", paymentTransaction.toString()); + emitter.send(paymentTransaction); + } + } + /** * Closes a payment transaction previously created with the {@link #preClose(CommonHeader, PreCloseRequest) preClose} API. @@ -356,6 +377,7 @@ public Uni closePayment( return txEntity; }) .map(e -> { + sendToQueue(txEntity.paymentTransaction); Log.debugf("closePayment - Response status %s", Status.ACCEPTED); return Response.status(Status.ACCEPTED).build(); }); @@ -483,6 +505,9 @@ public Uni receivePaymentStatus( @Pattern(regexp = PaymentNoticeConsta return entity; }) .map(e -> { + + sendToQueue(e.paymentTransaction); + ReceivePaymentStatusResponse receiveResponse = new ReceivePaymentStatusResponse(); receiveResponse.setOutcome(Outcome.OK.toString()); Log.debugf("receivePaymentStatus - Response: %s", receiveResponse); @@ -558,6 +583,8 @@ private Uni callNodeClosePaymentOutcomeOk(ClosePaymentRequest closePay paymentTransaction.setPaymentTimestamp(closePaymentRequest.getPaymentTimestamp()); paymentTransaction.setCloseTimestamp(getTimestamp()); + sendToQueue(paymentTransaction); + Log.debugf("closePayment - Response %s", closePaymentResponse); // update transaction on DB diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d527bc3..7976b60 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -87,17 +87,18 @@ quarkus.cxf.client."nodo".service-interface=it.gov.pagopa.pagopa_api.nodeforpsp. # Quarkus reactive rest client configuration # ------------------------------------------------------------------------------ -%dev.quarkus.rest-client.node-rest-api.url=http://localhost:8088 +%dev.quarkus.rest-client.node-rest-api.url=http://localhost:9999 %test.quarkus.rest-client.node-rest-api.url=http://localhost:8088 %prod.quarkus.rest-client.node-rest-api.url=${node.rest-service.url} -%dev.node-rest-client.apim-subscription-key= +%dev.node-rest-client.apim-subscription-key=325634634 %test.node-rest-client.apim-subscription-key=abc %prod.node-rest-client.apim-subscription-key=${node-rest-client-subscription-key} node-rest-client.client-id=swclient -%dev.quarkus.rest-client.mil-rest-api.url=https://mil-d-apim.azure-api.net +%dev.quarkus.rest-client.mil-rest-api.url=http://localhost:9999 +#%dev.quarkus.rest-client.mil-rest-api.url=https://mil-d-apim.azure-api.net %test.quarkus.rest-client.mil-rest-api.url=http://localhost:8088 %prod.quarkus.rest-client.mil-rest-api.url=${mil.rest-service.url} @@ -118,7 +119,33 @@ node-rest-client.client-id=swclient %prod.quarkus.rest-client.connect-timeout=${paymentnotice.rest-client.connect-timeout} %prod.quarkus.rest-client.read-timeout=${paymentnotice.rest-client.read-timeout} +# ------------------------------------------------------------------------------ +# Kafka / Azure Event Bus configuration (see https://quarkus.io/guides/kafka#azure-event-hub) +# +# kafka.bootstrap.servers=.servicebus.windows.net:9093 +# kafka.security.protocol=SASL_SSL +# kafka.sasl.mechanism=PLAIN +# kafka.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="$ConnectionString" password=""; +# ------------------------------------------------------------------------------ + +mp.messaging.outgoing.presets.connector=smallrye-kafka + +%dev.mp.messaging.outgoing.presets.topic=presets +%test.mp.messaging.outgoing.presets.topic=presets +%prod.mp.messaging.outgoing.presets.topic=${kafka-topic} +%dev.kafka.bootstrap.servers=milops.servicebus.windows.net:9093 +%dev.kafka.security.protocol=SASL_SSL +%dev.kafka.sasl.mechanism=PLAIN +%dev.kafka.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="" password=""; + +%test.kafka.bootstrap.servers=localhost:2024 +quarkus.kafka.devservices.enabled=false + +%prod.kafka.bootstrap.servers=${kafka-bootstrap-server} +%prod.kafka.security.protocol=SASL_SSL +%prod.kafka.sasl.mechanism=PLAIN +%prod.kafka.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="$ConnectionString" password="${kafka-connection-string-1}"; # ------------------------------------------------------------------------------ # Service configurations @@ -128,6 +155,7 @@ node-rest-client.client-id=swclient %dev.paymentnotice.closepayment.retry-after=30 %dev.paymentnotice.closepayment.location.base-url=https://mil-d-apim.azure-api.net/mil-payment-notice + %test.paymentnotice.closepayment.max-retry=3 %test.paymentnotice.closepayment.retry-after=30 %test.paymentnotice.closepayment.location.base-url=https://mil-d-apim.azure-api.net/mil-payment-notice @@ -216,4 +244,5 @@ node.error.map.PPT_ERRORE_EMESSO_DA_PAA-PAA_STAZIONE_INT_ERRATA=3 node.error.map.PPT_ERRORE_EMESSO_DA_PAA-PAA_ATTIVA_RPT_IMPORTO_NON_VALIDO=3 node.error.map.PPT_ERRORE_EMESSO_DA_PAA-PAA_SYSTEM_ERROR=3 node.error.map.PPT_ERRORE_EMESSO_DA_PAA-PAA_PAGAMENTO_IN_CORSO=4 -node.error.map.PPT_ERRORE_EMESSO_DA_PAA-PAA_PAGAMENTO_DUPLICATO=7 \ No newline at end of file +node.error.map.PPT_ERRORE_EMESSO_DA_PAA-PAA_PAGAMENTO_DUPLICATO=7 + diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/ActivatePaymentNoticeResourceTest.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/ActivatePaymentNoticeResourceTest.java index 4dd9ff7..5b8c356 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/ActivatePaymentNoticeResourceTest.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/ActivatePaymentNoticeResourceTest.java @@ -2,6 +2,7 @@ import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; import io.quarkus.test.junit.mockito.InjectMock; import io.restassured.http.ContentType; import io.restassured.response.Response; @@ -21,6 +22,7 @@ import it.pagopa.swclient.mil.paymentnotice.dao.Notice; import it.pagopa.swclient.mil.paymentnotice.redis.PaymentNoticeService; import it.pagopa.swclient.mil.paymentnotice.resource.ActivatePaymentNoticeResource; +import it.pagopa.swclient.mil.paymentnotice.resource.UnitTestProfile; import it.pagopa.swclient.mil.paymentnotice.util.ExceptionType; import it.pagopa.swclient.mil.paymentnotice.util.PaymentTestData; import it.pagopa.swclient.mil.paymentnotice.util.TestUtils; @@ -45,6 +47,7 @@ @QuarkusTest @TestHTTPEndpoint(ActivatePaymentNoticeResource.class) +@TestProfile(UnitTestProfile.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ActivatePaymentNoticeResourceTest { diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/AsyncClosePaymentProcessorTest.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/AsyncClosePaymentProcessorTest.java index deed2dd..ab7f814 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/AsyncClosePaymentProcessorTest.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/AsyncClosePaymentProcessorTest.java @@ -4,6 +4,7 @@ package it.pagopa.swclient.mil.paymentnotice; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; import io.quarkus.test.junit.mockito.InjectMock; import io.smallrye.mutiny.Context; import io.smallrye.mutiny.ItemWithContext; @@ -21,6 +22,7 @@ import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionEntity; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionStatus; import it.pagopa.swclient.mil.paymentnotice.resource.AsyncClosePaymentProcessor; +import it.pagopa.swclient.mil.paymentnotice.resource.UnitTestProfile; import it.pagopa.swclient.mil.paymentnotice.util.PaymentTestData; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; @@ -45,6 +47,7 @@ import java.util.concurrent.TimeUnit; @QuarkusTest +@TestProfile(UnitTestProfile.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class AsyncClosePaymentProcessorTest { @@ -79,7 +82,7 @@ void createTestObjects() { transactionId = RandomStringUtils.random(32, true, true); paymentTransactionEntity = PaymentTestData.getPaymentTransaction(transactionId, - PaymentTransactionStatus.PENDING, commonHeaders, 3); + PaymentTransactionStatus.PENDING, commonHeaders, 3, null); } diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/ClosePaymentResourceTest.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/ClosePaymentResourceTest.java index 55b5829..466099d 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/ClosePaymentResourceTest.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/ClosePaymentResourceTest.java @@ -2,10 +2,13 @@ import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; import io.quarkus.test.junit.mockito.InjectMock; import io.restassured.http.ContentType; import io.restassured.response.Response; import io.smallrye.mutiny.Uni; +import io.smallrye.reactive.messaging.memory.InMemoryConnector; +import io.smallrye.reactive.messaging.memory.InMemorySink; import it.pagopa.swclient.mil.paymentnotice.bean.ClosePaymentRequest; import it.pagopa.swclient.mil.paymentnotice.bean.Outcome; import it.pagopa.swclient.mil.paymentnotice.client.MilRestService; @@ -19,12 +22,17 @@ import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionRepository; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionStatus; import it.pagopa.swclient.mil.paymentnotice.resource.PaymentResource; +import it.pagopa.swclient.mil.paymentnotice.resource.UnitTestProfile; import it.pagopa.swclient.mil.paymentnotice.util.ExceptionType; import it.pagopa.swclient.mil.paymentnotice.util.PaymentTestData; import it.pagopa.swclient.mil.paymentnotice.util.TestUtils; +import jakarta.enterprise.inject.Any; +import jakarta.inject.Inject; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.microprofile.reactive.messaging.Message; import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -34,6 +42,9 @@ import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.shaded.org.awaitility.Awaitility; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -48,9 +59,12 @@ @QuarkusTest @TestHTTPEndpoint(PaymentResource.class) +@TestProfile(UnitTestProfile.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ClosePaymentResourceTest { + static final Logger logger = LoggerFactory.getLogger(ClosePaymentResourceTest.class); + @InjectMock @RestClient NodeRestService nodeRestService; @@ -62,6 +76,9 @@ class ClosePaymentResourceTest { @InjectMock PaymentTransactionRepository paymentTransactionRepository; + @Inject @Any + InMemoryConnector connector; + ClosePaymentRequest closePaymentRequestOK; ClosePaymentRequest closePaymentRequestKO; @@ -72,8 +89,12 @@ class ClosePaymentResourceTest { PaymentTransactionEntity paymentTransactionEntity; + PaymentTransactionEntity paymentTransactionPresetEntity; + String transactionId; + int receivedMessage = 0; + @BeforeAll void createTestObjects() { @@ -90,10 +111,18 @@ void createTestObjects() { transactionId = RandomStringUtils.random(32, true, true); paymentTransactionEntity = PaymentTestData.getPaymentTransaction(transactionId, - PaymentTransactionStatus.PENDING, commonHeaders, 3); + PaymentTransactionStatus.PENDING, commonHeaders, 3, null); + + paymentTransactionPresetEntity = PaymentTestData.getPaymentTransaction(transactionId, + PaymentTransactionStatus.PENDING, commonHeaders, 3, PaymentTestData.getPreset()); } + @AfterAll + void cleanUp() { + connector.sink("presets").clear(); + } + @Test void testClosePayment_200_node200_OK() { @@ -115,7 +144,7 @@ void testClosePayment_200_node200_OK() { Mockito .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) - .thenReturn(Uni.createFrom().item(paymentTransactionEntity)); + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Response response = given() @@ -187,6 +216,68 @@ void testClosePayment_200_node200_OK() { } + @Test + void testClosePayment_200_node200_OK_preset() { + + NodeClosePaymentResponse nodeClosePaymentResponse = new NodeClosePaymentResponse(); + nodeClosePaymentResponse.setOutcome(Outcome.OK.name()); + + Mockito + .when(milRestService.getPspConfiguration(Mockito.any(String.class), Mockito.any(String.class))) + .thenReturn(Uni.createFrom().item(acquirerConfiguration)); + + Mockito + .when(paymentTransactionRepository.findById(Mockito.any(String.class))) + .thenReturn(Uni.createFrom().item(paymentTransactionPresetEntity)); + + Mockito + .when(nodeRestService.closePayment(Mockito.anyString(), Mockito.any())) + .thenReturn(Uni.createFrom().item(nodeClosePaymentResponse)); + + Mockito + .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); + + + Response response = given() + .contentType(ContentType.JSON) + .headers(commonHeaders) + .and() + .pathParam("transactionId", transactionId) + .and() + .body(closePaymentRequestOK) + .when() + .patch("/{transactionId}") + .then() + .extract() + .response(); + + Assertions.assertEquals(200, response.statusCode()); + Assertions.assertNull(response.jsonPath().getJsonObject("errors")); + Assertions.assertEquals(Outcome.OK.name(), response.jsonPath().getString("outcome")); + Assertions.assertTrue(response.getHeader("Location") != null && + response.getHeader("Location").endsWith("/" + transactionId)); + Assertions.assertNotNull(response.getHeader("Retry-After")); + Assertions.assertNotNull(response.getHeader("Max-Retries")); + + // check topic integration + ArgumentCaptor captorEntity = ArgumentCaptor.forClass(PaymentTransactionEntity.class); + Mockito.verify(paymentTransactionRepository).update(captorEntity.capture()); + + receivedMessage++; + InMemorySink presetsOut = connector.sink("presets"); + Awaitility.await().>>until(presetsOut::received, t -> t.size() == receivedMessage); + + PaymentTransaction message = presetsOut.received().get(receivedMessage-1).getPayload(); + logger.info("Topic message: {}", message); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getTransactionId(), message.getTransactionId()); + Assertions.assertEquals(captorEntity.getValue().paymentTransaction.getStatus(), message.getStatus()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getPresetId(), message.getPreset().getPresetId()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getSubscriberId(), message.getPreset().getSubscriberId()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getPaTaxCode(), message.getPreset().getPaTaxCode()); + + } + @Test void testClosePayment_200_node200_KO() { @@ -207,7 +298,7 @@ void testClosePayment_200_node200_KO() { Mockito .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) - .thenReturn(Uni.createFrom().item(paymentTransactionEntity)); + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Response response = given() .contentType(ContentType.JSON) @@ -236,6 +327,65 @@ void testClosePayment_200_node200_KO() { Mockito.verify(paymentTransactionRepository).update(captorEntity.capture()); validateDBUpdate(captorEntity.getValue(), paymentTransaction, PaymentTransactionStatus.ERROR_ON_CLOSE); + } + + @Test + void testClosePayment_200_node200_KO_preset() { + + NodeClosePaymentResponse nodeClosePaymentResponse = new NodeClosePaymentResponse(); + nodeClosePaymentResponse.setOutcome(Outcome.KO.name()); + + Mockito + .when(milRestService.getPspConfiguration(Mockito.any(String.class), Mockito.any(String.class))) + .thenReturn(Uni.createFrom().item(acquirerConfiguration)); + + Mockito + .when(paymentTransactionRepository.findById(Mockito.any(String.class))) + .thenReturn(Uni.createFrom().item(paymentTransactionPresetEntity)); + + Mockito + .when(nodeRestService.closePayment(Mockito.anyString(), Mockito.any())) + .thenReturn(Uni.createFrom().item(nodeClosePaymentResponse)); + + Mockito + .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); + + Response response = given() + .contentType(ContentType.JSON) + .headers(commonHeaders) + .and() + .pathParam("transactionId", transactionId) + .and() + .body(closePaymentRequestOK) + .when() + .patch("/{transactionId}") + .then() + .extract() + .response(); + + Assertions.assertEquals(200, response.statusCode()); + Assertions.assertNull(response.jsonPath().getJsonObject("errors")); + Assertions.assertEquals(Outcome.KO.name(), response.jsonPath().getString("outcome")); + Assertions.assertNull(response.getHeader("Location")); + Assertions.assertNull(response.getHeader("Retry-After")); + Assertions.assertNull(response.getHeader("Max-Retries")); + + // check topic integration + ArgumentCaptor captorEntity = ArgumentCaptor.forClass(PaymentTransactionEntity.class); + Mockito.verify(paymentTransactionRepository).update(captorEntity.capture()); + + receivedMessage++; + InMemorySink presetsOut = connector.sink("presets"); + Awaitility.await().>>until(presetsOut::received, t -> t.size() == receivedMessage); + + PaymentTransaction message = presetsOut.received().get(receivedMessage-1).getPayload(); + logger.info("Topic message: {}", message); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getTransactionId(), message.getTransactionId()); + Assertions.assertEquals(captorEntity.getValue().paymentTransaction.getStatus(), message.getStatus()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getPresetId(), message.getPreset().getPresetId()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getSubscriberId(), message.getPreset().getSubscriberId()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getPaTaxCode(), message.getPreset().getPaTaxCode()); } @@ -258,7 +408,7 @@ void testClosePayment_200_nodeError_KO(int statusCode) { Mockito .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) - .thenReturn(Uni.createFrom().item(paymentTransactionEntity)); + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Response response = given() .contentType(ContentType.JSON) @@ -308,7 +458,7 @@ void testClosePayment_200_nodeError_OK(int statusCode) { Mockito .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) - .thenReturn(Uni.createFrom().item(paymentTransactionEntity)); + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Response response = given() .contentType(ContentType.JSON) @@ -357,7 +507,7 @@ void testClosePayment_200_nodeUnparsable() { Mockito .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) - .thenReturn(Uni.createFrom().item(paymentTransactionEntity)); + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Response response = given() @@ -408,7 +558,7 @@ void testClosePayment_200_nodeTimeout() { Mockito .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) - .thenReturn(Uni.createFrom().item(paymentTransactionEntity)); + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Response response = given() @@ -755,7 +905,7 @@ void testClosePaymentKO_200_nodeOK() { Mockito .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) - .thenReturn(Uni.createFrom().item(paymentTransactionEntity)); + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Mockito .when(nodeRestService.closePayment(Mockito.anyString(), Mockito.any(NodeClosePaymentRequest.class))) @@ -801,6 +951,62 @@ void testClosePaymentKO_200_nodeOK() { } + @Test + void testClosePaymentKO_200_nodeOK_preset() { + + NodeClosePaymentResponse nodeClosePaymentResponse = new NodeClosePaymentResponse(); + nodeClosePaymentResponse.setOutcome(Outcome.OK.name()); + + Mockito + .when(milRestService.getPspConfiguration(Mockito.any(String.class), Mockito.any(String.class))) + .thenReturn(Uni.createFrom().item(acquirerConfiguration)); + + Mockito + .when(paymentTransactionRepository.findById(Mockito.any(String.class))) + .thenReturn(Uni.createFrom().item(paymentTransactionPresetEntity)); + + Mockito + .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); + + Mockito + .when(nodeRestService.closePayment(Mockito.anyString(), Mockito.any(NodeClosePaymentRequest.class))) + .thenReturn(Uni.createFrom().item(nodeClosePaymentResponse)); + + + Response response = given() + .contentType(ContentType.JSON) + .headers(commonHeaders) + .and() + .pathParam("transactionId", transactionId) + .and() + .body(closePaymentRequestKO) + .when() + .patch("/{transactionId}") + .then() + .extract() + .response(); + + Assertions.assertEquals(202, response.statusCode()); + + // check topic integration + ArgumentCaptor captorEntity = ArgumentCaptor.forClass(PaymentTransactionEntity.class); + Mockito.verify(paymentTransactionRepository).update(captorEntity.capture()); + + receivedMessage++; + InMemorySink presetsOut = connector.sink("presets"); + Awaitility.await().>>until(presetsOut::received, t -> t.size() == receivedMessage); + + PaymentTransaction message = presetsOut.received().get(receivedMessage-1).getPayload(); + logger.info("Topic message: {}", message); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getTransactionId(), message.getTransactionId()); + Assertions.assertEquals(captorEntity.getValue().paymentTransaction.getStatus(), message.getStatus()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getPresetId(), message.getPreset().getPresetId()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getSubscriberId(), message.getPreset().getSubscriberId()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getPaTaxCode(), message.getPreset().getPaTaxCode()); + + } + @Test void testClosePaymentKO_200_dbError() { @@ -818,7 +1024,7 @@ void testClosePaymentKO_200_dbError() { Mockito .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) .thenReturn(Uni.createFrom().failure(TestUtils.getException(ExceptionType.DB_TIMEOUT_EXCEPTION))) - .thenReturn(Uni.createFrom().item(paymentTransactionEntity)); + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Mockito .when(nodeRestService.closePayment(Mockito.anyString(), Mockito.any(NodeClosePaymentRequest.class))) diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/ManagePaymentResultTest.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/ManagePaymentResultTest.java index e51dbc6..bfd4618 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/ManagePaymentResultTest.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/ManagePaymentResultTest.java @@ -13,17 +13,25 @@ import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery; import io.quarkus.panache.common.Sort; +import io.quarkus.test.junit.TestProfile; +import io.smallrye.reactive.messaging.memory.InMemoryConnector; +import io.smallrye.reactive.messaging.memory.InMemorySink; import it.pagopa.swclient.mil.paymentnotice.bean.Payment; import it.pagopa.swclient.mil.paymentnotice.dao.Notice; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransaction; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionEntity; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionRepository; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionStatus; +import it.pagopa.swclient.mil.paymentnotice.resource.UnitTestProfile; import it.pagopa.swclient.mil.paymentnotice.util.ExceptionType; import it.pagopa.swclient.mil.paymentnotice.util.TestUtils; import it.pagopa.swclient.mil.paymentnotice.resource.PaymentResource; +import jakarta.enterprise.inject.Any; +import jakarta.inject.Inject; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -42,20 +50,30 @@ import it.pagopa.swclient.mil.paymentnotice.bean.Outcome; import it.pagopa.swclient.mil.paymentnotice.bean.ReceivePaymentStatusRequest; import it.pagopa.swclient.mil.paymentnotice.util.PaymentTestData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.shaded.org.awaitility.Awaitility; @QuarkusTest @TestHTTPEndpoint(PaymentResource.class) +@TestProfile(UnitTestProfile.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ManagePaymentResultTest { + static final Logger logger = LoggerFactory.getLogger(ManagePaymentResultTest.class); @InjectMock PaymentTransactionRepository paymentTransactionRepository; + @Inject @Any + InMemoryConnector connector; + Map commonHeaders; PaymentTransactionEntity paymentTransactionEntity; + PaymentTransactionEntity paymentTransactionPresetEntity; + ReceivePaymentStatusRequest paymentStatusOK; ReceivePaymentStatusRequest paymentStatusKO; @@ -64,6 +82,8 @@ class ManagePaymentResultTest { String paymentDate; + int receivedMessage = 0; + @BeforeAll void createTestObjects() { @@ -75,7 +95,10 @@ void createTestObjects() { paymentDate = LocalDateTime.ofInstant(Instant.now().truncatedTo(ChronoUnit.SECONDS), ZoneOffset.UTC).toString(); paymentTransactionEntity = PaymentTestData.getPaymentTransaction(transactionId, - PaymentTransactionStatus.PENDING, commonHeaders, 3); + PaymentTransactionStatus.PENDING, commonHeaders, 3, null); + + paymentTransactionPresetEntity = PaymentTestData.getPaymentTransaction(transactionId, + PaymentTransactionStatus.PENDING, commonHeaders, 3, PaymentTestData.getPreset()); List paymentList = new ArrayList<>(); @@ -103,6 +126,11 @@ void createTestObjects() { } + @AfterAll + void cleanUp() { + connector.sink("presets").clear(); + } + @Test void testGetPayments_200() { @@ -350,7 +378,7 @@ void testReceivePaymentStatusOK_200() { Mockito .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) - .thenReturn(Uni.createFrom().item(paymentTransactionEntity)); + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Response response = given() @@ -387,6 +415,49 @@ void testReceivePaymentStatusOK_200() { } } + void testReceivePaymentStatusOK_200_preset() { + + Mockito + .when(paymentTransactionRepository.findById(Mockito.anyString())) + .thenReturn(Uni.createFrom().item(paymentTransactionPresetEntity)); + + Mockito + .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); + + + Response response = given() + .contentType(ContentType.JSON) + .and() + .pathParam("transactionId", transactionId) + .body(paymentStatusOK) + .when() + .post("/{transactionId}") + .then() + .extract() + .response(); + + Assertions.assertEquals(200, response.statusCode()); + Assertions.assertEquals(Outcome.OK.toString(), response.jsonPath().getString("outcome")); + Assertions.assertNull(response.jsonPath().getJsonObject("errors")); + + // check topic integration + ArgumentCaptor captorEntity = ArgumentCaptor.forClass(PaymentTransactionEntity.class); + Mockito.verify(paymentTransactionRepository).update(captorEntity.capture()); + + receivedMessage++; + InMemorySink presetsOut = connector.sink("presets"); + Awaitility.await().>>until(presetsOut::received, t -> t.size() == receivedMessage); + + PaymentTransaction message = presetsOut.received().get(receivedMessage-1).getPayload(); + logger.info("Topic message: {}", message); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getTransactionId(), message.getTransactionId()); + Assertions.assertEquals(captorEntity.getValue().paymentTransaction.getStatus(), message.getStatus()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getPresetId(), message.getPreset().getPresetId()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getSubscriberId(), message.getPreset().getSubscriberId()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getPaTaxCode(), message.getPreset().getPaTaxCode()); + } + @Test void testReceivePaymentStatusKO_200() { @@ -396,7 +467,7 @@ void testReceivePaymentStatusKO_200() { Mockito .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) - .thenReturn(Uni.createFrom().item(paymentTransactionEntity)); + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Response response = given() @@ -433,6 +504,50 @@ void testReceivePaymentStatusKO_200() { } } + @Test + void testReceivePaymentStatusKO_200_preset() { + + Mockito + .when(paymentTransactionRepository.findById(Mockito.anyString())) + .thenReturn(Uni.createFrom().item(paymentTransactionPresetEntity)); + + Mockito + .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); + + + Response response = given() + .contentType(ContentType.JSON) + .and() + .pathParam("transactionId", transactionId) + .body(paymentStatusKO) + .when() + .post("/{transactionId}") + .then() + .extract() + .response(); + + Assertions.assertEquals(200, response.statusCode()); + Assertions.assertEquals(Outcome.OK.toString(), response.jsonPath().getString("outcome")); + Assertions.assertNull(response.jsonPath().getJsonObject("errors")); + + // check topic integration + ArgumentCaptor captorEntity = ArgumentCaptor.forClass(PaymentTransactionEntity.class); + Mockito.verify(paymentTransactionRepository).update(captorEntity.capture()); + + receivedMessage++; + InMemorySink presetsOut = connector.sink("presets"); + Awaitility.await().>>until(presetsOut::received, t -> t.size() == receivedMessage); + + PaymentTransaction message = presetsOut.received().get(receivedMessage-1).getPayload(); + logger.info("Topic message: {}", message); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getTransactionId(), message.getTransactionId()); + Assertions.assertEquals(captorEntity.getValue().paymentTransaction.getStatus(), message.getStatus()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getPresetId(), message.getPreset().getPresetId()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getSubscriberId(), message.getPreset().getSubscriberId()); + Assertions.assertEquals(paymentTransactionPresetEntity.paymentTransaction.getPreset().getPaTaxCode(), message.getPreset().getPaTaxCode()); + } + @Test void testReceivePaymentStatus_404() { @@ -492,7 +607,7 @@ void testReceivePaymentResult_500_db_errorWrite() { Mockito .when(paymentTransactionRepository.update(Mockito.any(PaymentTransactionEntity.class))) .thenReturn(Uni.createFrom().failure(TestUtils.getException(ExceptionType.DB_TIMEOUT_EXCEPTION))) - .thenReturn(Uni.createFrom().item(paymentTransactionEntity)); + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Response response = given() .contentType(ContentType.JSON) diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/PreCloseResourceTest.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/PreCloseResourceTest.java index 829ab73..3d93c3d 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/PreCloseResourceTest.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/PreCloseResourceTest.java @@ -1,5 +1,30 @@ package it.pagopa.swclient.mil.paymentnotice; +import static io.restassured.RestAssured.given; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.quarkus.test.junit.TestProfile; +import io.smallrye.reactive.messaging.memory.InMemoryConnector; +import io.smallrye.reactive.messaging.memory.InMemorySink; +import it.pagopa.swclient.mil.paymentnotice.resource.UnitTestProfile; +import jakarta.enterprise.inject.Any; +import jakarta.inject.Inject; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.mockito.InjectMock; @@ -11,6 +36,7 @@ import it.pagopa.swclient.mil.paymentnotice.client.MilRestService; import it.pagopa.swclient.mil.paymentnotice.client.bean.AcquirerConfiguration; import it.pagopa.swclient.mil.paymentnotice.dao.Notice; +import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransaction; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionEntity; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionRepository; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionStatus; @@ -19,28 +45,18 @@ import it.pagopa.swclient.mil.paymentnotice.util.ExceptionType; import it.pagopa.swclient.mil.paymentnotice.util.PaymentTestData; import it.pagopa.swclient.mil.paymentnotice.util.TestUtils; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static io.restassured.RestAssured.given; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.shaded.org.awaitility.Awaitility; @QuarkusTest @TestHTTPEndpoint(PaymentResource.class) +@TestProfile(UnitTestProfile.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class PreCloseResourceTest { + static final Logger logger = LoggerFactory.getLogger(PreCloseResourceTest.class); + @InjectMock @RestClient MilRestService milRestService; @@ -51,8 +67,13 @@ class PreCloseResourceTest { @InjectMock PaymentNoticeService paymentNoticeService; + @Inject @Any + InMemoryConnector connector; + PreCloseRequest preCloseRequest; + PreCloseRequest preClosePresetRequest; + PreCloseRequest abortRequest; AcquirerConfiguration acquirerConfiguration; @@ -70,10 +91,17 @@ void createTestObjects() { // acquirer PSP configuration acquirerConfiguration = PaymentTestData.getAcquirerConfiguration(); - preCloseRequest = PaymentTestData.getPreCloseRequest(true, tokens); + preCloseRequest = PaymentTestData.getPreCloseRequest(true, tokens, false); - abortRequest = PaymentTestData.getPreCloseRequest(false, tokens); + preClosePresetRequest = PaymentTestData.getPreCloseRequest(true, tokens, true); + abortRequest = PaymentTestData.getPreCloseRequest(false, tokens, false); + + } + + @AfterAll + void cleanUp() { + connector.sink("presets").clear(); } @Test @@ -88,8 +116,7 @@ void testPreClose_201() { Mockito .when(paymentTransactionRepository.persist(Mockito.any(PaymentTransactionEntity.class))) - .thenReturn(Uni.createFrom().item(new PaymentTransactionEntity())); - + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); Response response = given() .contentType(ContentType.JSON) @@ -136,6 +163,54 @@ void testPreClose_201() { } + @Test + void testPreClose_201_preset() { + + final Map noticeMap = getNoticeMap(preClosePresetRequest.getPaymentTokens(), + preClosePresetRequest.getPaymentTokens().size()); + + Mockito + .when(paymentNoticeService.mget(Mockito.any())) + .thenReturn(Uni.createFrom().item(noticeMap)); + + Mockito + .when(paymentTransactionRepository.persist(Mockito.any(PaymentTransactionEntity.class))) + .then(i-> Uni.createFrom().item(i.getArgument(0, PaymentTransactionEntity.class))); + + Response response = given() + .contentType(ContentType.JSON) + .headers(validMilHeaders) + .and() + .body(preClosePresetRequest) + .when() + .post("/") + .then() + .extract() + .response(); + + Assertions.assertEquals(201, response.statusCode()); + Assertions.assertNull(response.jsonPath().getJsonObject("errors")); + Assertions.assertEquals(Outcome.OK.name(), response.jsonPath().getString("outcome")); + Assertions.assertTrue(response.getHeader("Location") != null && + response.getHeader("Location").endsWith("/payments/" + preClosePresetRequest.getTransactionId())); + + // check topic integration + ArgumentCaptor captorEntity = ArgumentCaptor.forClass(PaymentTransactionEntity.class); + Mockito.verify(paymentTransactionRepository).persist(captorEntity.capture()); + + InMemorySink presetsOut = connector.sink("presets"); + Awaitility.await().>>until(presetsOut::received, t -> t.size() == 1); + + PaymentTransaction message = presetsOut.received().get(0).getPayload(); + logger.info("Topic message: {}", message); + Assertions.assertEquals(preClosePresetRequest.getTransactionId(), message.getTransactionId()); + Assertions.assertEquals(captorEntity.getValue().paymentTransaction.getStatus(), message.getStatus()); + Assertions.assertEquals(preClosePresetRequest.getPreset().getPresetId(), message.getPreset().getPresetId()); + Assertions.assertEquals(preClosePresetRequest.getPreset().getSubscriberId(), message.getPreset().getSubscriberId()); + Assertions.assertEquals(preClosePresetRequest.getPreset().getPaTaxCode(), message.getPreset().getPaTaxCode()); + + } + @ParameterizedTest @MethodSource("it.pagopa.swclient.mil.paymentnotice.util.TestUtils#provideHeaderValidationErrorCases") void testPreClose_400_invalidHeaders(Map invalidHeaders, String errorCode) { diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/QrCodeParserTest.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/QrCodeParserTest.java index 9231a10..02b2a04 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/QrCodeParserTest.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/QrCodeParserTest.java @@ -3,6 +3,8 @@ */ package it.pagopa.swclient.mil.paymentnotice; +import io.quarkus.test.junit.TestProfile; +import it.pagopa.swclient.mil.paymentnotice.resource.UnitTestProfile; import it.pagopa.swclient.mil.paymentnotice.util.PaymentTestData; import it.pagopa.swclient.mil.paymentnotice.utils.QrCodeParser; import org.junit.jupiter.api.Assertions; @@ -18,6 +20,7 @@ import java.util.Base64; @QuarkusTest +@TestProfile(UnitTestProfile.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class QrCodeParserTest { diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/StAmountFormatterTest.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/StAmountFormatterTest.java index b12a558..15b0eaf 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/StAmountFormatterTest.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/StAmountFormatterTest.java @@ -5,6 +5,8 @@ import java.math.BigDecimal; +import io.quarkus.test.junit.TestProfile; +import it.pagopa.swclient.mil.paymentnotice.resource.UnitTestProfile; import it.pagopa.swclient.mil.paymentnotice.utils.StAmountFormatter; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -13,6 +15,7 @@ import io.quarkus.test.junit.QuarkusTest; @QuarkusTest +@TestProfile(UnitTestProfile.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class StAmountFormatterTest { diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/VerifyPaymentNoticeResourceTest.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/VerifyPaymentNoticeResourceTest.java index b4d8d25..565a5ed 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/VerifyPaymentNoticeResourceTest.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/VerifyPaymentNoticeResourceTest.java @@ -11,6 +11,8 @@ import java.util.GregorianCalendar; import java.util.Map; +import io.quarkus.test.junit.TestProfile; +import it.pagopa.swclient.mil.paymentnotice.resource.UnitTestProfile; import jakarta.inject.Inject; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeConstants; @@ -53,6 +55,7 @@ @QuarkusTest @TestHTTPEndpoint(VerifyPaymentNoticeResource.class) +@TestProfile(UnitTestProfile.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class VerifyPaymentNoticeResourceTest { diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/ClosePaymentResourceTestIT.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/ClosePaymentResourceTestIT.java index 5ceb03a..e70ba52 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/ClosePaymentResourceTestIT.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/ClosePaymentResourceTestIT.java @@ -17,13 +17,18 @@ import it.pagopa.swclient.mil.paymentnotice.bean.ClosePaymentRequest; import it.pagopa.swclient.mil.paymentnotice.bean.Outcome; import it.pagopa.swclient.mil.paymentnotice.bean.PaymentTransactionOutcome; +import it.pagopa.swclient.mil.paymentnotice.bean.Preset; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransaction; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionEntity; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionStatus; import it.pagopa.swclient.mil.paymentnotice.resource.PaymentResource; +import it.pagopa.swclient.mil.paymentnotice.util.KafkaUtils; import it.pagopa.swclient.mil.paymentnotice.util.PaymentTestData; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; import org.bson.codecs.configuration.CodecRegistries; import org.bson.codecs.configuration.CodecRegistry; import org.bson.codecs.pojo.PojoCodecProvider; @@ -38,6 +43,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -65,6 +71,8 @@ class ClosePaymentResourceTestIT implements DevServicesContext.ContextAware { CodecRegistry pojoCodecRegistry; + KafkaConsumer paymentTransactionConsumer; + String unknownTransactionId; String paymentTimestamp; @@ -92,20 +100,20 @@ void createTestObjects() { // store transactions on DB unknownTransactionId = RandomStringUtils.random(32, true, true); - List transactionIdList = List.of( - PaymentTestData.PAY_TID_NODE_OK, - PaymentTestData.PAY_TID_NODE_KO, - PaymentTestData.PAY_TID_NODE_400, - PaymentTestData.PAY_TID_NODE_404, - PaymentTestData.PAY_TID_NODE_408, - PaymentTestData.PAY_TID_NODE_422, - PaymentTestData.PAY_TID_NODE_500, - PaymentTestData.PAY_TID_NODE_UNPARSABLE, - PaymentTestData.PAY_TID_NODE_TIMEOUT + List> transactionIdList = List.of( + Pair.of(PaymentTestData.PAY_TID_NODE_OK, PaymentTestData.getPreset()), + Pair.of(PaymentTestData.PAY_TID_NODE_KO, null), + Pair.of(PaymentTestData.PAY_TID_NODE_400, null), + Pair.of(PaymentTestData.PAY_TID_NODE_404, null), + Pair.of(PaymentTestData.PAY_TID_NODE_408, null), + Pair.of(PaymentTestData.PAY_TID_NODE_422, null), + Pair.of(PaymentTestData.PAY_TID_NODE_500, null), + Pair.of(PaymentTestData.PAY_TID_NODE_UNPARSABLE, null), + Pair.of(PaymentTestData.PAY_TID_NODE_TIMEOUT, null) ); List paymentTransactionEntities = transactionIdList.stream() - .map(tx -> PaymentTestData.getPaymentTransaction(tx, PaymentTransactionStatus.PRE_CLOSE, validMilHeaders, 1)) + .map(p -> PaymentTestData.getPaymentTransaction(p.getLeft(), PaymentTransactionStatus.PRE_CLOSE, validMilHeaders, 1, p.getRight())) .toList(); MongoCollection collection = mongoClient.getDatabase("mil") @@ -117,6 +125,8 @@ void createTestObjects() { paymentTimestamp = LocalDateTime.ofInstant(Instant.now().truncatedTo(ChronoUnit.SECONDS), ZoneOffset.UTC) .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + paymentTransactionConsumer = KafkaUtils.getKafkaConsumer(devServicesContext, PaymentTransaction.class); + } @AfterAll @@ -128,6 +138,13 @@ void destroyTestObjects() { logger.error("Error while closing mongo client", e); } + try { + paymentTransactionConsumer.unsubscribe(); + paymentTransactionConsumer.close(); + } catch (Exception e){ + logger.error("Error while closing kafka consumer", e); + } + } ClosePaymentRequest getClosePaymentRequest(PaymentTransactionOutcome outcome) { @@ -165,7 +182,20 @@ void testClosePayment_200_node200_OK() { Assertions.assertNotNull(response.getHeader("Max-Retries")); // check transaction written on DB - checkDatabaseData(PaymentTestData.PAY_TID_NODE_OK, PaymentTransactionStatus.PENDING); + PaymentTransaction dbPaymentTransaction = checkDatabaseData(PaymentTestData.PAY_TID_NODE_OK, PaymentTransactionStatus.PENDING); + + // check transaction sent to topic + Instant start = Instant.now(); + ConsumerRecords records = paymentTransactionConsumer.poll(Duration.ofSeconds(10)); + paymentTransactionConsumer.commitSync(); + logger.info("Finished polling in {} seconds, found {} records", Duration.between(start, Instant.now()), records.count()); + Assertions.assertEquals(1, records.count()); + + PaymentTransaction topicPaymentTransaction = records.iterator().next().value(); + Assertions.assertEquals(dbPaymentTransaction.getTransactionId(), topicPaymentTransaction.getTransactionId()); + Assertions.assertEquals(dbPaymentTransaction.getStatus(), topicPaymentTransaction.getStatus()); + Assertions.assertEquals(dbPaymentTransaction.getPreset().getPresetId(), topicPaymentTransaction.getPreset().getPresetId()); + Assertions.assertEquals(dbPaymentTransaction.getPreset().getSubscriberId(), topicPaymentTransaction.getPreset().getSubscriberId()); } @@ -385,7 +415,7 @@ void testClosePaymentKO_200_nodeOK() { } - private void checkDatabaseData(String transactionId, PaymentTransactionStatus transactionStatus) { + private PaymentTransaction checkDatabaseData(String transactionId, PaymentTransactionStatus transactionStatus) { MongoCollection collection = mongoClient.getDatabase("mil") .getCollection("paymentTransactions", PaymentTransactionEntity.class) @@ -394,13 +424,15 @@ private void checkDatabaseData(String transactionId, PaymentTransactionStatus tr Bson filter = Filters.in("_id", transactionId); FindIterable documents = collection.find(filter); + PaymentTransaction paymentTransaction; + try (MongoCursor iterator = documents.iterator()) { Assertions.assertTrue(iterator.hasNext()); PaymentTransactionEntity paymentTransactionEntity = iterator.next(); logger.info("Found transaction on DB: {}", paymentTransactionEntity.paymentTransaction); - PaymentTransaction paymentTransaction = paymentTransactionEntity.paymentTransaction; + paymentTransaction = paymentTransactionEntity.paymentTransaction; Assertions.assertEquals(transactionId, paymentTransaction.getTransactionId()); Assertions.assertEquals(transactionStatus.name(), paymentTransaction.getStatus()); @@ -411,6 +443,8 @@ private void checkDatabaseData(String transactionId, PaymentTransactionStatus tr Assertions.assertNull(paymentTransaction.getCallbackTimestamp()); } + + return paymentTransaction; } } diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/CustomRedpandaContainer.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/CustomRedpandaContainer.java new file mode 100644 index 0000000..272bfd2 --- /dev/null +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/CustomRedpandaContainer.java @@ -0,0 +1,37 @@ +package it.pagopa.swclient.mil.paymentnotice.it; + +import com.github.dockerjava.api.command.InspectContainerResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.images.builder.Transferable; +import org.testcontainers.redpanda.RedpandaContainer; +import org.testcontainers.utility.DockerImageName; + +public class CustomRedpandaContainer extends RedpandaContainer { + + private static final Logger logger = LoggerFactory.getLogger(CustomRedpandaContainer.class); + + public CustomRedpandaContainer(String image) { + super(image); + } + + public CustomRedpandaContainer(DockerImageName imageName) { + super(imageName); + } + + protected void containerIsStarting(InspectContainerResponse containerInfo) { + super.containerIsStarting(containerInfo); + + logger.info("getNetworkAliases() -> {}", this.getNetworkAliases()); + + String command = "#!/bin/bash\n"; + command = command + "/usr/bin/rpk redpanda start --mode dev-container "; + command = command + "--kafka-addr PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092 "; + command = command + "--advertise-kafka-addr PLAINTEXT://" + this.getNetworkAliases().get(this.getNetworkAliases().size()-1) + ":29092,OUTSIDE://" + this.getHost() + ":" + this.getMappedPort(9092); + this.copyFileToContainer(Transferable.of(command, 511), "/testcontainers_start.sh"); + + + + } + +} diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/IntegrationTestProfile.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/IntegrationTestProfile.java index 562e935..737b12a 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/IntegrationTestProfile.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/IntegrationTestProfile.java @@ -1,15 +1,17 @@ package it.pagopa.swclient.mil.paymentnotice.it; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import com.google.common.collect.ImmutableList; + import io.quarkus.test.junit.QuarkusTestProfile; +import it.pagopa.swclient.mil.paymentnotice.it.resource.KafkaTestResource; import it.pagopa.swclient.mil.paymentnotice.it.resource.MongoTestResource; import it.pagopa.swclient.mil.paymentnotice.it.resource.RedisTestResource; import it.pagopa.swclient.mil.paymentnotice.it.resource.WiremockTestResource; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - public class IntegrationTestProfile implements QuarkusTestProfile { @Override @@ -40,7 +42,8 @@ public List testResources() { return ImmutableList.of( new TestResourceEntry(WiremockTestResource.class), new TestResourceEntry(RedisTestResource.class), - new TestResourceEntry(MongoTestResource.class) + new TestResourceEntry(MongoTestResource.class), + new TestResourceEntry(KafkaTestResource.class) ); } diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/ManagePaymentResultTestIT.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/ManagePaymentResultTestIT.java index 951300f..68886dc 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/ManagePaymentResultTestIT.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/ManagePaymentResultTestIT.java @@ -1,5 +1,37 @@ package it.pagopa.swclient.mil.paymentnotice.it; +import static io.restassured.RestAssured.given; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import it.pagopa.swclient.mil.paymentnotice.util.KafkaUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.codecs.pojo.PojoCodecProvider; +import org.bson.conversions.Bson; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.mongodb.MongoClientSettings; import com.mongodb.client.FindIterable; import com.mongodb.client.MongoClient; @@ -7,6 +39,7 @@ import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; import com.mongodb.client.model.Filters; + import io.quarkus.test.common.DevServicesContext; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusIntegrationTest; @@ -22,33 +55,6 @@ import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionStatus; import it.pagopa.swclient.mil.paymentnotice.resource.PaymentResource; import it.pagopa.swclient.mil.paymentnotice.util.PaymentTestData; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.RandomUtils; -import org.apache.commons.lang3.StringUtils; -import org.bson.codecs.configuration.CodecRegistries; -import org.bson.codecs.configuration.CodecRegistry; -import org.bson.codecs.pojo.PojoCodecProvider; -import org.bson.conversions.Bson; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -import static io.restassured.RestAssured.given; @QuarkusIntegrationTest @TestProfile(IntegrationTestProfile.class) @@ -68,6 +74,8 @@ class ManagePaymentResultTestIT implements DevServicesContext.ContextAware { CodecRegistry pojoCodecRegistry; + KafkaConsumer paymentTransactionConsumer; + String closedTransactionId; String pendingTransactionIdOK; @@ -105,10 +113,10 @@ void createTestObjects() { pendingTransactionIdKO = RandomStringUtils.random(32, true, true); List paymentTransactionEntities = new ArrayList<>(); - paymentTransactionEntities.add(PaymentTestData.getPaymentTransaction(closedTransactionId, PaymentTransactionStatus.CLOSED, milHeaders, 1)); - PaymentTransactionEntity transactionOK = PaymentTestData.getPaymentTransaction(pendingTransactionIdOK, PaymentTransactionStatus.PENDING, milHeaders, 1); + paymentTransactionEntities.add(PaymentTestData.getPaymentTransaction(closedTransactionId, PaymentTransactionStatus.CLOSED, milHeaders, 1, null)); + PaymentTransactionEntity transactionOK = PaymentTestData.getPaymentTransaction(pendingTransactionIdOK, PaymentTransactionStatus.PENDING, milHeaders, 1, PaymentTestData.getPreset()); paymentTransactionEntities.add(transactionOK); - PaymentTransactionEntity transactionKO = PaymentTestData.getPaymentTransaction(pendingTransactionIdKO, PaymentTransactionStatus.PENDING, milHeaders, 1); + PaymentTransactionEntity transactionKO = PaymentTestData.getPaymentTransaction(pendingTransactionIdKO, PaymentTransactionStatus.PENDING, milHeaders, 1, PaymentTestData.getPreset()); paymentTransactionEntities.add(transactionKO); MongoCollection collection = mongoClient.getDatabase("mil") @@ -143,12 +151,14 @@ void createTestObjects() { List paymentTransactionGet = new ArrayList<>(); for(int i=0; i records = paymentTransactionConsumer.poll(Duration.ofSeconds(10)); + paymentTransactionConsumer.commitSync(); + logger.info("Finished polling in {} seconds, found {} records", Duration.between(start, Instant.now()), records.count()); + Assertions.assertEquals(1, records.count()); + + PaymentTransaction topicPaymentTransaction = records.iterator().next().value(); + Assertions.assertEquals(dbPaymentTransaction.getTransactionId(), topicPaymentTransaction.getTransactionId()); + Assertions.assertEquals(dbPaymentTransaction.getStatus(), topicPaymentTransaction.getStatus()); + Assertions.assertEquals(dbPaymentTransaction.getPreset().getPresetId(), topicPaymentTransaction.getPreset().getPresetId()); + Assertions.assertEquals(dbPaymentTransaction.getPreset().getSubscriberId(), topicPaymentTransaction.getPreset().getSubscriberId()); } @@ -300,7 +329,20 @@ void testReceivePaymentStatus_200_paymentKO() { Assertions.assertNull(response.jsonPath().getJsonObject("errors")); // check transaction written on DB - checkDatabaseData(pendingTransactionIdKO, timestamp, PaymentTransactionStatus.ERROR_ON_RESULT, List.of(paymentKO)); + PaymentTransaction dbPaymentTransaction = checkDatabaseData(pendingTransactionIdKO, timestamp, PaymentTransactionStatus.ERROR_ON_RESULT, List.of(paymentKO)); + + // check transaction sent to topic + Instant start = Instant.now(); + ConsumerRecords records = paymentTransactionConsumer.poll(Duration.ofSeconds(10)); + paymentTransactionConsumer.commitSync(); + logger.info("Finished polling in {} seconds, found {} records", Duration.between(start, Instant.now()), records.count()); + Assertions.assertEquals(1, records.count()); + + PaymentTransaction topicPaymentTransaction = records.iterator().next().value(); + Assertions.assertEquals(dbPaymentTransaction.getTransactionId(), topicPaymentTransaction.getTransactionId()); + Assertions.assertEquals(dbPaymentTransaction.getStatus(), topicPaymentTransaction.getStatus()); + Assertions.assertEquals(dbPaymentTransaction.getPreset().getPresetId(), topicPaymentTransaction.getPreset().getPresetId()); + Assertions.assertEquals(dbPaymentTransaction.getPreset().getSubscriberId(), topicPaymentTransaction.getPreset().getSubscriberId()); } @@ -333,9 +375,11 @@ void testReceivePaymentStatus_404() { } - private void checkDatabaseData(String transactionId, String paymentDate, PaymentTransactionStatus transactionStatus, + private PaymentTransaction checkDatabaseData(String transactionId, String paymentDate, PaymentTransactionStatus transactionStatus, List paymentList) { + PaymentTransaction paymentTransaction; + MongoCollection collection = mongoClient.getDatabase("mil") .getCollection("paymentTransactions", PaymentTransactionEntity.class) .withCodecRegistry(pojoCodecRegistry); @@ -349,7 +393,7 @@ private void checkDatabaseData(String transactionId, String paymentDate, Payment logger.info("Found transaction on DB: {}", paymentTransactionEntity.paymentTransaction); - PaymentTransaction paymentTransaction = paymentTransactionEntity.paymentTransaction; + paymentTransaction = paymentTransactionEntity.paymentTransaction; Assertions.assertEquals(transactionId, paymentTransaction.getTransactionId()); Assertions.assertEquals(transactionStatus.name(), paymentTransaction.getStatus()); @@ -362,6 +406,8 @@ private void checkDatabaseData(String transactionId, String paymentDate, Payment validateNotices(paymentList, paymentTransaction.getNotices()); } + + return paymentTransaction; } private void validateNotices(List nodePayments, List returnedNotices) { diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/PreClosePaymentResourceTestIT.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/PreClosePaymentResourceTestIT.java index 37cf302..8b85c05 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/PreClosePaymentResourceTestIT.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/PreClosePaymentResourceTestIT.java @@ -25,9 +25,12 @@ import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionEntity; import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionStatus; import it.pagopa.swclient.mil.paymentnotice.resource.PaymentResource; +import it.pagopa.swclient.mil.paymentnotice.util.KafkaUtils; import it.pagopa.swclient.mil.paymentnotice.util.PaymentTestData; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; import org.bson.codecs.configuration.CodecRegistries; import org.bson.codecs.configuration.CodecRegistry; import org.bson.codecs.pojo.PojoCodecProvider; @@ -47,6 +50,8 @@ import javax.net.ssl.TrustManager; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Optional; @@ -71,6 +76,8 @@ class PreClosePaymentResourceTestIT implements DevServicesContext.ContextAware { CodecRegistry pojoCodecRegistry; + KafkaConsumer paymentTransactionConsumer; + List paymentTokens; List notices; @@ -132,7 +139,7 @@ void createTestObjects() { // store existing transaction on DB existingTransactionId = RandomStringUtils.random(32, true, true); PaymentTransactionEntity existingTransactionEntity = - PaymentTestData.getPaymentTransaction(existingTransactionId, PaymentTransactionStatus.CLOSED, validMilHeaders, 1); + PaymentTestData.getPaymentTransaction(existingTransactionId, PaymentTransactionStatus.CLOSED, validMilHeaders, 1, null); MongoCollection collection = mongoClient.getDatabase("mil") .getCollection("paymentTransactions", PaymentTransactionEntity.class) @@ -140,8 +147,12 @@ void createTestObjects() { collection.insertMany(List.of(existingTransactionEntity)); + paymentTransactionConsumer = KafkaUtils.getKafkaConsumer(devServicesContext, PaymentTransaction.class); + } + + @AfterAll void destroyTestObjects() { @@ -156,9 +167,17 @@ void destroyTestObjects() { } catch (Exception e){ logger.error("Error while destroying Jedis pool", e); } + + try { + paymentTransactionConsumer.unsubscribe(); + paymentTransactionConsumer.close(); + } catch (Exception e){ + logger.error("Error while closing kafka consumer", e); + } } - PreCloseRequest getPreCloseRequest(String transactionId, PaymentTransactionOutcome outcome, List tokens, long totalAmount) { + PreCloseRequest getPreCloseRequest(String transactionId, PaymentTransactionOutcome outcome, List tokens, + long totalAmount, boolean hasPreset) { PreCloseRequest preCloseRequest = new PreCloseRequest(); preCloseRequest.setOutcome(outcome.name()); @@ -169,6 +188,8 @@ PreCloseRequest getPreCloseRequest(String transactionId, PaymentTransactionOutco preCloseRequest.setFee(100L); } + if (hasPreset) preCloseRequest.setPreset(PaymentTestData.getPreset()); + return preCloseRequest; } @@ -181,7 +202,7 @@ void testPreClose_201() { .contentType(ContentType.JSON) .headers(validMilHeaders) .and() - .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.PRE_CLOSE, paymentTokens, totalAmount)) + .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.PRE_CLOSE, paymentTokens, totalAmount, true)) .when() .post("/") .then() @@ -196,11 +217,24 @@ void testPreClose_201() { response.getHeader("Location").endsWith("/" + transactionId)); // check transaction written on DB - checkDatabaseData(transactionId); + PaymentTransaction dbPaymentTransaction = checkDatabaseData(transactionId); + + // check transaction sent to topic + Instant start = Instant.now(); + ConsumerRecords records = paymentTransactionConsumer.poll(Duration.ofSeconds(10)); + paymentTransactionConsumer.commitSync(); + logger.info("Finished polling in {} seconds, found {} records", Duration.between(start, Instant.now()), records.count()); + Assertions.assertEquals(1, records.count()); + + PaymentTransaction topicPaymentTransaction = records.iterator().next().value(); + Assertions.assertEquals(dbPaymentTransaction.getTransactionId(), topicPaymentTransaction.getTransactionId()); + Assertions.assertEquals(dbPaymentTransaction.getStatus(), topicPaymentTransaction.getStatus()); + Assertions.assertEquals(dbPaymentTransaction.getPreset().getPresetId(), topicPaymentTransaction.getPreset().getPresetId()); + Assertions.assertEquals(dbPaymentTransaction.getPreset().getSubscriberId(), topicPaymentTransaction.getPreset().getSubscriberId()); } - private void checkDatabaseData(String transactionId) { + private PaymentTransaction checkDatabaseData(String transactionId) { MongoCollection collection = mongoClient.getDatabase("mil") .getCollection("paymentTransactions", PaymentTransactionEntity.class) @@ -209,13 +243,15 @@ private void checkDatabaseData(String transactionId) { Bson filter = Filters.in("_id", transactionId); FindIterable documents = collection.find(filter); + PaymentTransaction paymentTransaction; + try (MongoCursor iterator = documents.iterator()) { Assertions.assertTrue(iterator.hasNext()); PaymentTransactionEntity paymentTransactionEntity = iterator.next(); logger.info("Found transaction on DB: {}", paymentTransactionEntity.paymentTransaction); - PaymentTransaction paymentTransaction = paymentTransactionEntity.paymentTransaction; + paymentTransaction = paymentTransactionEntity.paymentTransaction; Assertions.assertEquals(transactionId, paymentTransaction.getTransactionId()); Assertions.assertEquals(validMilHeaders.get("AcquirerId"), paymentTransaction.getAcquirerId()); @@ -235,6 +271,8 @@ private void checkDatabaseData(String transactionId) { Assertions.assertNull(paymentTransaction.getCallbackTimestamp()); } + + return paymentTransaction; } private void validateNotices(List cachedNotices, List returnedNotices) { @@ -261,7 +299,7 @@ void testPreClose_400_amountNotMatch() { .contentType(ContentType.JSON) .headers(PaymentTestData.getMilHeaders(true, true)) .and() - .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.PRE_CLOSE, paymentTokens, totalAmount-10)) + .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.PRE_CLOSE, paymentTokens, totalAmount-10, false)) .when() .post("/") .then() @@ -283,7 +321,7 @@ void testPreClose_400_noticeNotFound() { .contentType(ContentType.JSON) .headers(PaymentTestData.getMilHeaders(true, true)) .and() - .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.PRE_CLOSE, List.of(transactionId), totalAmount)) + .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.PRE_CLOSE, List.of(transactionId), totalAmount, false)) .when() .post("/") .then() @@ -303,7 +341,7 @@ void testPreClose_409_transactionAlreadyExists() { .contentType(ContentType.JSON) .headers(PaymentTestData.getMilHeaders(true, true)) .and() - .body(getPreCloseRequest(existingTransactionId, PaymentTransactionOutcome.PRE_CLOSE, paymentTokens, totalAmount)) + .body(getPreCloseRequest(existingTransactionId, PaymentTransactionOutcome.PRE_CLOSE, paymentTokens, totalAmount, false)) .when() .post("/") .then() @@ -325,7 +363,7 @@ void testAbort_201() { .contentType(ContentType.JSON) .headers(PaymentTestData.getMilHeaders(true, true)) .and() - .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.ABORT, paymentTokens, totalAmount)) + .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.ABORT, paymentTokens, totalAmount, false)) .when() .post("/") .then() @@ -348,7 +386,7 @@ void testAbort_201_noticeNotFound() { .contentType(ContentType.JSON) .headers(PaymentTestData.getMilHeaders(true, true)) .and() - .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.ABORT, paymentTokens, totalAmount)) + .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.ABORT, paymentTokens, totalAmount, false)) .when() .post("/") .then() @@ -368,7 +406,7 @@ void testAbort_500_unknownAcquirer() { .contentType(ContentType.JSON) .headers(PaymentTestData.getMilHeaders(true, false)) .and() - .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.ABORT, paymentTokens, totalAmount)) + .body(getPreCloseRequest(transactionId, PaymentTransactionOutcome.ABORT, paymentTokens, totalAmount, false)) .when() .post("/") .then() diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/KafkaTestResource.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/KafkaTestResource.java new file mode 100644 index 0000000..a786ae5 --- /dev/null +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/KafkaTestResource.java @@ -0,0 +1,148 @@ +package it.pagopa.swclient.mil.paymentnotice.it.resource; + +import com.google.common.collect.ImmutableMap; +import io.quarkus.test.common.DevServicesContext; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.FixedHostPortGenericContainer; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.Transferable; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; + +public class KafkaTestResource implements QuarkusTestResourceLifecycleManager, DevServicesContext.ContextAware { + + private static final Logger logger = LoggerFactory.getLogger(KafkaTestResource.class); + + private static final String KAFKA_NETWORK_ALIAS = "kafka"; + + private GenericContainer kafkaContainer; + + private DevServicesContext devServicesContext; + + @Override + public void setIntegrationTestContext(DevServicesContext devServicesContext){ + this.devServicesContext = devServicesContext; + } + + @Override + public Map start() { + + try { + logger.info("Starting Kafka container..."); + + Map environmentVariables = new HashMap<>(); + + environmentVariables.put("BITNAMI_DEBUG","true"); + environmentVariables.put("ALLOW_PLAINTEXT_LISTENER", "yes"); + environmentVariables.put("KAFKA_ENABLE_KRAFT", "yes"); + environmentVariables.put("KAFKA_CFG_PROCESS_ROLES", "broker,controller"); + environmentVariables.put("KAFKA_CFG_CONTROLLER_LISTENER_NAMES", "CONTROLLER"); + environmentVariables.put("KAFKA_BROKER_ID", "1"); + environmentVariables.put("KAFKA_CFG_CONTROLLER_QUORUM_VOTERS","1@127.0.0.1:9094"); + environmentVariables.put("KAFKA_CFG_NODE_ID", "1"); + environmentVariables.put("KAFKA_CFG_LISTENERS", "INTERNAL://:9093,CLIENT://:9092,CONTROLLER://:9094,EXTERNAL://:29092"); + environmentVariables.put("KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP", "INTERNAL:SASL_PLAINTEXT,CLIENT:SASL_PLAINTEXT,CONTROLLER:SASL_PLAINTEXT,EXTERNAL:SASL_PLAINTEXT"); + environmentVariables.put("KAFKA_CFG_ADVERTISED_LISTENERS", "INTERNAL://kafka:9093,CLIENT://kafka:9092,EXTERNAL://localhost:29092"); + environmentVariables.put("KAFKA_CFG_INTER_BROKER_LISTENER_NAME", "INTERNAL"); + environmentVariables.put("KAFKA_CFG_SASL_ENABLED_MECHANISMS", "PLAIN"); + environmentVariables.put("KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL", "PLAIN"); + environmentVariables.put("KAFKA_CFG_SASL_MECHANISM_CONTROLLER_PROTOCOL", "PLAIN"); + environmentVariables.put("KAFKA_CFG_NUM_PARTITIONS", "1"); + + kafkaContainer = new FixedHostPortGenericContainer<>("bitnami/kafka:3.4.0") + .withFixedExposedPort(29092,29092) + .withNetwork(getNetwork()) + .withNetworkAliases(KAFKA_NETWORK_ALIAS) + .withEnv(environmentVariables) + .waitingFor(Wait.forLogMessage(".*Kafka Server started.*", 1)); + + //kafkaContainer.withLogConsumer(new Slf4jLogConsumer(logger, true)); + + kafkaContainer.start(); + + logger.info("kafkaContainer.isRunning(): {}", kafkaContainer.isRunning()); + + String topic = RandomStringUtils.random(8, 0, 0, true, true, null, new SecureRandom()).toLowerCase(); + try { + String config = "sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username=\"user\" password=\"bitnami\";\n"; + config = config + "security.protocol=SASL_PLAINTEXT\n"; + config = config + "sasl.mechanism=PLAIN"; + kafkaContainer.copyFileToContainer(Transferable.of(config, 511), "tmp/config.properties"); + + logger.info(kafkaContainer.execInContainer("/opt/bitnami/kafka/bin/kafka-topics.sh", + "--create", + "--bootstrap-server", "127.0.0.1:9092", + "--replication-factor", "1", + "--partitions", "1", + "--topic", topic, + "--command-config", "tmp/config.properties").toString()); + } + catch (UnsupportedOperationException | IOException | InterruptedException e) { + logger.error("Could not create topic", e); + } + + devServicesContext.devServicesProperties().put("test.kafka.bootstrap-server", "localhost:29092"); + devServicesContext.devServicesProperties().put("test.kafka.security-protocol", "SASL_PLAINTEXT"); + devServicesContext.devServicesProperties().put("test.kafka.sasl-mechanism", "PLAIN"); + devServicesContext.devServicesProperties().put("test.kafka.sasl-jaas-config", "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"user\" password=\"bitnami\";"); + + devServicesContext.devServicesProperties().put("test.kafka.topic", topic); + + // Pass the configuration to the application under test + return ImmutableMap.of( + "kafka-bootstrap-server", KAFKA_NETWORK_ALIAS + ":" + 9093, + "kafka-security-protocol", "SASL_PLAINTEXT", + "kafka-sasl-mechanism", "PLAIN", + "kafka-sasl-jaas-config", "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"user\" password=\"bitnami\";", + "kafka-topic", topic + ); + } + catch (Exception e) { + logger.error("Error while starting kafka", e); + throw e; + } + } + + // create a "fake" network using the same id as the one that will be used by Quarkus + // using the network is the only way to make the withNetworkAliases work + private Network getNetwork() { + logger.info("devServicesContext.containerNetworkId() -> " + devServicesContext.containerNetworkId()); + return new Network() { + @Override + public String getId() { + return devServicesContext.containerNetworkId().orElse(null); + } + + @Override + public void close() { + } + + @Override + public Statement apply(Statement statement, Description description) { + return null; + } + }; + } + + @Override + public void stop() { + if (null != kafkaContainer) { + logger.info("Stopping kafka container..."); + kafkaContainer.stop(); + logger.info("Kafka container stopped"); + } + + } + +} diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/MongoTestResource.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/MongoTestResource.java index a2a26f6..8a4b968 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/MongoTestResource.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/MongoTestResource.java @@ -43,7 +43,7 @@ public Map start() { //.withNetworkMode(devServicesContext.containerNetworkId().get()) .waitingFor(Wait.forListeningPort()); - mongoContainer.withLogConsumer(new Slf4jLogConsumer(logger)); + //mongoContainer.withLogConsumer(new Slf4jLogConsumer(logger, true)); mongoContainer.withFileSystemBind("./src/test/resources/it/mongo", "/home/mongo"); //mongoContainer.setCommand("--verbose"); @@ -52,13 +52,13 @@ public Map start() { final Integer exposedPort = mongoContainer.getMappedPort(27017); devServicesContext.devServicesProperties().put("test.mongo.exposed-port", exposedPort.toString()); - try { - ExecResult result = mongoContainer.execInContainer("mongosh", "<", "/home/mongo/mongoInit.js"); - logger.info("Init script result {}", result); - } - catch (Exception e) { - logger.error("Error while importing data into DB", e); - } +// try { +// ExecResult result = mongoContainer.execInContainer("mongosh", "<", "/home/mongo/mongoInit.js"); +// logger.info("Init script result {}", result); +// } +// catch (Exception e) { +// logger.error("Error while importing data into DB", e); +// } // Pass the configuration to the application under test return ImmutableMap.of( diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/RedisTestResource.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/RedisTestResource.java index 74585cb..7d7bc8f 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/RedisTestResource.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/RedisTestResource.java @@ -77,7 +77,7 @@ public Map start() { //.withNetworkMode(devServicesContext.containerNetworkId().get()) .waitingFor(Wait.forListeningPort()); - redisContainer.withLogConsumer(new Slf4jLogConsumer(logger)); + //redisContainer.withLogConsumer(new Slf4jLogConsumer(logger, true)); final String password = "wCou42NVkv7H8"; diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/WiremockTestResource.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/WiremockTestResource.java index 8be0f87..4b7273d 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/WiremockTestResource.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/it/resource/WiremockTestResource.java @@ -40,7 +40,7 @@ public Map start() { //.withNetworkMode(devServicesContext.containerNetworkId().get()) .waitingFor(Wait.forListeningPort()); - wiremockContainer.withLogConsumer(new Slf4jLogConsumer(logger)); + //wiremockContainer.withLogConsumer(new Slf4jLogConsumer(logger, true)); wiremockContainer.setCommand("--verbose --local-response-templating"); wiremockContainer.withFileSystemBind("./src/test/resources/it/wiremock", "/home/wiremock"); diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/resource/KafkaInMemoryTestResource.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/resource/KafkaInMemoryTestResource.java new file mode 100644 index 0000000..577afe3 --- /dev/null +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/resource/KafkaInMemoryTestResource.java @@ -0,0 +1,27 @@ +package it.pagopa.swclient.mil.paymentnotice.resource; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import io.smallrye.reactive.messaging.memory.InMemoryConnector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +public class KafkaInMemoryTestResource implements QuarkusTestResourceLifecycleManager { + + static final Logger logger = LoggerFactory.getLogger(KafkaInMemoryTestResource.class); + + @Override + public Map start() { + logger.info("Starting in memory Kafka connector"); + Map props1 = InMemoryConnector.switchOutgoingChannelsToInMemory("presets"); + return new HashMap<>(props1); + } + + @Override + public void stop() { + logger.info("Stopping in memory Kafka connector"); + InMemoryConnector.clear(); + } +} diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/resource/UnitTestProfile.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/resource/UnitTestProfile.java new file mode 100644 index 0000000..057314f --- /dev/null +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/resource/UnitTestProfile.java @@ -0,0 +1,30 @@ +package it.pagopa.swclient.mil.paymentnotice.resource; + +import com.google.common.collect.ImmutableList; +import io.quarkus.test.junit.QuarkusTestProfile; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class UnitTestProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + + return new HashMap<>(); + } + + @Override + public List testResources() { + return ImmutableList.of( + new TestResourceEntry(KafkaInMemoryTestResource.class) + ); + } + + @Override + public boolean disableGlobalTestResources() { + return true; + } + +} diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/util/KafkaUtils.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/util/KafkaUtils.java new file mode 100644 index 0000000..b8916b8 --- /dev/null +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/util/KafkaUtils.java @@ -0,0 +1,83 @@ +package it.pagopa.swclient.mil.paymentnotice.util; + +import io.quarkus.kafka.client.serialization.ObjectMapperDeserializer; +import io.quarkus.test.common.DevServicesContext; +import org.apache.kafka.clients.consumer.ConsumerRebalanceListener; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.common.TopicPartition; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +public class KafkaUtils { + + static final Logger logger = LoggerFactory.getLogger(KafkaUtils.class); + + public static KafkaConsumer getKafkaConsumer(DevServicesContext devServicesContext, Class clazz) { + // initialize kafka consumer + Properties kafkaConfig = new Properties(); + + Map testProperties = devServicesContext.devServicesProperties(); + kafkaConfig.put("bootstrap.servers", testProperties.get("test.kafka.bootstrap-server")); + kafkaConfig.put("security.protocol", testProperties.get("test.kafka.security-protocol")); + kafkaConfig.put("sasl.mechanism", testProperties.get("test.kafka.sasl-mechanism")); + kafkaConfig.put("sasl.jaas.config", testProperties.get("test.kafka.sasl-jaas-config")); + + kafkaConfig.put("group.id", "it-consumer"); + kafkaConfig.put("client.id", "it-consumer"); + kafkaConfig.put("enable.auto.commit", "true"); + kafkaConfig.put("auto.offset.reset", "earliest"); + kafkaConfig.put("auto.commit.interval.ms", "1000"); + + kafkaConfig.put("linger.ms", 1); + + KafkaConsumer kafkaConsumer = new KafkaConsumer<>(kafkaConfig, new StringDeserializer(), new ObjectMapperDeserializer<>(clazz)); + + String topic = testProperties.get("test.kafka.topic"); + logger.info("topic: {}", kafkaConsumer.listTopics().get(topic)); + + kafkaConsumer.subscribe(List.of(topic), new ConsumerRebalanceListener() { + @Override + public void onPartitionsRevoked(Collection partitions) { + logger.info("revoked partition {}", partitions); + for (TopicPartition topicPartition : partitions) { + logger.debug("topic [{}] partition [{}] beginning offset [{}] end offset [{}] committed [{}] position [{}] current lag [{}]", + topicPartition.topic(), + topicPartition.partition(), + kafkaConsumer.beginningOffsets(List.of(topicPartition)), + kafkaConsumer.endOffsets(List.of(topicPartition)), + kafkaConsumer.committed(Set.of(topicPartition), Duration.of(10, ChronoUnit.SECONDS)), + kafkaConsumer.position(topicPartition, Duration.of(10, ChronoUnit.SECONDS)), + kafkaConsumer.currentLag(topicPartition)); + } + } + + @Override + public void onPartitionsAssigned(Collection partitions) { + logger.info("assigned partition {}", partitions); + for (TopicPartition topicPartition : partitions) { + logger.debug("topic [{}] partition [{}] beginning offset [{}] end offset [{}] committed [{}] position [{}] current lag [{}]", + topicPartition.topic(), + topicPartition.partition(), + kafkaConsumer.beginningOffsets(List.of(topicPartition)), + kafkaConsumer.endOffsets(List.of(topicPartition)), + kafkaConsumer.committed(Set.of(topicPartition), Duration.of(10, ChronoUnit.SECONDS)), + kafkaConsumer.position(topicPartition, Duration.of(10, ChronoUnit.SECONDS)), + kafkaConsumer.currentLag(topicPartition)); + + //paymentTransactionConsumer.seek(topicPartition, 0); + } + } + }); + + return kafkaConsumer; + } +} diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/util/PaymentTestData.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/util/PaymentTestData.java index 5fbab00..87bd5b1 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/util/PaymentTestData.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/util/PaymentTestData.java @@ -5,6 +5,7 @@ import it.pagopa.swclient.mil.paymentnotice.bean.PaymentMethod; import it.pagopa.swclient.mil.paymentnotice.bean.PaymentTransactionOutcome; import it.pagopa.swclient.mil.paymentnotice.bean.PreCloseRequest; +import it.pagopa.swclient.mil.paymentnotice.bean.Preset; import it.pagopa.swclient.mil.paymentnotice.client.bean.AcquirerConfiguration; import it.pagopa.swclient.mil.paymentnotice.client.bean.PspConfiguration; import it.pagopa.swclient.mil.paymentnotice.dao.Notice; @@ -13,6 +14,7 @@ import it.pagopa.swclient.mil.paymentnotice.dao.PaymentTransactionStatus; import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import java.security.SecureRandom; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -59,7 +61,7 @@ public static ActivatePaymentNoticeRequest getActivatePaymentRequest() { return activatePaymentNoticeRequest; } - public static PreCloseRequest getPreCloseRequest(boolean isPreClose, int tokens) { + public static PreCloseRequest getPreCloseRequest(boolean isPreClose, int tokens, boolean isPreset) { PreCloseRequest preCloseRequest = new PreCloseRequest(); if (isPreClose) { preCloseRequest.setOutcome(PaymentTransactionOutcome.PRE_CLOSE.name()); @@ -78,6 +80,11 @@ public static PreCloseRequest getPreCloseRequest(boolean isPreClose, int tokens) } preCloseRequest.setPaymentTokens(paymentTokens); + // preset is optional + if (isPreset) { + preCloseRequest.setPreset(getPreset()); + } + return preCloseRequest; } @@ -102,10 +109,25 @@ public static Notice getNotice(String paymentToken) { return notice; } + public static Preset getPreset() { + String presetId = UUID.randomUUID().toString(); + String subscriberId = RandomStringUtils.random(6, 0, 0, true, true, null, new SecureRandom()).toLowerCase(); + return getPreset(presetId, subscriberId); + } + + public static Preset getPreset(String presetId, String subscriberId) { + Preset preset = new Preset(); + preset.setPresetId(presetId); + preset.setPaTaxCode(PA_TAX_CODE); + preset.setSubscriberId(subscriberId); + return preset; + } + public static PaymentTransactionEntity getPaymentTransaction(String transactionId, PaymentTransactionStatus status, Map headers, - int tokens) { + int tokens, + Preset preset) { if (status == PaymentTransactionStatus.ABORTED) throw new IllegalArgumentException(); @@ -150,6 +172,8 @@ public static PaymentTransactionEntity getPaymentTransaction(String transactionI } } + paymentTransaction.setPreset(preset); + var paymentTransactionEntity = new PaymentTransactionEntity(); paymentTransactionEntity.transactionId = transactionId; paymentTransactionEntity.paymentTransaction = paymentTransaction; diff --git a/src/test/java/it/pagopa/swclient/mil/paymentnotice/util/TestUtils.java b/src/test/java/it/pagopa/swclient/mil/paymentnotice/util/TestUtils.java index 661b960..ea0f66f 100644 --- a/src/test/java/it/pagopa/swclient/mil/paymentnotice/util/TestUtils.java +++ b/src/test/java/it/pagopa/swclient/mil/paymentnotice/util/TestUtils.java @@ -1,5 +1,19 @@ package it.pagopa.swclient.mil.paymentnotice.util; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.mongodb.MongoWriteException; +import com.mongodb.ServerAddress; +import com.mongodb.WriteError; +import it.pagopa.swclient.mil.paymentnotice.ErrorCode; +import jakarta.ws.rs.core.Response; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.jboss.resteasy.reactive.ClientWebApplicationException; +import org.junit.jupiter.params.provider.Arguments; + +import javax.net.ssl.SSLHandshakeException; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.io.IOException; @@ -12,22 +26,6 @@ import java.util.concurrent.TimeoutException; import java.util.stream.Stream; -import javax.net.ssl.SSLHandshakeException; - -import it.pagopa.swclient.mil.paymentnotice.ErrorCode; -import jakarta.ws.rs.core.Response; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.mongodb.MongoWriteException; -import com.mongodb.ServerAddress; -import com.mongodb.WriteError; -import org.bson.BsonDocument; -import org.bson.BsonString; -import org.jboss.resteasy.reactive.ClientWebApplicationException; -import org.junit.jupiter.params.provider.Arguments; - public class TestUtils { private TestUtils() {} @@ -117,20 +115,33 @@ public static Stream provideActivateRequestValidationErrorCases() { public static Stream providePreCloseRequestValidationErrorCases() { return Stream.of( - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "outcome", null), ErrorCode.ERROR_OUTCOME_MUST_NOT_BE_NULL), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "outcome", "OK"), ErrorCode.ERROR_OUTCOME_MUST_MATCH_MATCH_REGEXP), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "paymentTokens", null), ErrorCode.ERROR_PAYMENT_TOKEN_LIST_MUST_NOT_BE_NULL), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "paymentTokens", List.of("100","101","102","103","104","105")), ErrorCode.ERROR_PAYMENT_TOKEN_LIST_MUST_HAVE_AT_MOST), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "paymentTokens", List.of("123456789012345678901234567890123456")), ErrorCode.ERROR_PAYMENT_TOKEN_MATCH_MATCH_REGEXP), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "transactionId", null), ErrorCode.ERROR_TRANSACTION_ID_MUST_NOT_BE_NULL), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "transactionId", "abc"), ErrorCode.ERROR_TRANSACTION_ID_MUST_MATCH_REGEXP), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "totalAmount", null), ErrorCode.ERROR_TOTAL_AMOUNT_MUST_NOT_BE_NULL), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "totalAmount", 0L), ErrorCode.ERROR_TOTAL_AMOUNT_MUST_BE_GREATER_THAN), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "totalAmount", 199999999999L), ErrorCode.ERROR_TOTAL_AMOUNT_MUST_BE_LESS_THAN), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "totalAmount", 12345L), ErrorCode.ERROR_TOTAL_AMOUNT_MUST_MATCH_TOTAL_CACHED_VALUE), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "fee", null), ErrorCode.ERROR_FEE_MUST_NOT_BE_NULL), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "fee", 0L), ErrorCode.ERROR_FEE_MUST_BE_GREATER_THAN), - Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1), "fee", 199999999999L), ErrorCode.ERROR_FEE_MUST_BE_LESS_THAN) + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "outcome", null), ErrorCode.ERROR_OUTCOME_MUST_NOT_BE_NULL), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "outcome", "OK"), ErrorCode.ERROR_OUTCOME_MUST_MATCH_MATCH_REGEXP), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "paymentTokens", null), ErrorCode.ERROR_PAYMENT_TOKEN_LIST_MUST_NOT_BE_NULL), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "paymentTokens", List.of("100","101","102","103","104","105")), ErrorCode.ERROR_PAYMENT_TOKEN_LIST_MUST_HAVE_AT_MOST), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "paymentTokens", List.of("123456789012345678901234567890123456")), ErrorCode.ERROR_PAYMENT_TOKEN_MATCH_MATCH_REGEXP), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "transactionId", null), ErrorCode.ERROR_TRANSACTION_ID_MUST_NOT_BE_NULL), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "transactionId", "abc"), ErrorCode.ERROR_TRANSACTION_ID_MUST_MATCH_REGEXP), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "totalAmount", null), ErrorCode.ERROR_TOTAL_AMOUNT_MUST_NOT_BE_NULL), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "totalAmount", 0L), ErrorCode.ERROR_TOTAL_AMOUNT_MUST_BE_GREATER_THAN), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "totalAmount", 199999999999L), ErrorCode.ERROR_TOTAL_AMOUNT_MUST_BE_LESS_THAN), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "totalAmount", 12345L), ErrorCode.ERROR_TOTAL_AMOUNT_MUST_MATCH_TOTAL_CACHED_VALUE), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "fee", null), ErrorCode.ERROR_FEE_MUST_NOT_BE_NULL), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "fee", 0L), ErrorCode.ERROR_FEE_MUST_BE_GREATER_THAN), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), "fee", 199999999999L), ErrorCode.ERROR_FEE_MUST_BE_LESS_THAN), + // preset test data + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), + "preset", setAndGet(PaymentTestData.getPreset(), "paTaxCode", null)), ErrorCode.PA_TAX_CODE_MUST_NOT_BE_NULL), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), + "preset", setAndGet(PaymentTestData.getPreset(), "paTaxCode", "abcde")), ErrorCode.PA_TAX_CODE_MUST_MATCH_REGEXP), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), + "preset", setAndGet(PaymentTestData.getPreset(), "subscriberId", null)), ErrorCode.SUBSCRIBER_ID_CODE_MUST_NOT_BE_NULL), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), + "preset", setAndGet(PaymentTestData.getPreset(), "subscriberId", "abcde")), ErrorCode.SUBSCRIBER_ID_MUST_MATCH_REGEXP), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), + "preset", setAndGet(PaymentTestData.getPreset(), "presetId", null)), ErrorCode.PRESET_ID_MUST_NOT_BE_NULL), + Arguments.of(setAndGet(PaymentTestData.getPreCloseRequest(true, 1, false), + "preset", setAndGet(PaymentTestData.getPreset(), "presetId", "abcde")), ErrorCode.PRESET_ID_MUST_MATCH_REGEXP) ); } @@ -201,5 +212,4 @@ private static T setAndGet(T object, String propertyName, V propertyValue return object; } - }