diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index b8f7080..e232dd1 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -45,77 +45,76 @@ jobs: cpd_exclusions: "**/models/**,**/clients/model/**,**/entity/*" coverage_report_path: "./target/jacoco-report/jacoco.xml" -# smoke-test: -# name: Smoke Test -# runs-on: ubuntu-latest -# environment: -# name: dev -# steps: -# - name: Checkout -# id: checkout -# uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 -# -# - name: Login -# id: login -# # from https://github.com/Azure/login/commits/master -# uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 -# with: -# client-id: ${{ secrets.CLIENT_ID }} -# tenant-id: ${{ secrets.TENANT_ID }} -# subscription-id: ${{ secrets.SUBSCRIPTION_ID }} -# -# - name: Run Service on Docker -# shell: bash -# id: run_service_docker -# run: | -# cd ./docker -# chmod +x ./run_docker.sh -# ./run_docker.sh local -# -# - name: Run Integration Tests -# shell: bash -# id: run_integration_test -# run: | -# export GPD_PULL_API_SUBSCRIPTION_KEY=${{ secrets.PULL_SUBKEY }} -# export CANARY=${{ inputs.canary }} -# export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} -# export SUBKEY=${{ secrets.SUBKEY }} -# -# cd ./integration-test -# chmod +x ./run_integration_test.sh -# ./run_integration_test.sh local + smoke-test: + name: Smoke Test + runs-on: ubuntu-latest + environment: + name: dev + steps: + - name: Checkout + id: checkout + uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 + + - name: Login + id: login + # from https://github.com/Azure/login/commits/master + uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 + with: + client-id: ${{ secrets.CLIENT_ID }} + tenant-id: ${{ secrets.TENANT_ID }} + subscription-id: ${{ secrets.SUBSCRIPTION_ID }} + + - name: Run Service on Docker + shell: bash + id: run_service_docker + run: | + cd ./docker + chmod +x ./run_docker.sh + ./run_docker.sh local + + - name: Run Integration Tests + shell: bash + id: run_integration_test + run: | + export CANARY=${{ inputs.canary }} + export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} + export SUBKEY=${{ secrets.SUBKEY }} + cd ./integration-test + chmod +x ./run_integration_test.sh + ./run_integration_test.sh local + + + delete_github_deployments: + runs-on: ubuntu-latest + needs: smoke-test + if: ${{ always() }} + steps: + - name: Delete Previous deployments + uses: actions/github-script@v6 + env: + SHA_HEAD: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.sha) || github.sha}} + with: + script: | + const { SHA_HEAD } = process.env -# delete_github_deployments: -# runs-on: ubuntu-latest -# needs: smoke-test -# if: ${{ always() }} -# steps: -# - name: Delete Previous deployments -# uses: actions/github-script@v6 -# env: -# SHA_HEAD: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.sha) || github.sha}} -# with: -# script: | -# const { SHA_HEAD } = process.env -# -# const deployments = await github.rest.repos.listDeployments({ -# owner: context.repo.owner, -# repo: context.repo.repo, -# sha: SHA_HEAD -# }); -# await Promise.all( -# deployments.data.map(async (deployment) => { -# await github.rest.repos.createDeploymentStatus({ -# owner: context.repo.owner, -# repo: context.repo.repo, -# deployment_id: deployment.id, -# state: 'inactive' -# }); -# return github.rest.repos.deleteDeployment({ -# owner: context.repo.owner, -# repo: context.repo.repo, -# deployment_id: deployment.id -# }); -# }) -# ); + const deployments = await github.rest.repos.listDeployments({ + owner: context.repo.owner, + repo: context.repo.repo, + sha: SHA_HEAD + }); + await Promise.all( + deployments.data.map(async (deployment) => { + await github.rest.repos.createDeploymentStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + deployment_id: deployment.id, + state: 'inactive' + }); + return github.rest.repos.deleteDeployment({ + owner: context.repo.owner, + repo: context.repo.repo, + deployment_id: deployment.id + }); + }) + ); diff --git a/docker/alertmanager/alertmanager.conf b/docker/alertmanager/alertmanager.conf deleted file mode 100644 index 13611f6..0000000 --- a/docker/alertmanager/alertmanager.conf +++ /dev/null @@ -1,12 +0,0 @@ -global: - resolve_timeout: 1m - pagerduty_url: 'https://events.pagerduty.com/v2/enqueue' - -route: - receiverEntity: 'pagerduty-notifications' - -receivers: -- name: 'pagerduty-notifications' - pagerduty_configs: - - service_key: 0c1cc665a594419b6d215e81f4e38f7 - send_resolved: true diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index f834154..7b132a5 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,113 +1,21 @@ version: '3.8' services: - elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:8.6.2 + app: + container_name: 'pagopa-payment-options-service' + image: ${image} + platform: linux/amd64 + build: + dockerfile: Dockerfile_integration_test + context: ../ + args: + QUARKUS_PROFILE: prod + APP_NAME: pagopa-payment-options-service + SLEEP_FOR_INTEGRATION: 180000 + env_file: + - ./.env ports: - - "9200:9200" - - "9300:9300" - environment: - ES_JAVA_OPTS: "-Xms512m -Xmx512m" - discovery.type: "single-node" - xpack.security.enabled: false - xpack.security.enrollment.enabled: false - networks: - - infra - - kibana: - image: docker.elastic.co/kibana/kibana:8.6.2 - ports: - - "5601:5601" - networks: - - infra - depends_on: - - elasticsearch - - alertmanager: - hostname: alertmanager - image: prom/alertmanager - volumes: - - ${PWD}/alertmanager/alertmanager.conf:/etc/alertmanager/alertmanager.conf - command: - - '--config.file=/etc/alertmanager/alertmanager.conf' - ports: - - "9093:9093" - networks: - - infra - - prometheus: - hostname: prometheus - image: prom/prometheus - volumes: - - ${PWD}/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - - ${PWD}/prometheus/alert_rules.yml:/etc/prometheus/alert_rules.yml - - prometheus_data:/prometheus - command: - - '--config.file=/etc/prometheus/prometheus.yml' - links: - - alertmanager:alertmanager - ports: - - "9090:9090" - networks: - - infra - - grafana: - hostname: grafana - image: grafana/grafana - volumes: - - ${PWD}/grafana/grafana_datasources.yml:/etc/grafana/provisioning/datasources/all.yaml - - ${PWD}/grafana/grafana_config.ini:/etc/grafana/config.ini - - grafana_data:/var/lib/grafana - ports: - - "3000:3000" - networks: - - infra - - jaeger-all-in-one: - image: jaegertracing/all-in-one:latest - ports: - - "16686:16686" - - "14268:14268" - - "14250:14250" - networks: - - infra - - otel-collector: - image: otel/opentelemetry-collector:latest - command: [ "--config=/etc/otel-collector-config.yaml" ] - volumes: - - ${PWD}/otel-collector/otel-collector-config.yaml:/etc/otel-collector-config.yaml:Z - ports: - - "13133:13133" # Health-check extension - - "4317:4317" # OTLP gRPC receiver - depends_on: - - jaeger-all-in-one - networks: - - infra - -# mongo: -# image: mongo -# restart: always -# environment: -# MONGO_INITDB_ROOT_USERNAME: root -# MONGO_INITDB_ROOT_PASSWORD: example -# ports: -# - 27017:27017 -# networks: -# - infra -# -# mongo-express: -# image: mongo-express -# restart: always -# ports: -# - 8085:8081 -# environment: -# ME_CONFIG_MONGODB_ADMINUSERNAME: root -# ME_CONFIG_MONGODB_ADMINPASSWORD: example -# ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/ -# networks: -# - infra - + - "8080:8080" volumes: prometheus_data: { } diff --git a/docker/grafana/grafana_config.ini b/docker/grafana/grafana_config.ini deleted file mode 100644 index 9c84f44..0000000 --- a/docker/grafana/grafana_config.ini +++ /dev/null @@ -1,5 +0,0 @@ -[paths] -provisioning = /etc/grafana/provisioning - -[server] -enable_gzip = true \ No newline at end of file diff --git a/docker/grafana/grafana_datasources.yml b/docker/grafana/grafana_datasources.yml deleted file mode 100644 index ba6949e..0000000 --- a/docker/grafana/grafana_datasources.yml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: 1 - -datasources: - - name: 'prometheus' - type: 'prometheus' - access: 'proxy' - url: 'http://prometheus:9090' \ No newline at end of file diff --git a/docker/otel-collector/otel-collector-config.yaml b/docker/otel-collector/otel-collector-config.yaml deleted file mode 100644 index c08ab06..0000000 --- a/docker/otel-collector/otel-collector-config.yaml +++ /dev/null @@ -1,33 +0,0 @@ -receivers: - otlp: - protocols: - grpc: - endpoint: otel-collector:4317 - -exporters: - jaeger: - endpoint: jaeger-all-in-one:14250 - tls: - insecure: true - otlphttp/elastic: - tls: - insecure: true - # Elastic APM server https endpoint without the "https://" prefix - endpoint: "https://kibana.dev.platform.pagopa.it:443/apm" - headers: - # Elastic APM Server secret token - Authorization: "Bearer 30K8q56M9r3jhKrIdhf968PO" - -processors: - batch: - -extensions: - health_check: - -service: - extensions: [ health_check ] - pipelines: - traces: - receivers: [ otlp ] - processors: [ batch ] - exporters: [ jaeger, otlphttp/elastic ] diff --git a/docker/prometheus/alert_rules.yml b/docker/prometheus/alert_rules.yml deleted file mode 100644 index 5d0c4fd..0000000 --- a/docker/prometheus/alert_rules.yml +++ /dev/null @@ -1,13 +0,0 @@ -groups: - - name: JVMMemory - rules: - - alert: JVMMemoryThresholdCrossed - # Condition for alerting - expr: jvm_memory_committed_bytes{region="heap"}/jvm_memory_max_bytes{region="heap"} > 0.8 - # Annotation - additional informational labels to store more information - annotations: - title: 'Instance has crossed 80% heap memory usage' - description: ' of job has crossed 80% heap memory usage' - # Labels - additional labels to be attached to the alert - labels: - severity: 'critical' \ No newline at end of file diff --git a/docker/prometheus/prometheus.yml b/docker/prometheus/prometheus.yml deleted file mode 100644 index 9521553..0000000 --- a/docker/prometheus/prometheus.yml +++ /dev/null @@ -1,29 +0,0 @@ -# global settings -global: - scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. - evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. - # scrape_timeout is set to the global default (10s). - -alerting: - alertmanagers: - - static_configs: - - targets: [ "alertmanager:9093" ] - -# Load rules once and periodically evaluate them according to the global 'evaluation_interval'. -rule_files: - - /etc/prometheus/alert_rules.yml - -# A scrape configuration containing exactly one endpoint to scrape: -# Here it's Prometheus itself. -scrape_configs: - # The job name is added as a label `job=` to any timeseries scraped from this config. - - job_name: 'ndp-metrics' - metrics_path: '/q/metrics' - scrape_interval: 5s - static_configs: - - targets: [ 'docker.for.mac.host.internal:8080' ] - #labels: - # application: ndp - # namespace: nodo - # service: ndp - # env: dev diff --git a/docker/run_docker.sh b/docker/run_docker.sh index f907c83..6b32fb4 100644 --- a/docker/run_docker.sh +++ b/docker/run_docker.sh @@ -1,7 +1,8 @@ -# sh ./run_docker.sh --skip-recreate +#!/bin/bash + +# sh ./run_docker.sh ENV=$1 -RECREATE=$2 if [ -z "$ENV" ] then @@ -9,48 +10,44 @@ then echo "No environment specified: local is used." fi +pip3 install yq if [ "$ENV" = "local" ]; then - containerRegistry="pagopadcommonacr.azurecr.io" image="service-local:latest" - echo "Running local image and dev dependencies" + ENV="dev" else - - if [ "$ENV" = "dev" ]; then - containerRegistry="pagopadcommonacr.azurecr.io" - echo "Running all dev images" - elif [ "$ENV" = "uat" ]; then - containerRegistry="pagopaucommonacr.azurecr.io" - echo "Running all uat images" - elif [ "$ENV" = "prod" ]; then - containerRegistry="pagopapcommonacr.azurecr.io" - echo "Running all prod images" - else - echo "Error with parameter: use " - exit 1 - fi - - pip3 install yq repository=$(yq -r '."microservice-chart".image.repository' ../helm/values-$ENV.yaml) image="${repository}:latest" fi +export image=${image} +FILE=.env +if test -f "$FILE"; then + rm .env +fi +config=$(yq -r '."microservice-chart".envConfig' ../helm/values-$ENV.yaml) +for line in $(echo $config | jq -r '. | to_entries[] | select(.key) | "\(.key)=\(.value)"'); do + echo $line >> .env +done -export containerRegistry=${containerRegistry} -export image=${image} +keyvault=$(yq -r '."microservice-chart".keyvault.name' ../helm/values-$ENV.yaml) +secret=$(yq -r '."microservice-chart".envSecret' ../helm/values-$ENV.yaml) +for line in $(echo $secret | jq -r '. | to_entries[] | select(.key) | "\(.key)=\(.value)"'); do + IFS='=' read -r -a array <<< "$line" + response=$(az keyvault secret show --vault-name $keyvault --name "${array[1]}") + value=$(echo $response | jq -r '.value') + echo "${array[0]}=$value" >> .env +done stack_name=$(cd .. && basename "$PWD") -if [ "$RECREATE" = "--skip-recreate" ]; then - docker compose -p "${stack_name}" up -d - else - docker compose -p "${stack_name}" up -d --remove-orphans --force-recreate --build -fi +docker compose -p "${stack_name}" up -d --remove-orphans --force-recreate --build + # waiting the containers printf 'Waiting for the service' attempt_counter=0 max_attempts=50 -until $(curl --output /dev/null --silent --head --fail http://localhost:8080/actuator/info); do +until [ "$(curl -s -w '%{http_code}' -o /dev/null "http://localhost:8080/q/health/live")" -eq 200 ]; do if [ ${attempt_counter} -eq ${max_attempts} ];then echo "Max attempts reached" exit 1 diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 0fa6656..d8f9345 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: pagopa-payment-options description: Microservice that handles services for payment options type: application -version: 0.69.0 -appVersion: 0.4.2 +version: 0.82.0 +appVersion: 0.4.2-13-PPANTT-131-feat-add-integration-tests dependencies: - name: microservice-chart version: 5.9.0 diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index 39288de..0bd27e3 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -4,7 +4,7 @@ microservice-chart: fullnameOverride: "payment-options" image: repository: ghcr.io/pagopa/pagopa-payment-options-service - tag: "0.4.2" + tag: "0.4.2-13-PPANTT-131-feat-add-integration-tests" pullPolicy: Always livenessProbe: httpGet: @@ -76,9 +76,11 @@ microservice-chart: APP_NAME: "payment-options-service" APP_ENVIRONMENT: "dev" WEBSITE_SITE_NAME: 'payment-options-service' # required to show cloud role name in application insights - APP_LOGGING_LEVEL: 'DEBUG' + APP_LOG_LEVEL: 'DEBUG' DEFAULT_LOGGING_LEVEL: 'INFO' - APICONFIG_CACHE_URL: 'https://api.dev.platform.pagopa.it/api-config-cache/o/v1' + APICONFIG_CACHE_URL: 'https://api.dev.platform.pagopa.it/api-config-cache/p/v1' + EC_APIM_FORWARDER_ENDPOINT: 'https://api.dev.platform.pagopa.it' + EC_APIM_PATH: '/pagopa-node-forwarder/api/v1' CACHE_EVT_HOST: 'pagopa-d-weu-core-evh-ns04.servicebus.windows.net:9093' CACHE_EVT_TOPIC: 'nodo-dei-pagamenti-cache' VERIFY_KO_EVT_HOST: 'pagopa-d-weu-core-evh-ns03.servicebus.windows.net:9093' diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index 70f3689..e2ea73f 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -4,7 +4,7 @@ microservice-chart: fullnameOverride: "payment-options" image: repository: ghcr.io/pagopa/pagopa-payment-options-service - tag: "0.4.2" + tag: "0.4.2-13-PPANTT-131-feat-add-integration-tests" pullPolicy: Always livenessProbe: httpGet: @@ -83,7 +83,9 @@ microservice-chart: WEBSITE_SITE_NAME: 'payment-options-service' # required to show cloud role name in application insights APP_LOGGING_LEVEL: 'DEBUG' DEFAULT_LOGGING_LEVEL: 'INFO' - APICONFIG_CACHE_URL: 'https://api.platform.pagopa.it/api-config-cache/o/v1' + APICONFIG_CACHE_URL: 'https://api.platform.pagopa.it/api-config-cache/p/v1' + EC_APIM_FORWARDER_ENDPOINT: 'https://api.platform.pagopa.it' + EC_APIM_PATH: '/pagopa-node-forwarder/api/v1' CACHE_EVT_HOST: 'pagopa-p-weu-core-evh-ns04.servicebus.windows.net:9093' CACHE_EVT_TOPIC: 'nodo-dei-pagamenti-cache' VERIFY_KO_EVT_HOST: 'pagopa-p-weu-core-evh-ns03.servicebus.windows.net:9093' diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index da49b28..8d5e6bc 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -4,7 +4,7 @@ microservice-chart: fullnameOverride: "payment-options" image: repository: ghcr.io/pagopa/pagopa-payment-options-service - tag: "0.4.2" + tag: "0.4.2-13-PPANTT-131-feat-add-integration-tests" pullPolicy: Always livenessProbe: httpGet: @@ -83,7 +83,9 @@ microservice-chart: WEBSITE_SITE_NAME: 'payment-options-service' # required to show cloud role name in application insights APP_LOGGING_LEVEL: 'DEBUG' DEFAULT_LOGGING_LEVEL: 'INFO' - APICONFIG_CACHE_URL: 'https://api.uat.platform.pagopa.it/api-config-cache/o/v1' + APICONFIG_CACHE_URL: 'https://api.uat.platform.pagopa.it/api-config-cache/p/v1' + EC_APIM_FORWARDER_ENDPOINT: 'https://api.uat.platform.pagopa.it' + EC_APIM_PATH: '/pagopa-node-forwarder/api/v1' CACHE_EVT_HOST: 'pagopa-u-weu-core-evh-ns04.servicebus.windows.net:9093' CACHE_EVT_TOPIC: 'nodo-dei-pagamenti-cache' VERIFY_KO_EVT_HOST: 'pagopa-u-weu-core-evh-ns03.servicebus.windows.net:9093' diff --git a/integration-test/README.md b/integration-test/README.md index 16d26a8..1e4aa6e 100644 --- a/integration-test/README.md +++ b/integration-test/README.md @@ -1 +1,22 @@ -Put here the integration tests +# Integration Tests + +πŸ‘€ Integration tests are in `integration-test/src/` folder. See there for more information. + +## How run on Docker 🐳 + +To run the integration tests on docker, you can run from this directory the script: + +``` shell +sh ./run_integration_test.sh +``` + +ℹ️ _Note_: for **PagoPa ACR** is **required** the login `az acr login -n ` + +If you use dev, uat or prod **you test the images on Azure ACR** + +--- +πŸ’» If you want to test your local branch, + +``` shell +sh ./run_integration_test.sh local SUBSCRIPTION-KEY +``` diff --git a/integration-test/run_integration_test.sh b/integration-test/run_integration_test.sh new file mode 100755 index 0000000..750b584 --- /dev/null +++ b/integration-test/run_integration_test.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# example: sh ./run_integration_test.sh +set -e + +# run integration tests +cd ./src || exit +yarn install +yarn test:"$1" \ No newline at end of file diff --git a/integration-test/src/README.md b/integration-test/src/README.md new file mode 100644 index 0000000..93d85f0 --- /dev/null +++ b/integration-test/src/README.md @@ -0,0 +1,51 @@ +# Integration Test with Cucumber + +## Technology Stack + +- [cucumber js](https://github.com/cucumber/cucumber-js) +- NodeJS v14.17.6 + +## How to start + +- install dependencies: `yarn install` +- run tests: `yarn test` + +if all right you should see something like that : + +```sh +15 scenarios (15 passed) +65 steps (65 passed) +0m09.409s (executing steps: 0m09.349s) +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ View your Cucumber Report at: β”‚ +β”‚ https://reports.cucumber.io/reports/16ebc4c0-cab6-41f6-9355-f894f9a9601d β”‚ +β”‚ β”‚ +β”‚ This report will self-destruct in 24h. β”‚ +β”‚ Keep reports forever: https://reports.cucumber.io/profile β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +Click on reporter link to view details . + +### Debug + +To run a single _feature_ or single _Scenario_ typing + +Ex. single _features_ `organizations.feature` + +```sh +npx cucumber-js -r step_definitions features/.feature +``` + +Ex. single _Scenario_ into `.feature` ( add source line ) + +```sh +npx cucumber-js -r step_definitions features/.feature:46 +``` + +### Note + +Remember to start the Backend before start the tests. + +You can configure the host in `./config/.env.local` file. + diff --git a/integration-test/src/config/.env.dev b/integration-test/src/config/.env.dev new file mode 100644 index 0000000..bd804a2 --- /dev/null +++ b/integration-test/src/config/.env.dev @@ -0,0 +1,17 @@ +PAY_OPT_HOST=https://api.dev.platform.pagopa.it/payment-options/service/v1 + +VALID_ORGANIZATIONAL_FISCAL_CODE=77777777777 +MISSING_ORGANIZATIONAL_FISCAL_CODE=87777777777 +SINGLE_OPT_NOTICE_NUMBER=311111111111111111 +SINGLE_AND_MANY_OPT_NOTICE_NUMBER=311111111111111112 +SINGLE_AND_MULTI_OPT_NOTICE_NUMBER=311111111111111116 +SINGLE_AND_CO_OPT_NOTICE_NUMBER=311111111112222225 +INVALID_NOTICE_NUMBER=111111111111111111 +DISABLED_STATION_NOTICE_NUMBER=399111111111111111 +DISABLED_STATION_ODP_NOTICE_NUMBER=312111111111111111 +ERROR_STATION_NOTICE_NUMBER=311199999999999999 +WRONG_STATION_NOTICE_NUMBER=311111111119999902 +UNAUTHORIZED_PSP=AAAAAAAAA +VALID_PSP=99999000001 + +API_TIMEOUT=30000 diff --git a/integration-test/src/config/.env.local b/integration-test/src/config/.env.local new file mode 100644 index 0000000..be6dcf4 --- /dev/null +++ b/integration-test/src/config/.env.local @@ -0,0 +1,18 @@ +PAY_OPT_HOST=http://localhost:8080 + +VALID_ORGANIZATIONAL_FISCAL_CODE=77777777777 +MISSING_ORGANIZATIONAL_FISCAL_CODE=87777777777 +SINGLE_OPT_NOTICE_NUMBER=311111111111111111 +SINGLE_AND_MANY_OPT_NOTICE_NUMBER=311111111111111112 +SINGLE_AND_MULTI_OPT_NOTICE_NUMBER=311111111111111116 +SINGLE_AND_CO_OPT_NOTICE_NUMBER=311111111112222225 +INVALID_NOTICE_NUMBER=111111111111111111 +DISABLED_STATION_NOTICE_NUMBER=399111111111111111 +DISABLED_STATION_ODP_NOTICE_NUMBER=312111111111111111 +ERROR_STATION_NOTICE_NUMBER=311199999999999999 +WRONG_STATION_NOTICE_NUMBER=311111111119999902 +UNAUTHORIZED_PSP=AAAAAAAAA +VALID_PSP=99999000001 + +API_TIMEOUT=30000 + diff --git a/integration-test/src/config/.env.uat b/integration-test/src/config/.env.uat new file mode 100644 index 0000000..45fb289 --- /dev/null +++ b/integration-test/src/config/.env.uat @@ -0,0 +1,18 @@ +PAY_OPT_HOST=https://api.uat.platform.pagopa.it/payment-options/service/v1 + +VALID_ORGANIZATIONAL_FISCAL_CODE=77777777777 +MISSING_ORGANIZATIONAL_FISCAL_CODE=87777777777 +SINGLE_OPT_NOTICE_NUMBER=311111111111111111 +SINGLE_AND_MANY_OPT_NOTICE_NUMBER=311111111111111112 +SINGLE_AND_MULTI_OPT_NOTICE_NUMBER=311111111111111116 +SINGLE_AND_CO_OPT_NOTICE_NUMBER=311111111112222225 +INVALID_NOTICE_NUMBER=111111111111111111 +DISABLED_STATION_NOTICE_NUMBER=399111111111111111 +DISABLED_STATION_ODP_NOTICE_NUMBER=312111111111111111 +ERROR_STATION_NOTICE_NUMBER=311199999999999999 +WRONG_STATION_NOTICE_NUMBER=311111111119999902 +UNAUTHORIZED_PSP=AAAAAAAAA +VALID_PSP=99999000001 + +API_TIMEOUT=30000 + diff --git a/integration-test/src/features/get_payment_options.feature b/integration-test/src/features/get_payment_options.feature new file mode 100644 index 0000000..a594c93 --- /dev/null +++ b/integration-test/src/features/get_payment_options.feature @@ -0,0 +1,58 @@ +Feature: Get Payment Options + + Scenario: Unauthorized idPsp + When an Http GET request is sent to recover payment options for taxCode "valid" with noticeNumber "single" and idPsp "invalid" + Then response has an http status that is either 404 or 403 + + Scenario: Nav not allowed for OdP + When an Http GET request is sent to recover payment options for taxCode "valid" with noticeNumber "invalid" and idPsp "valid" + Then response has a 400 Http status + And has error code "ODP-015" + + Scenario: Station is disabled + When an Http GET request is sent to recover payment options for taxCode "valid" with noticeNumber "disabled" and idPsp "valid" + Then response has a 400 Http status + And has error code "ODP-0099" + + Scenario: Station has odp flag disabled + When an Http GET request is sent to recover payment options for taxCode "valid" with noticeNumber "disabledOdp" and idPsp "valid" + Then response has a 400 Http status + And has error code "ODP-016" + + Scenario: Retrieve Payment Options (Opzione Unica) + When an Http GET request is sent to recover payment options for taxCode "valid" with noticeNumber "single" and idPsp "valid" + Then response has a 200 Http status + And payments options has size 1 + And payments option n 1 has 1 installments + + Scenario: Retrieve Payment Options (Opzione Unica + Unico Piano Rateale) + When an Http GET request is sent to recover payment options for taxCode "valid" with noticeNumber "singleAndMultiple" and idPsp "valid" + Then response has a 200 Http status + And payments options has size 2 + And payments option n 1 has 1 installments + And payments option n 2 has 3 installments + + Scenario: Retrieve Payment Options (Opzione Unica + Molteplici Piani Rateali) + When an Http GET request is sent to recover payment options for taxCode "valid" with noticeNumber "singleAndManyMultiples" and idPsp "valid" + Then response has a 200 Http status + And payments options has size 3 + And payments option n 1 has 1 installments + And payments option n 2 has 3 installments + And payments option n 3 has 5 installments + + Scenario: Retrieve Payment Options (Co-Obbligati) + When an Http GET request is sent to recover payment options for taxCode "valid" with noticeNumber "co-oblidged" and idPsp "valid" + Then response has a 200 Http status + And payments options has size 2 + And payments option n 1 has 1 installments + And payments option n 2 has 1 installments + + Scenario: PAA Wrong System Error + When an Http GET request is sent to recover payment options for taxCode "valid" with noticeNumber "wrong" and idPsp "valid" + Then response has a 400 Http status + And has error code "PAA_STAZIONE_INT_ERRATA" + + Scenario: PAA System Error + When an Http GET request is sent to recover payment options for taxCode "valid" with noticeNumber "error" and idPsp "valid" + Then response has a 400 Http status + And has error code "PAA_SYSTEM_ERROR" diff --git a/integration-test/src/package.json b/integration-test/src/package.json new file mode 100644 index 0000000..274fffa --- /dev/null +++ b/integration-test/src/package.json @@ -0,0 +1,21 @@ +{ + "name": "pagopa-pdf-engine", + "license": "MIT", + "version": "0.0.1", + "main": "index.js", + "scripts": { + "test": "dotenv -e ./config/.env.local yarn cucumber", + "test:local": "dotenv -e ./config/.env.local yarn cucumber", + "test:dev": "dotenv -e ./config/.env.dev yarn cucumber", + "test:uat": "dotenv -e ./config/.env.uat yarn cucumber", + "cucumber": "npx cucumber-js --publish -r step_definitions" + }, + "devDependencies": { + "@cucumber/cucumber": "^9.1.2", + "axios": "^1.4.0", + "dotenv": "^16.1.4", + "dotenv-cli": "^7.2.1", + "form-data": "^4.0.0", + "npx": "^10.2.2" + } +} diff --git a/integration-test/src/step_definitions/payment_options_step.js b/integration-test/src/step_definitions/payment_options_step.js new file mode 100644 index 0000000..0f3e04f --- /dev/null +++ b/integration-test/src/step_definitions/payment_options_step.js @@ -0,0 +1,120 @@ +const assert = require('assert'); +const {defineParameterType, Given, When, Then, After, AfterAll} = require('@cucumber/cucumber'); +const {getPaymentOptions} = require("./support/client/payment_options_client"); + +const idOrg = process.env.VALID_ORGANIZATIONAL_FISCAL_CODE; +const missingOrg = process.env.MISSING_ORGANIZATIONAL_FISCAL_CODE; +const singleOptNoticeNumber = process.env.SINGLE_OPT_NOTICE_NUMBER; +const singleOptAndSingleMultiNoticeNumber = process.env.SINGLE_AND_MANY_OPT_NOTICE_NUMBER; +const singleOptAndManyMultiNoticeNumber = process.env.SINGLE_AND_MULTI_OPT_NOTICE_NUMBER; +const coOptNoticeNumber = process.env.SINGLE_AND_CO_OPT_NOTICE_NUMBER; +const invalidStationNoticeNumber = process.env.INVALID_NOTICE_NUMBER; +const disabledStationNoticeNumber = process.env.DISABLED_STATION_NOTICE_NUMBER; +const disabledOdpStationNoticeNumber = process.env.DISABLED_STATION_ODP_NOTICE_NUMBER; +const missingStationNoticeNumber = process.env.MISSING_STATION_NOTICE_NUMBER; +const errorNoticeNumber = process.env.ERROR_STATION_NOTICE_NUMBER; +const wrongNoticeNumber = process.env.WRONG_STATION_NOTICE_NUMBER; +const invalidPsp = process.env.UNAUTHORIZED_PSP; +const validPsp = process.env.VALID_PSP; + + +const paymentOptions = []; + +defineParameterType({ + name: "boolean", + regexp: /true|false/, + transformer: (s) => s === "true" +}); + + +// After each Scenario +AfterAll(async function () { + + this.paymentOptions = []; + this.response = null; + +}); + +When('an Http GET request is sent to recover payment options for taxCode {string} with noticeNumber {string} and idPsp {string}', + async function (taxCode, noticeNumber, idPsp) { + + var selectedTaxCode; + var selectedNoticeNumber; + var selectedPsp; + + switch(taxCode) { + case "missing": + selectedTaxCode = missingOrg; + break; + default: + selectedTaxCode = idOrg; + } + + switch(noticeNumber) { + case "invalid": + selectedNoticeNumber = invalidStationNoticeNumber; + break; + case "disabledOdp": + selectedNoticeNumber = disabledOdpStationNoticeNumber; + break; + case "missing": + selectedNoticeNumber = missingStationNoticeNumber; + break; + case "single": + selectedNoticeNumber = singleOptNoticeNumber; + break; + case "singleAndMultiple": + selectedNoticeNumber = singleOptAndSingleMultiNoticeNumber; + break; + case "singleAndManyMultiples": + selectedNoticeNumber = singleOptAndManyMultiNoticeNumber; + break; + case "co-oblidged": + selectedNoticeNumber = coOptNoticeNumber; + break; + case "wrong": + selectedNoticeNumber = wrongNoticeNumber; + break; + case "disabled": + selectedNoticeNumber = disabledStationNoticeNumber; + break; + case "error": + selectedNoticeNumber = errorNoticeNumber; + break; + } + + switch (idPsp) { + case "invalid": + selectedPsp = invalidPsp; + break; + case "valid": + selectedPsp = validPsp; + } + + this.response = await getPaymentOptions( + selectedTaxCode, selectedNoticeNumber, selectedPsp); + + }); + +Then('payments options has size {int}', function (expectedSize) { + assert.strictEqual(this.response?.data?.paymentOptions.length, expectedSize); +}); + +Then('payments option n {int} has {int} installments', function (i, expectedSize) { + console.log(this.response?.data?.paymentOptions); + assert.strictEqual(this.response?.data?.paymentOptions[i - 1].installments.length, expectedSize); +}); + +Then('response has a {int} Http status', function (expectedStatus) { + assert.strictEqual(this.response.status, expectedStatus); +}); + + +Then('response has an http status that is either {int} or {int}', function (missingStatus, unauthorizedStatus) { + assert.strictEqual(true, this.response.status == missingStatus || this.response.status == unauthorizedStatus); +}); + + +Then('has error code {string}', function (expectedCode) { + assert.strictEqual(this.response?.data?.appErrorCode, expectedCode); +}); diff --git a/integration-test/src/step_definitions/support/client/payment_options_client.js b/integration-test/src/step_definitions/support/client/payment_options_client.js new file mode 100644 index 0000000..72d405f --- /dev/null +++ b/integration-test/src/step_definitions/support/client/payment_options_client.js @@ -0,0 +1,30 @@ +const {get} = require("../utility/axios_common"); + +const PAY_OPT_HOST = process.env.PAY_OPT_HOST; +const API_TIMEOUT = process.env.API_TIMEOUT; + +async function getPaymentOptions(taxCode, noticeNumber, idPsp) { + + let params = {}; + + if (idPsp) { + params.idPsp = idPsp; + params.idBrokerPsp = idPsp; + } + + const data = await get(PAY_OPT_HOST + + `/payment-options/organizations/`+taxCode+`/notices/`+noticeNumber, { + timeout: API_TIMEOUT, + params, + headers: { + "Ocp-Apim-Subscription-Key": process.env.SUBKEY, + "Content-Type": "application/json" + } + }); + + return data; +} + +module.exports = { + getPaymentOptions +} diff --git a/integration-test/src/step_definitions/support/utility/axios_common.js b/integration-test/src/step_definitions/support/utility/axios_common.js new file mode 100644 index 0000000..4159779 --- /dev/null +++ b/integration-test/src/step_definitions/support/utility/axios_common.js @@ -0,0 +1,44 @@ +const axios = require("axios"); + +function get(url, config) { + return axios.get(url, config) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +function post(url, body, config) { + return axios.post(url, body, config) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +function put(url, body, config) { + return axios.put(url, body, config) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + + +function del(url, config) { + return axios.delete(url, config) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +module.exports = {get, post, put, del} diff --git a/openapi/openapi.json b/openapi/openapi.json index 3c2cf5e..ecf4ff3 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -1,187 +1,203 @@ { - "openapi" : "3.0.3", - "info" : { - "title" : "PagoPa Payment Options (TEST)", - "description" : "PagoPa Payment Options service \n ### APP ERROR CODES ### \n\n\n
Details\n **NAME** | **HTTP STATUS CODE** | **DESCRIPTION** \n- | - | - \n **ODP-001** | *Bad Request* | ODP-SINTASSI. Errore riportato in caso di errore di sintassi nel contenuto della richiesta verso ODP\n **ODP-002** | *Not Found* | ODP_PSP_SCONOSCIUTO. Errore riportato in caso non sia presente fra i dati ottenuti da config-cache un psp coincidente con l’id fornito in input\n **ODP-003** | *Bad Request* | ODP_PSP_DISABILITATO. Errore riportato in caso il psp coincidente con l’id fornito in input risulti disabilitato\n **ODP-004** | *Not Found* | ODP_INTERMEDIARIO_PSP_SCONOSCIUTO. Errore riportato in caso il broker psp coincidente con i dati forniti in input non sia presente nei dati ottenuti da config-cache\n **ODP-005** | *Bad Request* | ODP_INTERMEDIARIO_PSP_DISABILITATO. Errore riportato in caso il broker psp coincidente con l’id fornito in input risulti disabilitato\n **ODP-006** | *Unauthorized* | ODP_AUTENTICAZIONE. Errore di autenticazione rispetto ai dati forniti\n **ODP-007** | *Forbidden* | ODP_AUTORIZZAZIONE. Errore di autorizzazione rispetto ai dati forniti\n **ODP-008** | *Bad Request* | ODP_SEMANTICA. Errore fornito in caso della presenza di casi d’invaliditΓ  semantica nei dati del flusso di verifica\n **ODP-009** | *Not Found* | ODP_STAZIONE_INT_PA_SCONOSCIUTA. Errore forninca in caso di assenza associazione stazione e PA\n **ODP-0099** | *Bad Request* | ODP_STAZIONE_INT_PA_DISABILITATA. Errore riportato in caso la stazione coincidente con l’id fornito in input risulti disabilitato\n **ODP-010** | *Service Unavailable* | ODP_STAZIONE_INT_PA_IRRAGGIUNGIBILE. Errore fornito in caso d’irraggiungibilitΓ  della stazione\n **ODP-011** | *Bad Request* | ODP_STAZIONE_INT_PA_SERVIZIO_NON_ATTIVO. Errore fornito in caso di servizio non attivo\n **ODP-012** | *Internal Server Error* | ODP_STAZIONE_INT_PA_TIMEOUT. Errore fornito in caso di timeout della stazione\n **ODP-013** | *Internal Server Error* | ODP_ERRORE_EMESSO_DA_PAA. Errore fornito in caso di errori da PAA\n **ODP-014** | *Bad Request* | ODP_STAZIONE_INT_PA_ERRORE_RESPONSE. Errore fornito in caso di risposta KO dalla stazione\n **ODP-015** | *Bad Request* | ODP_PSP_NAV_NOT_NMU. Errore fornito nel caso in cui il nav non sia valido per il flusso OdP\n **ODP-016** | *Bad Request* | ODP_SYSTEM_ERROR Codice d’errore generico\n **ODP-017** | *Internal Server Error* | ODP_SYSTEM_ERROR. Codice d’errore generico\n **ODP-018** | *Bad Request* | ODP_INTERMEDIARIO_PA_DISABILITATO\n **ODP-019** | *Not Found* | ODP_INTERMEDIARIO_PA_SCONOSCIUTO\n **ODP-020** | *Bad Request* | ODP_DOMINIO_DISABILITATO\n **ODP-021** | *Not Found* | ODP_DOMINIO_SCONOSCIUTO \n\n
\n", - "termsOfService" : "https://www.pagopa.gov.it/", - "version" : "0.4.2" + "openapi": "3.0.3", + "info": { + "title": "PagoPa Payment Options (TEST)", + "description": "PagoPa Payment Options service \n ### APP ERROR CODES ### \n\n\n
Details\n **NAME** | **HTTP STATUS CODE** | **DESCRIPTION** \n- | - | - \n **ODP-001** | *Bad Request* | ODP-SINTASSI. Errore riportato in caso di errore di sintassi nel contenuto della richiesta verso ODP\n **ODP-002** | *Not Found* | ODP_PSP_SCONOSCIUTO. Errore riportato in caso non sia presente fra i dati ottenuti da config-cache un psp coincidente con l’id fornito in input\n **ODP-003** | *Bad Request* | ODP_PSP_DISABILITATO. Errore riportato in caso il psp coincidente con l’id fornito in input risulti disabilitato\n **ODP-004** | *Not Found* | ODP_INTERMEDIARIO_PSP_SCONOSCIUTO. Errore riportato in caso il broker psp coincidente con i dati forniti in input non sia presente nei dati ottenuti da config-cache\n **ODP-005** | *Bad Request* | ODP_INTERMEDIARIO_PSP_DISABILITATO. Errore riportato in caso il broker psp coincidente con l’id fornito in input risulti disabilitato\n **ODP-006** | *Unauthorized* | ODP_AUTENTICAZIONE. Errore di autenticazione rispetto ai dati forniti\n **ODP-007** | *Forbidden* | ODP_AUTORIZZAZIONE. Errore di autorizzazione rispetto ai dati forniti\n **ODP-008** | *Bad Request* | ODP_SEMANTICA. Errore fornito in caso della presenza di casi d’invaliditΓ  semantica nei dati del flusso di verifica\n **ODP-009** | *Not Found* | ODP_STAZIONE_INT_PA_SCONOSCIUTA. Errore forninca in caso di assenza associazione stazione e PA\n **ODP-0099** | *Bad Request* | ODP_STAZIONE_INT_PA_DISABILITATA. Errore riportato in caso la stazione coincidente con l’id fornito in input risulti disabilitato\n **ODP-010** | *Service Unavailable* | ODP_STAZIONE_INT_PA_IRRAGGIUNGIBILE. Errore fornito in caso d’irraggiungibilitΓ  della stazione\n **ODP-011** | *Bad Request* | ODP_STAZIONE_INT_PA_SERVIZIO_NON_ATTIVO. Errore fornito in caso di servizio non attivo\n **ODP-012** | *Internal Server Error* | ODP_STAZIONE_INT_PA_TIMEOUT. Errore fornito in caso di timeout della stazione\n **ODP-013** | *Internal Server Error* | ODP_ERRORE_EMESSO_DA_PAA. Errore fornito in caso di errori da PAA\n **ODP-014** | *Bad Request* | ODP_STAZIONE_INT_PA_ERRORE_RESPONSE. Errore fornito in caso di risposta KO dalla stazione\n **ODP-015** | *Bad Request* | ODP_PSP_NAV_NOT_NMU. Errore fornito nel caso in cui il nav non sia valido per il flusso OdP\n **ODP-016** | *Bad Request* | ODP_SYSTEM_ERROR Codice d’errore generico\n **ODP-017** | *Internal Server Error* | ODP_SYSTEM_ERROR. Codice d’errore generico\n **ODP-018** | *Bad Request* | ODP_INTERMEDIARIO_PA_DISABILITATO\n **ODP-019** | *Not Found* | ODP_INTERMEDIARIO_PA_SCONOSCIUTO\n **ODP-020** | *Bad Request* | ODP_DOMINIO_DISABILITATO\n **ODP-021** | *Not Found* | ODP_DOMINIO_SCONOSCIUTO \n\n
\n", + "termsOfService": "https://www.pagopa.gov.it/", + "version": "0.4.2-13-PPANTT-131-feat-add-integration-tests" }, - "servers" : [ { - "url" : "http://localhost:8080", - "description" : "Localhost base URL" - }, { - "url" : "https://{host}/payment-options/service/v1", - "description" : "Base URL", - "variables" : { - "host" : { - "default" : "api.dev.platform.pagopa.it", - "enum" : [ "api.dev.platform.pagopa.it", "api.uat.platform.pagopa.it", "api.platform.pagopa.it" ] + "servers": [ + { + "url": "http://localhost:8080", + "description": "Localhost base URL" + }, + { + "url": "https://{host}/payment-options/service/v1", + "description": "Base URL", + "variables": { + "host": { + "default": "api.dev.platform.pagopa.it", + "enum": [ + "api.dev.platform.pagopa.it", + "api.uat.platform.pagopa.it", + "api.platform.pagopa.it" + ] + } } } - } ], - "paths" : { - "/info" : { - "get" : { - "tags" : [ "Generic" ], - "responses" : { - "200" : { - "description" : "OK", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/AppInfo" + ], + "paths": { + "/info": { + "get": { + "tags": [ + "Generic" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppInfo" } } } }, - "400" : { - "description" : "Bad Request", - "content" : { - "application/json" : { } + "400": { + "description": "Bad Request", + "content": { + "application/json": {} } } } } }, - "/payment-options/organizations/{fiscal-code}/notices/{notice-number}" : { - "get" : { - "tags" : [ "Payment Options Resource" ], - "summary" : "Get payment options", - "description" : "Retrieve the payment options related to the provided input", - "operationId" : "getPaymentOptions", - "parameters" : [ { - "name" : "fiscal-code", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - }, { - "name" : "notice-number", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - }, { - "name" : "idPsp", - "in" : "query", - "schema" : { - "type" : "string" - } - }, { - "name" : "X-Session-Id", - "in" : "header", - "schema" : { - "type" : "string" + "/payment-options/organizations/{fiscal-code}/notices/{notice-number}": { + "get": { + "tags": [ + "Payment Options Resource" + ], + "summary": "Get payment options", + "description": "Retrieve the payment options related to the provided input", + "operationId": "getPaymentOptions", + "parameters": [ + { + "name": "fiscal-code", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "notice-number", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "idPsp", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "X-Session-Id", + "in": "header", + "schema": { + "type": "string" + } } - } ], - "responses" : { - "500" : { - "$ref" : "#/components/responses/ErrorResponse500" + ], + "responses": { + "500": { + "$ref": "#/components/responses/ErrorResponse500" }, - "400" : { - "$ref" : "#/components/responses/ErrorResponse400" + "400": { + "$ref": "#/components/responses/ErrorResponse400" }, - "404" : { - "$ref" : "#/components/responses/ErrorResponse404" + "404": { + "$ref": "#/components/responses/ErrorResponse404" }, - "200" : { - "description" : "Success", - "content" : { - "application/json" : { } + "200": { + "description": "Success", + "content": { + "application/json": {} } } } } } }, - "components" : { - "schemas" : { - "AppInfo" : { - "type" : "object", - "properties" : { - "name" : { - "type" : "string" + "components": { + "schemas": { + "AppInfo": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "version" : { - "type" : "string" + "version": { + "type": "string" }, - "environment" : { - "type" : "string" + "environment": { + "type": "string" } } }, - "ErrorResponse" : { - "type" : "object", - "properties" : { - "httpStatusCode" : { - "format" : "int32", - "type" : "integer", - "example" : 500 + "ErrorResponse": { + "type": "object", + "properties": { + "httpStatusCode": { + "format": "int32", + "type": "integer", + "example": 500 }, - "httpStatusDescription" : { - "type" : "string", - "example" : "Internal Server Error" + "httpStatusDescription": { + "type": "string", + "example": "Internal Server Error" }, - "errorMessage" : { - "type" : "string", - "example" : "An unexpected error has occurred. Please contact support." + "errorMessage": { + "type": "string", + "example": "An unexpected error has occurred. Please contact support." }, - "appErrorCode" : { - "type" : "string", - "example" : "ODP-" + "appErrorCode": { + "type": "string", + "example": "ODP-" }, - "timestamp" : { - "format" : "int64", - "type" : "integer", - "example" : 1724425035 + "timestamp": { + "format": "int64", + "type": "integer", + "example": 1724425035 }, - "dateTime" : { - "type" : "string", - "example" : "2024-08-23T14:57:15.635528" + "dateTime": { + "type": "string", + "example": "2024-08-23T14:57:15.635528" } } } }, - "responses" : { - "ErrorResponse400" : { - "description" : "Default app exception for status 400", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "responses": { + "ErrorResponse400": { + "description": "Default app exception for status 400", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } }, - "ErrorResponse404" : { - "description" : "Default app exception for status 404", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "ErrorResponse404": { + "description": "Default app exception for status 404", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } }, - "ErrorResponse500" : { - "description" : "Internal Server Error", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "ErrorResponse500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } } }, - "securitySchemes" : { - "ApiKey" : { - "type" : "apiKey", - "name" : "Ocp-Apim-Subscription-Key", - "in" : "header" + "securitySchemes": { + "ApiKey": { + "type": "apiKey", + "name": "Ocp-Apim-Subscription-Key", + "in": "header" } } } -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index fdd3d70..ed316bd 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ it.gov.pagopa payment-options-service Payment Options Service - 0.4.2 + 0.4.2-13-PPANTT-131-feat-add-integration-tests 0.8.7 diff --git a/src/main/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClient.java b/src/main/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClient.java index f5366c4..57c6e97 100644 --- a/src/main/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClient.java +++ b/src/main/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClient.java @@ -6,6 +6,7 @@ import it.gov.pagopa.payment.options.models.ErrorResponse; import it.gov.pagopa.payment.options.models.clients.creditorInstitution.PaymentOptionsResponse; import it.gov.pagopa.payment.options.models.enums.AppErrorCodeEnum; +import it.gov.pagopa.payment.options.models.events.odpRe.Properties; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; @@ -50,6 +51,7 @@ public PaymentOptionsResponse callEcPaymentOptionsVerify( RestClientBuilder builder = RestClientBuilder.newBuilder().baseUrl( new URL(String.format(endpoint, fiscalCode, noticeNumber))); + if (proxyHost != null && proxyPort != null) { builder = builder.proxyAddress(proxyHost, proxyPort.intValue()); } @@ -57,9 +59,7 @@ public PaymentOptionsResponse callEcPaymentOptionsVerify( CreditorInstitutionRestClientInterface.class); try (Response response = ecRestClientInterface.verifyPaymentOptions( - fiscalCode, noticeNumber, - targetHost, targetPort.intValue(), - targetPath)) { + targetHost, targetPort.intValue(), targetPath)) { return objectMapper.readValue( response.readEntity(String.class), PaymentOptionsResponse.class); @@ -72,6 +72,7 @@ public PaymentOptionsResponse callEcPaymentOptionsVerify( } catch (Exception e) { logger.error("[Payment Options] Unable to call the station due to error: {}", e.getMessage()); + logger.error(e.getMessage(), e); throw new PaymentOptionsException( AppErrorCodeEnum.ODP_STAZIONE_INT_PA_IRRAGGIUNGIBILE, e.getMessage()); } diff --git a/src/main/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClientInterface.java b/src/main/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClientInterface.java index 73f5ca4..593a881 100644 --- a/src/main/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClientInterface.java +++ b/src/main/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClientInterface.java @@ -1,23 +1,27 @@ package it.gov.pagopa.payment.options.clients; +import it.gov.pagopa.payment.options.models.events.odpRe.Properties; import jakarta.ws.rs.GET; import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody; import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; +import org.jboss.resteasy.reactive.PartType; +import org.jboss.resteasy.reactive.RestForm; /** * Template for the creditor institution REST client */ public interface CreditorInstitutionRestClientInterface { - @GET - @Path("/payment-options/organizations/{fiscal-code}/notices/{notice-number}") + @POST + @Path("/forward") @ClientHeaderParam(name = "Ocp-Apim-Subscription-Key", value = "${CreditorInstitutionRestClient.ocpSubKey}") Response verifyPaymentOptions( - @PathParam("fiscal-code") String fiscalCode, - @PathParam("notice-number") String noticeNumber, @HeaderParam("X-Host-Url") String hostUrl, @HeaderParam("X-Host-Port") Integer hostPort, @HeaderParam("X-Host-Path") String hostPath diff --git a/src/main/java/it/gov/pagopa/payment/options/models/clients/cache/Station.java b/src/main/java/it/gov/pagopa/payment/options/models/clients/cache/Station.java index 1b17687..259ddb8 100644 --- a/src/main/java/it/gov/pagopa/payment/options/models/clients/cache/Station.java +++ b/src/main/java/it/gov/pagopa/payment/options/models/clients/cache/Station.java @@ -68,7 +68,7 @@ public class Station { @JsonProperty("flag_standin") private Boolean flagStandin = null; - @JsonProperty("verify_payment_option_enabled") + @JsonProperty("is_payment_options_enabled") private Boolean verifyPaymentOptionEnabled = false; @JsonProperty("rest_endpoint") diff --git a/src/main/java/it/gov/pagopa/payment/options/services/CreditorInstitutionService.java b/src/main/java/it/gov/pagopa/payment/options/services/CreditorInstitutionService.java index 073cffb..8af92be 100644 --- a/src/main/java/it/gov/pagopa/payment/options/services/CreditorInstitutionService.java +++ b/src/main/java/it/gov/pagopa/payment/options/services/CreditorInstitutionService.java @@ -9,6 +9,7 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import java.net.MalformedURLException; +import java.util.Optional; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,6 +25,9 @@ public class CreditorInstitutionService { @ConfigProperty(name = "CreditorInstitutionRestClient.apimEndpoint") String APIM_FORWARDER_ENDPOINT; + @ConfigProperty(name = "CreditorInstitutionRestClient.apimPath") + Optional APIM_FORWARDER_PATH; + static String PAYMENT_OPTIONS_SERVICE_SUFFIX = "/payment-options/organizations/%s/notices/%s"; @Inject @@ -50,8 +54,7 @@ public PaymentOptionsResponse getPaymentOptions( "[Payment Options] Station not configured to pass through the APIM Forwarder"); } - String endpoint = getEndpoint(station); - + String endpoint = getEndpoint(station, APIM_FORWARDER_PATH.orElse("")); if (station.getRestEndpoint() == null) { throw new PaymentOptionsException(AppErrorCodeEnum.ODP_SEMANTICA, @@ -64,14 +67,14 @@ public PaymentOptionsResponse getPaymentOptions( try { String[] verifyEndpointParts = station.getRestEndpoint().split("/", 4); - targetHost = verifyEndpointParts[0] + verifyEndpointParts[2]; + targetHost = verifyEndpointParts[2]; String[] hostSplit = verifyEndpointParts[2].split(":"); targetPort = hostSplit.length > 1 ? Long.parseLong(hostSplit[1]) : verifyEndpointParts[0].contains(ProtocolEnum.HTTPS.name().toLowerCase()) ? 443L : 80L; - targetPath = verifyEndpointParts[3].concat( - String.format(PAYMENT_OPTIONS_SERVICE_SUFFIX, fiscalCode, noticeNumber)); + String formattedPath = String.format(PAYMENT_OPTIONS_SERVICE_SUFFIX, fiscalCode, noticeNumber); + targetPath = verifyEndpointParts.length > 3 ? verifyEndpointParts[3].concat(formattedPath) : formattedPath; } catch (Exception e) { logger.error("[Payment Options] Malformed Target URL: {}", e.getMessage()); throw new PaymentOptionsException(AppErrorCodeEnum.ODP_SEMANTICA, e.getMessage()); @@ -92,14 +95,15 @@ public PaymentOptionsResponse getPaymentOptions( } - private static String getEndpoint(Station station) { + private static String getEndpoint(Station station, String apimForwarderPath) { return (station.getConnection().getProtocol() != null && (station.getConnection().getProtocol().equals(ProtocolEnum.HTTPS)) ? ProtocolEnum.HTTPS.name().toLowerCase() : station.getConnection().getProtocol().name().toLowerCase()) + "://" + station.getConnection().getIp() + ":" + (station.getConnection().getPort() != null ? - String.valueOf(station.getConnection().getPort()) : "80"); + String.valueOf(station.getConnection().getPort()) : "80") + .concat(apimForwarderPath); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index b8b7c2d..50b43f4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -83,6 +83,8 @@ ApiConfigCacheClient.ocpSubKey=${APICONFIG_SUBKEY:} CreditorInstitutionRestClient.ocpSubKey=${EC_APIM_SUBKEY:} CreditorInstitutionRestClient.apimEndpoint=${EC_APIM_FORWARDER_ENDPOINT:localhost:8083} +CreditorInstitutionRestClient.apimPath=${EC_APIM_PATH:/pagopa-node-forwarder/api/v1} + ################### ## OPENTELEMETRY diff --git a/src/test/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClientTest.java b/src/test/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClientTest.java index 1f6a8a1..697ed28 100644 --- a/src/test/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClientTest.java +++ b/src/test/java/it/gov/pagopa/payment/options/clients/CreditorInstitutionRestClientTest.java @@ -34,7 +34,7 @@ void callEcPaymentOptionsVerifyShouldReturnData() { PaymentOptionsResponse paymentOptionsResponse = assertDoesNotThrow(() -> creditorInstitutionRestClient.callEcPaymentOptionsVerify( wiremockUrl, null, null, - "http://externalService", 443L, "/externalPath", + "http://externalService", 443L, "/payment-options/organizations/77777777777/notices/311111111112222222", "77777777777", "311111111112222222")); assertNotNull(paymentOptionsResponse); } @@ -45,7 +45,7 @@ void callEcPaymentOptionsVerifyShouldReturnErrorResponse() { assertThrows(CreditorInstitutionException.class, () -> creditorInstitutionRestClient.callEcPaymentOptionsVerify( wiremockUrl, null, null, - "http://externalService", 443L, "/externalPath", + "http://externalService", 443L, "/payment-options/organizations/87777777777/notices/311111111112222222", "87777777777", "311111111112222222")); assertNotNull(exception); assertEquals(exception.getErrorResponse().getHttpStatusCode(), 500); @@ -57,7 +57,7 @@ void callEcPaymentOptionsVerifyShouldReturnErrorOnUnexpectedContent() { assertThrows(PaymentOptionsException.class, () -> creditorInstitutionRestClient.callEcPaymentOptionsVerify( wiremockUrl, null, null, - "http://externalService", 443L, "/externalPath", + "http://externalService", 443L, "/payment-options/organizations/97777777777/notices/311111111112222222", "97777777777", "311111111112222222")); assertNotNull(exception); } @@ -68,7 +68,7 @@ void callEcPaymentOptionsVerifyShouldReturnUnreachableKOWithoutErrorResponse() { assertThrows(PaymentOptionsException.class, () -> creditorInstitutionRestClient.callEcPaymentOptionsVerify( wiremockUrl, null, null, - "http://externalService", 443L, "/externalPath", + "http://externalService", 443L, "/payment-options/organizations/08888888888/notices/88888888888", "08888888888", "88888888888")); assertNotNull(exception); assertEquals(exception.getErrorCode(), AppErrorCodeEnum.ODP_STAZIONE_INT_PA_IRRAGGIUNGIBILE); @@ -80,7 +80,7 @@ void callEcPaymentOptionsVerifyShouldReturnExceptionOnMalformedUrl() { assertThrows(MalformedURLException.class, () -> creditorInstitutionRestClient.callEcPaymentOptionsVerify( "AAAAAAA", null, null, - "http://externalService", 443L, "/externalPath", + "http://externalService", 443L, "/payment-options/organizations/08888888888/notices/88888888888", "88888888888", "88888888888")); } @@ -89,7 +89,7 @@ void callEcPaymentOptionsVerifyShouldReturnExceptionOnWrongProxy() { assertThrows(Exception.class, () -> creditorInstitutionRestClient.callEcPaymentOptionsVerify( "AAAAAAA", "AAAAAAA%%%", 8081L, - "http://externalService", 443L, "/externalPath", + "http://externalService", 443L, "/payment-options/organizations/08888888888/notices/88888888888", "88888888888", "88888888888")); } diff --git a/src/test/java/it/gov/pagopa/payment/options/test/extensions/WireMockExtensions.java b/src/test/java/it/gov/pagopa/payment/options/test/extensions/WireMockExtensions.java index 8b292a1..5774f12 100644 --- a/src/test/java/it/gov/pagopa/payment/options/test/extensions/WireMockExtensions.java +++ b/src/test/java/it/gov/pagopa/payment/options/test/extensions/WireMockExtensions.java @@ -1,7 +1,9 @@ package it.gov.pagopa.payment.options.test.extensions; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import com.fasterxml.jackson.databind.ObjectMapper; @@ -50,7 +52,7 @@ public Map start() { .connection(Connection.builder() .protocol(ProtocolEnum.HTTP).ip("localhost") .port((long) wireMockServer.port()).build()) - .restEndpoint("https://localhost:9095/test") + .restEndpoint("https://localhost:9095") .build())) .creditorInstitutions(Map.of("77777777777", CreditorInstitution.builder() @@ -73,8 +75,9 @@ public Map start() { )); wireMockServer.stubFor( - get(urlEqualTo( - "/payment-options/organizations/97777777777/notices/311111111112222222")) + post(urlEqualTo( + "/forward")) + .withHeader("X-Host-Path", equalTo("/payment-options/organizations/97777777777/notices/311111111112222222")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody("AAAAAAAA") @@ -82,8 +85,9 @@ public Map start() { ); wireMockServer.stubFor( - get(urlEqualTo( - "/payment-options/organizations/77777777777/notices/311111111112222222")) + post(urlEqualTo( + "/forward")) + .withHeader("X-Host-Path", equalTo("/payment-options/organizations/77777777777/notices/311111111112222222")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withResponseBody( @@ -95,8 +99,9 @@ public Map start() { ); wireMockServer.stubFor( - get(urlEqualTo( - "/payment-options/organizations/87777777777/notices/311111111112222222")) + post(urlEqualTo( + "/forward")) + .withHeader("X-Host-Path", equalTo("/payment-options/organizations/87777777777/notices/311111111112222222")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withStatus(412) @@ -119,6 +124,7 @@ public Map start() { "ApiConfigCacheClient.ocpSubKey", "test", "CreditorInstitutionRestClient.apimEndpoint", wireMockServer.baseUrl(), + "CreditorInstitutionRestClient.apimPath", "", "CreditorInstitutionRestClient.ocpSubKey", "test", "wiremock.port", String.valueOf(wireMockServer.port()) );