feat(connector): add Ilixium routing via unified connector service #39345
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Run Cypress tests | |
| on: | |
| merge_group: | |
| types: | |
| - checks_requested | |
| pull_request: | |
| permissions: | |
| contents: read | |
| actions: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_INCREMENTAL: 0 | |
| CARGO_NET_RETRY: 10 | |
| MANDATORY_PAYMENTS_CONNECTORS_BATCH_1: "stripe" | |
| OPTIONAL_PAYMENTS_CONNECTORS_BATCH_1: "worldpay braintree cryptopay paypal worldpayxml volt" | |
| OPTIONAL_PAYMENTS_CONNECTORS_BATCH_2: "gigadat checkout redsys novalnet noon" | |
| OPTIONAL_PAYMENTS_CONNECTORS_BATCH_3: "loonio zift airwallex fiuu" | |
| ALPHA_PAYMENTS_CONNECTORS: "celero silverflow" | |
| PLATFORM_CONNECTORS: "stripe" | |
| EXTENDED_PAYMENTS_CONNECTORS_BATCH_1: "bluesnap gigadat loonio nmi" | |
| EXTENDED_PAYMENTS_CONNECTORS_BATCH_2: "paypal redsys zift" | |
| EXTENDED_PAYOUTS_CONNECTORS: "adyenplatform wise" | |
| MOCK_PAYMENTS_CONNECTORS_BATCH_1: "gigadat zift loonio payload redsys worldpayxml" | |
| MOCK_PAYMENTS_CONNECTORS_BATCH_2: "cybersource braintree nmi calida adyen peachpayments" | |
| MOCK_PAYMENTS_CONNECTORS_BATCH_3: "novalnet checkout authorizedotnet cashtocode fiuu nexixpay" | |
| MOCK_PAYMENTS_CONNECTORS_BATCH_4: "finix" | |
| MOCK_PAYOUTS_CONNECTORS: "adyenplatform wise" | |
| RUST_BACKTRACE: short | |
| RUSTUP_MAX_RETRIES: 10 | |
| RUN_TESTS: ${{ ((github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name)) || (github.event_name == 'merge_group')}} | |
| DEBUG: cypress:cli | |
| RUST_MIN_STACK: 16777216 | |
| jobs: | |
| formatter: | |
| name: Run formatter on Cypress tests and address lints | |
| if: ${{ github.event_name == 'pull_request' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate a token | |
| if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} | |
| id: generate_token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| app-id: ${{ secrets.HYPERSWITCH_BOT_APP_ID }} | |
| private-key: ${{ secrets.HYPERSWITCH_BOT_APP_PRIVATE_KEY }} | |
| - name: Checkout repository with token | |
| if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event.pull_request.head.ref }} | |
| token: ${{ steps.generate_token.outputs.token }} | |
| - name: Checkout repository for fork | |
| if: ${{ github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} | |
| uses: actions/checkout@v6 | |
| - name: Install Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| - name: Install Cypress and dependencies | |
| run: | | |
| npm ci --prefix ./cypress-tests | |
| - name: Check formatting for forked pull requests | |
| if: ${{ github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} | |
| shell: bash | |
| run: | | |
| npm run format:check --prefix cypress-tests | |
| npm run lint --prefix cypress-tests | |
| - name: Check formatting | |
| if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} | |
| shell: bash | |
| run: | | |
| npm run format --prefix cypress-tests | |
| npm run lint --prefix cypress-tests -- --fix | |
| if ! git diff --exit-code --quiet -- cypress-tests; then | |
| echo "::notice::Cypress formatting and lint check failed" | |
| git config --local user.name 'hyperswitch-bot[bot]' | |
| git config --local user.email '148525504+hyperswitch-bot[bot]@users.noreply.github.com' | |
| git add cypress-tests | |
| git commit --message 'chore(cypress): run formatter and address lints' | |
| git push | |
| fi | |
| check-cypress-prerequisites: | |
| name: Check Cypress prerequisites | |
| runs-on: ubuntu-latest | |
| outputs: | |
| run-checks: ${{ steps.check.outputs.run-checks }} | |
| steps: | |
| - name: Check if S-test-ready label is present | |
| id: check | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| # Merge queue, run checks | |
| if [[ "${{ github.event_name }}" == "merge_group" ]]; then | |
| echo "run-checks=true" >> "${GITHUB_OUTPUT}" | |
| exit 0 | |
| fi | |
| HEAD_REPO="${{ github.event.pull_request.head.repo.full_name }}" | |
| BASE_REPO="${{ github.event.pull_request.base.repo.full_name }}" | |
| # Repository is a fork. | |
| # `RUN_TESTS` based checks successfully skip the checks anyway, | |
| # no point gating it behind a label in that case. | |
| if [[ "${HEAD_REPO}" != "${BASE_REPO}" ]]; then | |
| echo "run-checks=true" >> "${GITHUB_OUTPUT}" | |
| exit 0 | |
| fi | |
| LABELS=$( | |
| gh pr view ${{ github.event.pull_request.number }} \ | |
| --repo ${{ github.repository }} \ | |
| --json labels \ | |
| --jq '.labels[].name' | |
| ) | |
| if echo "${LABELS}" | grep --quiet "S-test-ready"; then | |
| echo "run-checks=true" >> "${GITHUB_OUTPUT}" | |
| else | |
| echo "run-checks=false" >> "${GITHUB_OUTPUT}" | |
| exit 1 | |
| fi | |
| build-hyperswitch: | |
| name: Build hyperswitch server | |
| needs: [check-cypress-prerequisites] | |
| if: ${{ needs.check-cypress-prerequisites.outputs.run-checks == 'true' }} | |
| # use hyperswitch-runners-merge for merge queue level checks | |
| runs-on: ${{ case(github.event_name == 'merge_group', 'hyperswitch-runners-merge', 'hyperswitch-runners') }} | |
| env: | |
| # Use `sccache` for caching compilation artifacts | |
| RUSTC_WRAPPER: sccache | |
| steps: | |
| - name: Skip tests for PRs from forks | |
| shell: bash | |
| if: ${{ env.RUN_TESTS == 'false' }} | |
| run: echo 'Skipping tests for PRs from forks' | |
| - name: Checkout repository | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/checkout@v6 | |
| - name: Install mold linker | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: rui314/setup-mold@v1 | |
| with: | |
| make-default: true | |
| - name: Install Rust | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: stable 2 weeks ago | |
| components: clippy | |
| - name: Install Protoc | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: arduino/setup-protoc@v3 | |
| with: | |
| repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Install sccache | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: sccache | |
| checksum: true | |
| - name: Build project | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: cargo build --package router --bin router --jobs 2 | |
| - name: Upload binary artifact | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: hyperswitch-server | |
| overwrite: true | |
| path: target/debug/router | |
| retention-days: 1 | |
| prepare-mandatory-matrix: | |
| name: Prepare Mandatory Connectors matrix to run cypress tests | |
| needs: [check-cypress-prerequisites] | |
| if: ${{ needs.check-cypress-prerequisites.outputs.run-checks == 'true' }} | |
| # use hyperswitch-runners-merge for merge queue level checks | |
| runs-on: ${{ case(github.event_name == 'merge_group', 'hyperswitch-runners-merge', 'hyperswitch-runners') }} | |
| outputs: | |
| mandatory-matrix: ${{ steps.set-mandatory-matrix.outputs.matrix }} | |
| steps: | |
| - name: Set matrix for mandatory connectors batches | |
| id: set-mandatory-matrix | |
| shell: bash | |
| run: | | |
| { | |
| echo "matrix<<EOF" | |
| echo '{ | |
| "batch": [ | |
| { | |
| "name": "batch-1", | |
| "connectors": "'"${MANDATORY_PAYMENTS_CONNECTORS_BATCH_1}"'", | |
| "parallel": 3, | |
| "report_artifact_name": "cypress-mandatory-test-results-batch-1", | |
| "server_logs_artifact": "mandatory-server-logs-batch-1" | |
| }, | |
| { | |
| "name": "platform", | |
| "connectors": "", | |
| "platform_connectors": "'"${PLATFORM_CONNECTORS}"'", | |
| "parallel": 2, | |
| "report_artifact_name": "cypress-mandatory-test-results-platform", | |
| "server_logs_artifact": "mandatory-server-logs-platform" | |
| } | |
| ] | |
| }' | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| mandatory-cypress-tests: | |
| name: Run mandatory Cypress tests | |
| needs: [build-hyperswitch, prepare-mandatory-matrix] | |
| permissions: | |
| pull-requests: write | |
| # use hyperswitch-runners-merge for merge queue level checks | |
| runs-on: ${{ case(github.event_name == 'merge_group', 'hyperswitch-runners-merge', 'hyperswitch-runners') }} | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.prepare-mandatory-matrix.outputs['mandatory-matrix']) }} | |
| env: | |
| # Use `sccache` for caching compilation artifacts | |
| RUSTC_WRAPPER: sccache | |
| services: | |
| redis: | |
| image: "public.ecr.aws/docker/library/redis:alpine" | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| postgres: | |
| image: "public.ecr.aws/docker/library/postgres:alpine" | |
| env: | |
| POSTGRES_USER: db_user | |
| POSTGRES_PASSWORD: db_pass | |
| POSTGRES_DB: hyperswitch_db | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| superposition: | |
| image: ghcr.io/juspay/superposition-demo:0.102.0 | |
| env: | |
| API_HOSTNAME: http://localhost:8081 | |
| REDIS_URL: redis://redis:6379 | |
| ports: | |
| - 8081:8080 | |
| steps: | |
| - name: Skip tests for PRs from forks | |
| if: ${{ env.RUN_TESTS == 'false' }} | |
| shell: bash | |
| run: echo 'Skipping tests for PRs from forks' | |
| - name: Checkout repository | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/checkout@v6 | |
| - name: Install Rust | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: stable 2 weeks ago | |
| components: rustfmt | |
| - name: Install sccache | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: sccache | |
| checksum: true | |
| - name: Download hyperswitch server artifact | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: hyperswitch-server | |
| path: target/debug | |
| - name: Make server executable | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: chmod +x target/debug/router | |
| - name: Download Encrypted TOML from S3 and Decrypt | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.CONNECTOR_CREDS_AWS_ACCESS_KEY_ID }} | |
| AWS_REGION: ${{ secrets.CONNECTOR_CREDS_AWS_REGION }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CONNECTOR_CREDS_AWS_SECRET_ACCESS_KEY }} | |
| CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} | |
| CONNECTOR_CREDS_S3_BUCKET_URI: ${{ secrets.CONNECTOR_CREDS_S3_BUCKET_URI}} | |
| DESTINATION_FILE_NAME: "creds.json.gpg" | |
| S3_SOURCE_FILE_NAME: "b06ec672-ca83-405a-a834-856c3fe18dad.json.gpg" | |
| shell: bash | |
| run: | | |
| mkdir -p ".github/secrets" ".github/test" | |
| aws s3 cp "${CONNECTOR_CREDS_S3_BUCKET_URI}/${S3_SOURCE_FILE_NAME}" ".github/secrets/${DESTINATION_FILE_NAME}" | |
| gpg --quiet --batch --yes --decrypt --passphrase="${CONNECTOR_AUTH_PASSPHRASE}" --output ".github/test/creds.json" ".github/secrets/${DESTINATION_FILE_NAME}" | |
| - name: Set paths in env | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| shell: bash | |
| run: | | |
| echo "CYPRESS_CONNECTOR_AUTH_FILE_PATH=${{ github.workspace }}/.github/test/creds.json" >> $GITHUB_ENV | |
| echo "SERVER_LOG_PATH=${{ github.workspace }}/server-logs.txt" >> $GITHUB_ENV | |
| - name: Fetch keys | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| TOML_PATH: "./config/development.toml" | |
| run: | | |
| LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' ${TOML_PATH}) | |
| echo "CYPRESS_ADMINAPIKEY=${LOCAL_ADMIN_API_KEY}" >> $GITHUB_ENV | |
| - name: Install Diesel CLI | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: baptiste0928/cargo-install@v3.4.0 | |
| with: | |
| crate: diesel_cli | |
| features: postgres | |
| args: --no-default-features | |
| - name: Install Just | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: just | |
| checksum: true | |
| - name: Install Node.js | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| - name: Install Cypress and dependencies | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| npm ci --prefix ./cypress-tests | |
| - name: Run database migrations | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| shell: bash | |
| env: | |
| DATABASE_URL: postgres://db_user:db_pass@localhost:5432/hyperswitch_db | |
| run: just migrate run --locked-schema | |
| - name: Insert card info into the database | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy cards_info FROM '.github/data/cards_info.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Insert GSM info into the database | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy gateway_status_map FROM '.github/data/gateway_status_map.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Seed Superposition | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| SUPERPOSITION_URL: "http://localhost:8081" | |
| SEED_FILE: "./config/superposition_seed.toml" | |
| WORKSPACE_ID: "dev" | |
| ORG_ID: "localorg" | |
| run: bash scripts/seed_superposition.sh | |
| - name: Setup Local Server | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC_KEY: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT_KEY: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE_KEY }} | |
| # Apply seeded Superposition config quickly in CI (default is 300s). | |
| ROUTER__SUPERPOSITION__POLLING_INTERVAL: 10 | |
| SERVER_LOG_PATH: ${{ github.workspace }}/server-logs.txt | |
| run: | | |
| # Start the server in the background | |
| target/debug/router >> "${{ env.SERVER_LOG_PATH }}" 2>&1 & | |
| SERVER_PID=$! | |
| echo "PID=${SERVER_PID}" >> $GITHUB_ENV | |
| # Wait for the server to start in port 8080 | |
| COUNT=0 | |
| while ! nc -z localhost 8080; do | |
| if [ $COUNT -gt 12 ]; then # Wait for up to 2 minutes (12 * 10 seconds) | |
| echo "Server did not start within a reasonable time. Exiting." | |
| kill ${SERVER_PID} | |
| exit 1 | |
| else | |
| COUNT=$((COUNT+1)) | |
| sleep 10 | |
| fi | |
| done | |
| - name: Run Cypress tests | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| CYPRESS_BASEURL: "http://localhost:8080" | |
| ROUTER__SERVER__WORKERS: 4 | |
| PAYMENTS_CONNECTORS: "${{ matrix.batch.connectors }}" | |
| PLATFORM_CONNECTORS: "${{ matrix.batch.platform_connectors }}" | |
| shell: bash -leuo pipefail {0} | |
| run: | | |
| scripts/execute_cypress.sh --parallel ${{ matrix.batch.parallel }} | |
| kill "${{ env.PID }}" | |
| - name: Upload server logs on failure | |
| if: env.RUN_TESTS == 'true' && failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ matrix.batch.server_logs_artifact }} | |
| path: ${{ env.SERVER_LOG_PATH }} | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| - name: Upload Cypress test results | |
| if: env.RUN_TESTS == 'true' && failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ matrix.batch.report_artifact_name }} | |
| path: | | |
| cypress-tests/cypress/reports/ | |
| retention-days: 1 | |
| mandatory-cypress-tests-consolidated-result: | |
| name: Mandatory Cypress Tests — Consolidated Result | |
| needs: [mandatory-cypress-tests] | |
| if: ${{ !cancelled() }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - name: Evaluate Matrix Results | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| RESULT="${{ needs.mandatory-cypress-tests.result }}" | |
| RUN_TESTS_VAL="${{ env.RUN_TESTS }}" | |
| echo "mandatory-cypress-tests result: $RESULT" | |
| echo "Run tests env: $RUN_TESTS_VAL" | |
| if [ "$RUN_TESTS_VAL" == "false" ]; then | |
| echo "Tests were skipped (Fork PR). Marking as success." | |
| exit 0 | |
| fi | |
| if [ "$RESULT" == "success" ]; then | |
| echo "All mandatory tests passed." | |
| exit 0 | |
| else | |
| echo "Mandatory tests failed." | |
| if [ "${{ github.event_name }}" != "merge_group" ]; then | |
| echo "Removing 'S-test-ready' label from the PR." | |
| gh pr edit ${{ github.event.pull_request.number }} --remove-label "S-test-ready" --repo ${{ github.repository }} | |
| fi | |
| exit 1 | |
| fi | |
| prepare-extended-matrix: | |
| name: Prepare Extended Connectors matrix to run cypress tests | |
| if: false | |
| runs-on: ubuntu-latest | |
| outputs: | |
| extended-matrix: ${{ steps.set-extended-matrix.outputs.matrix }} | |
| steps: | |
| - name: Set matrix for extended connectors batches | |
| id: set-extended-matrix | |
| shell: bash | |
| run: | | |
| { | |
| echo "matrix<<EOF" | |
| echo '{ | |
| "batch": [ | |
| { | |
| "name": "extended-payments-batch-1", | |
| "payments_connectors": "'"${EXTENDED_PAYMENTS_CONNECTORS_BATCH_1}"'", | |
| "payouts_connectors": "", | |
| "parallel": 4, | |
| "report_artifact_name": "cypress-extended-test-results-payments-1", | |
| "server_logs_artifact": "extended-server-logs-payments-1" | |
| }, | |
| { | |
| "name": "extended-payments-batch-2", | |
| "payments_connectors": "'"${EXTENDED_PAYMENTS_CONNECTORS_BATCH_2}"'", | |
| "payouts_connectors": "", | |
| "parallel": 3, | |
| "report_artifact_name": "cypress-extended-test-results-payments-2", | |
| "server_logs_artifact": "extended-server-logs-payments-2" | |
| }, | |
| { | |
| "name": "extended-payouts", | |
| "payments_connectors": "", | |
| "payouts_connectors": "'"${EXTENDED_PAYOUTS_CONNECTORS}"'", | |
| "parallel": 2, | |
| "report_artifact_name": "cypress-extended-test-results-payouts", | |
| "server_logs_artifact": "extended-server-logs-payouts" | |
| } | |
| ] | |
| }' | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| extended-cypress-tests: | |
| name: Run extended Cypress tests | |
| needs: [build-hyperswitch, prepare-extended-matrix] | |
| if: false | |
| runs-on: hyperswitch-runners | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.prepare-extended-matrix.outputs['extended-matrix']) }} | |
| env: | |
| # Use `sccache` for caching compilation artifacts | |
| RUSTC_WRAPPER: sccache | |
| services: | |
| redis: | |
| image: "public.ecr.aws/docker/library/redis:alpine" | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| postgres: | |
| image: "public.ecr.aws/docker/library/postgres:alpine" | |
| env: | |
| POSTGRES_USER: db_user | |
| POSTGRES_PASSWORD: db_pass | |
| POSTGRES_DB: hyperswitch_db | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: stable 2 weeks ago | |
| components: rustfmt | |
| - name: Install sccache | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: sccache | |
| checksum: true | |
| - name: Download hyperswitch server artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: hyperswitch-server | |
| path: target/debug | |
| - name: Make server executable | |
| run: chmod +x target/debug/router | |
| - name: Download Encrypted TOML from S3 and Decrypt | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.CONNECTOR_CREDS_AWS_ACCESS_KEY_ID }} | |
| AWS_REGION: ${{ secrets.CONNECTOR_CREDS_AWS_REGION }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CONNECTOR_CREDS_AWS_SECRET_ACCESS_KEY }} | |
| CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} | |
| CONNECTOR_CREDS_S3_BUCKET_URI: ${{ secrets.CONNECTOR_CREDS_S3_BUCKET_URI}} | |
| DESTINATION_FILE_NAME: "creds.json.gpg" | |
| S3_SOURCE_FILE_NAME: "b06ec672-ca83-405a-a834-856c3fe18dad.json.gpg" | |
| shell: bash | |
| run: | | |
| mkdir -p ".github/secrets" ".github/test" | |
| aws s3 cp "${CONNECTOR_CREDS_S3_BUCKET_URI}/${S3_SOURCE_FILE_NAME}" ".github/secrets/${DESTINATION_FILE_NAME}" | |
| gpg --quiet --batch --yes --decrypt --passphrase="${CONNECTOR_AUTH_PASSPHRASE}" --output ".github/test/creds.json" ".github/secrets/${DESTINATION_FILE_NAME}" | |
| - name: Set paths in env | |
| shell: bash | |
| run: | | |
| echo "CYPRESS_CONNECTOR_AUTH_FILE_PATH=${{ github.workspace }}/.github/test/creds.json" >> $GITHUB_ENV | |
| echo "SERVER_LOG_PATH=${{ github.workspace }}/server-logs.txt" >> $GITHUB_ENV | |
| - name: Fetch keys | |
| env: | |
| TOML_PATH: "./config/development.toml" | |
| run: | | |
| LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' ${TOML_PATH}) | |
| echo "CYPRESS_ADMINAPIKEY=${LOCAL_ADMIN_API_KEY}" >> $GITHUB_ENV | |
| - name: Install Diesel CLI | |
| uses: baptiste0928/cargo-install@v3.4.0 | |
| with: | |
| crate: diesel_cli | |
| features: postgres | |
| args: --no-default-features | |
| - name: Install Just | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: just | |
| checksum: true | |
| - name: Install Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| - name: Install Cypress and dependencies | |
| run: | | |
| npm ci --prefix ./cypress-tests | |
| - name: Run database migrations | |
| shell: bash | |
| env: | |
| DATABASE_URL: postgres://db_user:db_pass@localhost:5432/hyperswitch_db | |
| run: just migrate run --locked-schema | |
| - name: Insert card info into the database | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy cards_info FROM '.github/data/cards_info.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Insert GSM info into the database | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy gateway_status_map FROM '.github/data/gateway_status_map.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Setup Local Server | |
| env: | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC_KEY: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT_KEY: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE_KEY }} | |
| # Apply seeded Superposition config quickly in CI (default is 300s). | |
| ROUTER__SUPERPOSITION__POLLING_INTERVAL: 10 | |
| SERVER_LOG_PATH: ${{ github.workspace }}/server-logs.txt | |
| run: | | |
| # Start the server in the background | |
| target/debug/router >> "${{ env.SERVER_LOG_PATH }}" 2>&1 & | |
| SERVER_PID=$! | |
| echo "PID=${SERVER_PID}" >> $GITHUB_ENV | |
| # Wait for the server to start in port 8080 | |
| COUNT=0 | |
| while ! nc -z localhost 8080; do | |
| if [ $COUNT -gt 12 ]; then # Wait for up to 2 minutes (12 * 10 seconds) | |
| echo "Server did not start within a reasonable time. Exiting." | |
| kill ${SERVER_PID} | |
| exit 1 | |
| else | |
| COUNT=$((COUNT+1)) | |
| sleep 10 | |
| fi | |
| done | |
| - name: Run Cypress tests | |
| env: | |
| CYPRESS_BASEURL: "http://localhost:8080" | |
| ROUTER__SERVER__WORKERS: 4 | |
| PAYMENTS_CONNECTORS: "${{ matrix.batch.payments_connectors }}" | |
| PAYOUTS_CONNECTORS: "${{ matrix.batch.payouts_connectors }}" | |
| shell: bash -leuo pipefail {0} | |
| run: | | |
| scripts/execute_cypress.sh --parallel ${{ matrix.batch.parallel }} | |
| kill "${{ env.PID }}" | |
| - name: Upload server logs on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ matrix.batch.server_logs_artifact }} | |
| path: ${{ env.SERVER_LOG_PATH }} | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| - name: Upload Cypress test results | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ matrix.batch.report_artifact_name }} | |
| path: | | |
| cypress-tests/cypress/reports/ | |
| retention-days: 1 | |
| extended-cypress-tests-consolidated-result: | |
| name: Extended Cypress Tests — Consolidated Result | |
| needs: [extended-cypress-tests] | |
| runs-on: ubuntu-latest | |
| if: false | |
| steps: | |
| - name: Evaluate Matrix Results | |
| shell: bash | |
| run: | | |
| RESULT="${{ needs.extended-cypress-tests.result }}" | |
| echo "extended-cypress-tests result: $RESULT" | |
| if [ "$RESULT" == "success" ] || [ "$RESULT" == "skipped" ]; then | |
| echo "Extended cypress tests passed (or skipped upstream)." | |
| exit 0 | |
| else | |
| echo "Extended cypress tests failed." | |
| exit 1 | |
| fi | |
| prepare-mock-matrix: | |
| name: Prepare Mock Connectors matrix | |
| needs: [check-cypress-prerequisites] | |
| if: ${{ needs.check-cypress-prerequisites.outputs.run-checks == 'true' }} | |
| runs-on: ubuntu-latest | |
| outputs: | |
| mock-matrix: ${{ steps.set-mock-matrix.outputs.matrix }} | |
| steps: | |
| - name: Set matrix for mock connector batches | |
| id: set-mock-matrix | |
| shell: bash | |
| run: | | |
| { | |
| echo "matrix<<EOF" | |
| echo '{ | |
| "batch": [ | |
| { | |
| "name": "mock-payouts", | |
| "payments_connectors": "", | |
| "payouts_connectors": "'"${MOCK_PAYOUTS_CONNECTORS}"'", | |
| "platform_connectors": "", | |
| "parallel": 2, | |
| "report_artifact_name": "cypress-mock-test-results-payouts", | |
| "server_logs_artifact": "mock-server-logs-payouts" | |
| }, | |
| { | |
| "name": "mock-payments-batch-1", | |
| "payments_connectors": "'"${MOCK_PAYMENTS_CONNECTORS_BATCH_1}"'", | |
| "payouts_connectors": "", | |
| "platform_connectors": "", | |
| "parallel": 3, | |
| "report_artifact_name": "cypress-mock-test-results-payments-batch-1", | |
| "server_logs_artifact": "mock-server-logs-payments-batch-1" | |
| }, | |
| { | |
| "name": "mock-payments-batch-2", | |
| "payments_connectors": "'"${MOCK_PAYMENTS_CONNECTORS_BATCH_2}"'", | |
| "payouts_connectors": "", | |
| "platform_connectors": "", | |
| "parallel": 3, | |
| "report_artifact_name": "cypress-mock-test-results-payments-batch-2", | |
| "server_logs_artifact": "mock-server-logs-payments-batch-2" | |
| }, | |
| { | |
| "name": "mock-payments-batch-3", | |
| "payments_connectors": "'"${MOCK_PAYMENTS_CONNECTORS_BATCH_3}"'", | |
| "payouts_connectors": "", | |
| "platform_connectors": "", | |
| "parallel": 3, | |
| "report_artifact_name": "cypress-mock-test-results-payments-batch-3", | |
| "server_logs_artifact": "mock-server-logs-payments-batch-3" | |
| }, | |
| { | |
| "name": "mock-payments-batch-4", | |
| "payments_connectors": "'"${MOCK_PAYMENTS_CONNECTORS_BATCH_4}"'", | |
| "payouts_connectors": "", | |
| "platform_connectors": "", | |
| "parallel": 3, | |
| "report_artifact_name": "cypress-mock-test-results-payments-batch-4", | |
| "server_logs_artifact": "mock-server-logs-payments-batch-4" | |
| } | |
| ] | |
| }' | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| mitm-cassette-replay-cypress-tests: | |
| name: Run MITM mock Cypress tests (${{ matrix.batch.name }}) | |
| needs: [build-hyperswitch, prepare-mock-matrix] | |
| runs-on: ${{ case(github.event_name == 'merge_group', 'hyperswitch-runners-merge', 'hyperswitch-runners') }} | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.prepare-mock-matrix.outputs['mock-matrix']) }} | |
| env: | |
| RUSTC_WRAPPER: sccache | |
| services: | |
| redis: | |
| image: "public.ecr.aws/docker/library/redis:alpine" | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| postgres: | |
| image: "public.ecr.aws/docker/library/postgres:alpine" | |
| env: | |
| POSTGRES_USER: db_user | |
| POSTGRES_PASSWORD: db_pass | |
| POSTGRES_DB: hyperswitch_db | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| superposition: | |
| image: ghcr.io/juspay/superposition-demo:0.102.0 | |
| env: | |
| API_HOSTNAME: http://localhost:8081 | |
| REDIS_URL: redis://redis:6379 | |
| ports: | |
| - 8081:8080 | |
| steps: | |
| - name: Skip tests for PRs from forks | |
| if: ${{ env.RUN_TESTS == 'false' }} | |
| shell: bash | |
| run: echo 'Skipping tests for PRs from forks' | |
| - name: Checkout repository | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/checkout@v6 | |
| - name: Install Rust | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: stable 2 weeks ago | |
| components: rustfmt | |
| - name: Install sccache | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: sccache | |
| checksum: true | |
| - name: Download hyperswitch server artifact | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: hyperswitch-server | |
| path: target/debug | |
| - name: Make server executable | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: chmod +x target/debug/router | |
| - name: Download Encrypted TOML from S3 and Decrypt | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.CONNECTOR_CREDS_AWS_ACCESS_KEY_ID }} | |
| AWS_REGION: ${{ secrets.CONNECTOR_CREDS_AWS_REGION }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CONNECTOR_CREDS_AWS_SECRET_ACCESS_KEY }} | |
| CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} | |
| CONNECTOR_CREDS_S3_BUCKET_URI: ${{ secrets.CONNECTOR_CREDS_S3_BUCKET_URI }} | |
| DESTINATION_FILE_NAME: "creds.json.gpg" | |
| S3_SOURCE_FILE_NAME: "b06ec672-ca83-405a-a834-856c3fe18dad.json.gpg" | |
| shell: bash | |
| run: | | |
| mkdir -p ".github/secrets" ".github/test" | |
| aws s3 cp "${CONNECTOR_CREDS_S3_BUCKET_URI}/${S3_SOURCE_FILE_NAME}" ".github/secrets/${DESTINATION_FILE_NAME}" | |
| gpg --quiet --batch --yes --decrypt --passphrase="${CONNECTOR_AUTH_PASSPHRASE}" --output ".github/test/creds.json" ".github/secrets/${DESTINATION_FILE_NAME}" | |
| - name: Set paths in env | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| shell: bash | |
| run: | | |
| echo "CYPRESS_CONNECTOR_AUTH_FILE_PATH=${{ github.workspace }}/.github/test/creds.json" >> $GITHUB_ENV | |
| echo "SERVER_LOG_PATH=${{ github.workspace }}/server-logs.txt" >> $GITHUB_ENV | |
| - name: Fetch keys | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| TOML_PATH: "./config/development.toml" | |
| run: | | |
| LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' ${TOML_PATH}) | |
| echo "CYPRESS_ADMINAPIKEY=${LOCAL_ADMIN_API_KEY}" >> $GITHUB_ENV | |
| - name: Install Diesel CLI | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: baptiste0928/cargo-install@v3.4.0 | |
| with: | |
| crate: diesel_cli | |
| features: postgres | |
| args: --no-default-features | |
| - name: Install Just | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: just | |
| checksum: true | |
| - name: Install Node.js | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| - name: Install Cypress and dependencies | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| npm ci --prefix ./cypress-tests | |
| - name: Run database migrations | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| shell: bash | |
| env: | |
| DATABASE_URL: postgres://db_user:db_pass@localhost:5432/hyperswitch_db | |
| run: just migrate run --locked-schema | |
| - name: Insert card info into the database | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy cards_info FROM '.github/data/cards_info.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Insert GSM info into the database | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy gateway_status_map FROM '.github/data/gateway_status_map.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Seed Superposition | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| SUPERPOSITION_URL: "http://localhost:8081" | |
| SEED_FILE: "./config/superposition_seed.toml" | |
| WORKSPACE_ID: "dev" | |
| ORG_ID: "localorg" | |
| run: bash scripts/seed_superposition.sh | |
| - name: Download cassettes from S3 | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.CONNECTOR_CREDS_AWS_ACCESS_KEY_ID }} | |
| AWS_REGION: ${{ secrets.CONNECTOR_CREDS_AWS_REGION }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CONNECTOR_CREDS_AWS_SECRET_ACCESS_KEY }} | |
| CONNECTOR_MOCK_DATA_S3_BUCKET_URI: ${{ secrets.CONNECTOR_MOCK_DATA_S3_BUCKET_URI }} | |
| CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} | |
| shell: bash | |
| run: | | |
| # S3 layout: {bucket}/{connector}/{type}.tar.gz.gpg | |
| # Each tarball must extract its files under {connector}/... so the | |
| # replay store finds them at cypress-tests/mitm-proxy/captures/{connector}/. | |
| # We accept tarballs created either way (with or without a leading | |
| # {connector}/ component) and normalise the on-disk layout here so | |
| # a malformed tarball doesn't silently produce zero cassettes. | |
| set -euo pipefail | |
| mkdir -p "cypress-tests/mitm-proxy/captures" | |
| fetch_cassettes() { | |
| local connector="$1" | |
| local type="$2" | |
| local src="${CONNECTOR_MOCK_DATA_S3_BUCKET_URI}/${connector}/${type}.tar.gz.gpg" | |
| local gpg_path="/tmp/${connector}-${type}-cassettes.tar.gz.gpg" | |
| local tar_path="/tmp/${connector}-${type}-cassettes.tar.gz" | |
| local dest="cypress-tests/mitm-proxy/captures/${connector}" | |
| echo "→ ${connector}/${type}: ${src}" | |
| aws s3 cp "${src}" "${gpg_path}" | |
| echo " encrypted size: $(stat -c %s "${gpg_path}" 2>/dev/null || stat -f %z "${gpg_path}") bytes" | |
| gpg --quiet --batch --yes --decrypt \ | |
| --passphrase="${CONNECTOR_AUTH_PASSPHRASE}" \ | |
| --output "${tar_path}" "${gpg_path}" | |
| echo " decrypted size: $(stat -c %s "${tar_path}" 2>/dev/null || stat -f %z "${tar_path}") bytes" | |
| # Stage into a temp dir so we can normalise the prefix structure | |
| # before placing files under captures/{connector}/. | |
| local stage | |
| stage="$(mktemp -d)" | |
| tar -xzf "${tar_path}" -C "${stage}" | |
| echo " tarball top-level entries: $(ls -A "${stage}" | tr '\n' ' ')" | |
| mkdir -p "${dest}" | |
| # If the tarball wrapped its contents in {connector}/, strip that | |
| # one level. Otherwise treat the tarball root as already being | |
| # {connector}/'s contents. | |
| if [ -d "${stage}/${connector}" ]; then | |
| cp -R "${stage}/${connector}/." "${dest}/" | |
| else | |
| cp -R "${stage}/." "${dest}/" | |
| fi | |
| rm -rf "${stage}" | |
| local count | |
| count="$(find "${dest}" -name '*.json' | wc -l | tr -d ' ')" | |
| echo " extracted: ${count} cassettes → ${dest}/" | |
| if [ "${count}" -eq 0 ]; then | |
| echo "::error::No cassettes for ${connector}/${type}. Tarball is empty or has an unexpected internal layout; re-record and re-upload." | |
| return 1 | |
| fi | |
| } | |
| for connector in ${{ matrix.batch.platform_connectors }}; do | |
| [ -z "${connector}" ] && continue | |
| fetch_cassettes "${connector}" "platform" | |
| done | |
| for connector in ${{ matrix.batch.payments_connectors }}; do | |
| [ -z "${connector}" ] && continue | |
| fetch_cassettes "${connector}" "payment" | |
| done | |
| for connector in ${{ matrix.batch.payouts_connectors }}; do | |
| [ -z "${connector}" ] && continue | |
| fetch_cassettes "${connector}" "payout" | |
| done | |
| - name: Install uv | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: astral-sh/setup-uv@v8.3.2 | |
| - name: Install mitmproxy dependencies | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| shell: bash | |
| env: | |
| # Use the runner's OS trust store instead of uv's bundled webpki roots. | |
| # Required when the runner sits behind a TLS-intercepting proxy whose | |
| # internal CA is installed system-wide but not in uv's static bundle. | |
| UV_SYSTEM_CERTS: "true" | |
| run: | | |
| # Create an isolated venv for mitmproxy so we don't touch the | |
| # runner's system Python (PEP 668 / externally-managed-environment). | |
| uv venv --system-certs "${{ github.workspace }}/.venv-mitm" | |
| uv pip install --system-certs --python "${{ github.workspace }}/.venv-mitm/bin/python" \ | |
| -r cypress-tests/mitm-proxy/requirements.txt | |
| # Expose the venv to subsequent steps (mitmdump cert gen, start_replay.sh). | |
| echo "${{ github.workspace }}/.venv-mitm/bin" >> "$GITHUB_PATH" | |
| "${{ github.workspace }}/.venv-mitm/bin/mitmdump" --version | |
| - name: Generate mitmproxy CA cert | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| shell: bash | |
| run: | | |
| timeout 5 mitmdump --listen-port 18888 >/tmp/mitm-certgen.log 2>&1 || true | |
| test -f "$HOME/.mitmproxy/mitmproxy-ca-cert.pem" | |
| - name: Setup Local Server | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC_KEY: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT_KEY: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE_KEY }} | |
| # Apply seeded Superposition config quickly in CI (default is 300s). | |
| ROUTER__SUPERPOSITION__POLLING_INTERVAL: 10 | |
| ROUTER__PROXY__HTTPS_URL: "http://127.0.0.1:8888" | |
| ROUTER__PROXY__HTTP_URL: "http://127.0.0.1:8888" | |
| ROUTER__PROXY__MITM_ENABLED: "true" | |
| ROUTER__TRACE_HEADER__ID_REUSE_STRATEGY: "use_incoming" | |
| ROUTER__SERVER__WORKERS: 4 | |
| SERVER_LOG_PATH: ${{ github.workspace }}/server-logs.txt | |
| RUST_MIN_STACK: 134217728 | |
| shell: bash | |
| run: | | |
| MITM_CERT=$(sed 's/$/\\r\\n/' "$HOME/.mitmproxy/mitmproxy-ca-cert.pem" | tr -d '\n') | |
| export ROUTER__PROXY__MITM_CA_CERTIFICATE="$MITM_CERT" | |
| target/debug/router >> "${SERVER_LOG_PATH}" 2>&1 & | |
| SERVER_PID=$! | |
| echo "PID=${SERVER_PID}" >> $GITHUB_ENV | |
| COUNT=0 | |
| while ! nc -z localhost 8080; do | |
| if [ $COUNT -gt 12 ]; then | |
| echo "Server did not start within a reasonable time. Exiting." | |
| kill ${SERVER_PID} | |
| exit 1 | |
| fi | |
| COUNT=$((COUNT+1)) | |
| sleep 10 | |
| done | |
| - name: Start mitmproxy replay | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| shell: bash | |
| env: | |
| CAPTURE_DIR: ${{ github.workspace }}/cypress-tests/mitm-proxy/captures | |
| ADMIN_PORT: "8001" | |
| PROXY_PORT: "8888" | |
| run: | | |
| nohup bash cypress-tests/mitm-proxy/start_replay.sh \ | |
| > "${{ github.workspace }}/mitm-replay.log" 2>&1 & | |
| MITM_PID=$! | |
| echo "MITM_PID=${MITM_PID}" >> $GITHUB_ENV | |
| COUNT=0 | |
| while ! nc -z localhost 8888; do | |
| if [ $COUNT -gt 15 ]; then | |
| echo "mitmdump did not start within a reasonable time. Exiting." | |
| kill ${MITM_PID} | |
| exit 1 | |
| fi | |
| COUNT=$((COUNT+1)) | |
| sleep 2 | |
| done | |
| echo "mitmproxy replay started (PID=${MITM_PID})" | |
| - name: Run Cypress tests | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| CYPRESS_BASEURL: "http://localhost:8080" | |
| PAYMENTS_CONNECTORS: "${{ matrix.batch.payments_connectors }}" | |
| PAYOUTS_CONNECTORS: "${{ matrix.batch.payouts_connectors }}" | |
| PLATFORM_CONNECTORS: "${{ matrix.batch.platform_connectors }}" | |
| CYPRESS_MOCK_SERVER: "true" | |
| CYPRESS_IS_PROXY_ENABLED: "true" | |
| # Notifies the MITM proxy of each test start/end so test names appear | |
| # in the replay log alongside cassette hits/misses (logging only). | |
| CYPRESS_PROXY_ADMIN_URL: "http://127.0.0.1:8001" | |
| CYPRESS_CAPTURE_DIR: "${{ github.workspace }}/cypress-tests/mitm-proxy/captures" | |
| shell: bash -leuo pipefail {0} | |
| run: | | |
| scripts/execute_cypress.sh --parallel ${{ matrix.batch.parallel }} | |
| kill "${{ env.PID }}" | |
| kill "${{ env.MITM_PID }}" 2>/dev/null || true | |
| - name: Upload MITM replay logs on failure | |
| if: env.RUN_TESTS == 'true' && failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ matrix.batch.server_logs_artifact }}-mitm-replay | |
| path: ${{ github.workspace }}/mitm-replay.log | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| - name: Upload server logs on failure | |
| if: env.RUN_TESTS == 'true' && failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ matrix.batch.server_logs_artifact }} | |
| path: ${{ env.SERVER_LOG_PATH }} | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| - name: Upload Cypress test results | |
| if: env.RUN_TESTS == 'true' && failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ matrix.batch.report_artifact_name }} | |
| path: | | |
| cypress-tests/cypress/reports/ | |
| retention-days: 1 | |
| mitm-cassette-replay-cypress-tests-consolidated-result: | |
| name: MITM Mock Cypress Tests — Consolidated Result | |
| needs: [mitm-cassette-replay-cypress-tests] | |
| if: ${{ !cancelled() }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - name: Evaluate Matrix Results | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| RESULT="${{ needs.mitm-cassette-replay-cypress-tests.result }}" | |
| RUN_TESTS_VAL="${{ env.RUN_TESTS }}" | |
| echo "mitm-cassette-replay-cypress-tests result: $RESULT" | |
| echo "Run tests env: $RUN_TESTS_VAL" | |
| if [ "$RUN_TESTS_VAL" == "false" ]; then | |
| echo "Tests were skipped (Fork PR). Marking as success." | |
| exit 0 | |
| fi | |
| if [ "$RESULT" == "success" ]; then | |
| echo "All MITM mock tests passed." | |
| exit 0 | |
| else | |
| echo "MITM mock tests failed." | |
| if [ "${{ github.event_name }}" != "merge_group" ]; then | |
| echo "Removing 'S-test-ready' label from the PR." | |
| gh pr edit ${{ github.event.pull_request.number }} --remove-label "S-test-ready" --repo ${{ github.repository }} | |
| fi | |
| exit 1 | |
| fi | |
| prepare-optional-matrix: | |
| name: Prepare Optional Connectors matrix to run cypress tests | |
| if: false | |
| runs-on: hyperswitch-runners | |
| outputs: | |
| matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| steps: | |
| - name: Set matrix for optional connectors batches | |
| id: set-matrix | |
| shell: bash | |
| run: | | |
| { | |
| echo "matrix<<EOF" | |
| echo '{ | |
| "batch": [ | |
| { | |
| "name": "batch-1", | |
| "connectors": "'"${OPTIONAL_PAYMENTS_CONNECTORS_BATCH_1}"'", | |
| "parallel": 6, | |
| "report_artifact_name": "cypress-test-results-batch-1", | |
| "server_logs_artifact": "optional-server-logs-batch-1" | |
| }, | |
| { | |
| "name": "batch-2", | |
| "connectors": "'"${OPTIONAL_PAYMENTS_CONNECTORS_BATCH_2}"'", | |
| "parallel": 6, | |
| "report_artifact_name": "cypress-test-results-batch-2", | |
| "server_logs_artifact": "optional-server-logs-batch-2" | |
| } | |
| ] | |
| }' | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| runner-optional-connectors: | |
| name: Run Optional Connectors Cypress tests | |
| if: false | |
| needs: [build-hyperswitch, prepare-optional-matrix] | |
| runs-on: hyperswitch-runners | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.prepare-optional-matrix.outputs.matrix) }} | |
| env: | |
| # Use `sccache` for caching compilation artifacts | |
| RUSTC_WRAPPER: sccache | |
| services: | |
| redis: | |
| image: "public.ecr.aws/docker/library/redis:alpine" | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| postgres: | |
| image: "public.ecr.aws/docker/library/postgres:alpine" | |
| env: | |
| POSTGRES_USER: db_user | |
| POSTGRES_PASSWORD: db_pass | |
| POSTGRES_DB: hyperswitch_db | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| superposition: | |
| image: ghcr.io/juspay/superposition-demo:0.102.0 | |
| env: | |
| API_HOSTNAME: http://localhost:8081 | |
| REDIS_URL: redis://redis:6379 | |
| ports: | |
| - 8081:8080 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: stable 2 weeks ago | |
| components: rustfmt | |
| - name: Install sccache | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: sccache | |
| checksum: true | |
| - name: Download hyperswitch server artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: hyperswitch-server | |
| path: target/debug | |
| - name: Make server executable | |
| run: chmod +x target/debug/router | |
| - name: Download Encrypted TOML from S3 and Decrypt | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.CONNECTOR_CREDS_AWS_ACCESS_KEY_ID }} | |
| AWS_REGION: ${{ secrets.CONNECTOR_CREDS_AWS_REGION }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CONNECTOR_CREDS_AWS_SECRET_ACCESS_KEY }} | |
| CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} | |
| CONNECTOR_CREDS_S3_BUCKET_URI: ${{ secrets.CONNECTOR_CREDS_S3_BUCKET_URI}} | |
| DESTINATION_FILE_NAME: "creds.json.gpg" | |
| S3_SOURCE_FILE_NAME: "b06ec672-ca83-405a-a834-856c3fe18dad.json.gpg" | |
| shell: bash | |
| run: | | |
| mkdir -p ".github/secrets" ".github/test" | |
| aws s3 cp "${CONNECTOR_CREDS_S3_BUCKET_URI}/${S3_SOURCE_FILE_NAME}" ".github/secrets/${DESTINATION_FILE_NAME}" | |
| gpg --quiet --batch --yes --decrypt --passphrase="${CONNECTOR_AUTH_PASSPHRASE}" --output ".github/test/creds.json" ".github/secrets/${DESTINATION_FILE_NAME}" | |
| - name: Set paths in env | |
| shell: bash | |
| run: | | |
| echo "CYPRESS_CONNECTOR_AUTH_FILE_PATH=${{ github.workspace }}/.github/test/creds.json" >> $GITHUB_ENV | |
| echo "SERVER_LOG_PATH=${{ github.workspace }}/server-logs.txt" >> $GITHUB_ENV | |
| - name: Fetch keys | |
| env: | |
| TOML_PATH: "./config/development.toml" | |
| run: | | |
| LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' ${TOML_PATH}) | |
| echo "CYPRESS_ADMINAPIKEY=${LOCAL_ADMIN_API_KEY}" >> $GITHUB_ENV | |
| - name: Install Diesel CLI | |
| uses: baptiste0928/cargo-install@v3.4.0 | |
| with: | |
| crate: diesel_cli | |
| features: postgres | |
| args: --no-default-features | |
| - name: Install Just | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: just | |
| checksum: true | |
| - name: Install Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| - name: Install Cypress and dependencies | |
| run: | | |
| npm ci --prefix ./cypress-tests | |
| - name: Run database migrations | |
| shell: bash | |
| env: | |
| DATABASE_URL: postgres://db_user:db_pass@localhost:5432/hyperswitch_db | |
| run: just migrate run --locked-schema | |
| - name: Insert card info into the database | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy cards_info FROM '.github/data/cards_info.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Insert GSM info into the database | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy gateway_status_map FROM '.github/data/gateway_status_map.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Seed Superposition | |
| env: | |
| SUPERPOSITION_URL: "http://localhost:8081" | |
| SEED_FILE: "./config/superposition_seed.toml" | |
| WORKSPACE_ID: "dev" | |
| ORG_ID: "localorg" | |
| run: bash scripts/seed_superposition.sh | |
| - name: Setup Local Server | |
| env: | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC_KEY: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT_KEY: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE_KEY }} | |
| # Apply seeded Superposition config quickly in CI (default is 300s). | |
| ROUTER__SUPERPOSITION__POLLING_INTERVAL: 10 | |
| run: | | |
| touch "${{ env.SERVER_LOG_PATH }}" | |
| # Start the server in the background | |
| target/debug/router >> "${{ env.SERVER_LOG_PATH }}" 2>&1 & | |
| SERVER_PID=$! | |
| echo "PID=${SERVER_PID}" >> $GITHUB_ENV | |
| # Wait for the server to start in port 8080 | |
| COUNT=0 | |
| while ! nc -z localhost 8080; do | |
| if [ $COUNT -gt 12 ]; then # Wait for up to 2 minutes (12 * 10 seconds) | |
| echo "Server did not start within a reasonable time. Exiting." | |
| kill ${SERVER_PID} | |
| exit 1 | |
| else | |
| COUNT=$((COUNT+1)) | |
| sleep 10 | |
| fi | |
| done | |
| - name: Run Cypress tests | |
| env: | |
| CYPRESS_BASEURL: "http://localhost:8080" | |
| ROUTER__SERVER__WORKERS: 4 | |
| PAYMENTS_CONNECTORS: "${{ matrix.batch.connectors }}" | |
| shell: bash -leuo pipefail {0} | |
| run: | | |
| scripts/execute_cypress.sh --parallel ${{ matrix.batch.parallel }} | |
| kill "${{ env.PID }}" | |
| - name: Upload server logs on failure | |
| if: env.RUN_TESTS == 'true' && failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ matrix.batch.server_logs_artifact }} | |
| path: ${{ env.SERVER_LOG_PATH }} | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| - name: Upload Cypress test results | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ matrix.batch.report_artifact_name }} | |
| path: | | |
| cypress-tests/cypress/reports/ | |
| retention-days: 1 | |
| runner_alpha: | |
| name: Run optional Cypress tests for alpha connectors | |
| needs: build-hyperswitch | |
| if: false | |
| runs-on: hyperswitch-runners | |
| env: | |
| # Use `sccache` for caching compilation artifacts | |
| RUSTC_WRAPPER: sccache | |
| services: | |
| redis: | |
| image: "public.ecr.aws/docker/library/redis:alpine" | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| postgres: | |
| image: "public.ecr.aws/docker/library/postgres:alpine" | |
| env: | |
| POSTGRES_USER: db_user | |
| POSTGRES_PASSWORD: db_pass | |
| POSTGRES_DB: hyperswitch_db | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| superposition: | |
| image: ghcr.io/juspay/superposition-demo:0.102.0 | |
| env: | |
| API_HOSTNAME: http://localhost:8081 | |
| REDIS_URL: redis://redis:6379 | |
| ports: | |
| - 8081:8080 | |
| steps: | |
| - name: Skip tests for PRs from forks | |
| if: ${{ env.RUN_TESTS == 'false' }} | |
| shell: bash | |
| run: echo 'Skipping tests for PRs from forks' | |
| - name: Checkout repository | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/checkout@v6 | |
| - name: Download hyperswitch server artifact | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: hyperswitch-server | |
| path: target/debug | |
| - name: Make server executable | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: chmod +x target/debug/router | |
| - name: Set paths in env | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| shell: bash | |
| run: | | |
| echo "CYPRESS_CONNECTOR_AUTH_FILE_PATH=${{ github.workspace }}/cypress-tests/cypress/e2e/configs/mock-server/Creds.json" >> $GITHUB_ENV | |
| - name: Fetch keys | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| TOML_PATH: "./config/development.toml" | |
| run: | | |
| LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' ${TOML_PATH}) | |
| echo "CYPRESS_ADMINAPIKEY=${LOCAL_ADMIN_API_KEY}" >> $GITHUB_ENV | |
| - name: Install Rust | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: stable 2 weeks ago | |
| components: rustfmt | |
| - name: Install sccache | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: sccache | |
| checksum: true | |
| - name: Install Diesel CLI | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: baptiste0928/cargo-install@v3.4.0 | |
| with: | |
| crate: diesel_cli | |
| features: postgres | |
| args: --no-default-features | |
| - name: Install Just | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: just | |
| checksum: true | |
| - name: Install Node.js | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| - name: Install Cypress and dependencies | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| npm ci --prefix ./cypress-tests | |
| - name: Run database migrations | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| shell: bash | |
| env: | |
| DATABASE_URL: postgres://db_user:db_pass@localhost:5432/hyperswitch_db | |
| run: just migrate run --locked-schema | |
| - name: Insert card info into the database | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy cards_info FROM '.github/data/cards_info.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Insert GSM info into the database | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy gateway_status_map FROM '.github/data/gateway_status_map.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Seed Superposition | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| SUPERPOSITION_URL: "http://localhost:8081" | |
| SEED_FILE: "./config/superposition_seed.toml" | |
| WORKSPACE_ID: "dev" | |
| ORG_ID: "localorg" | |
| run: bash scripts/seed_superposition.sh | |
| - name: Start mock servers for alpha connectors | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| echo "Starting mock server for ${connector}" | |
| cd cypress-tests | |
| npm run mockserver & | |
| MOCK_PID=$! | |
| echo "MOCK_PID=${MOCK_PID}" >> $GITHUB_ENV | |
| cd .. | |
| # Wait for mock server to be ready | |
| COUNT=0 | |
| while ! nc -z localhost 3010; do | |
| if [ $COUNT -gt 6 ]; then # Wait for up to 1 minute (6 * 10 seconds) | |
| echo "Mock server for ${connector} did not start within a reasonable time." | |
| exit 1 | |
| else | |
| COUNT=$((COUNT+1)) | |
| sleep 10 | |
| fi | |
| done | |
| echo "Mock server for ${connector} is ready" | |
| - name: Setup Local Server with Alpha Connector Configs | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC_KEY: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE }} | |
| ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT_KEY: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE_KEY }} | |
| # Apply seeded Superposition config quickly in CI (default is 300s). | |
| ROUTER__SUPERPOSITION__POLLING_INTERVAL: 10 | |
| run: | | |
| # Set base URLs for alpha connectors to point to mock servers | |
| for connector in ${{ env.ALPHA_PAYMENTS_CONNECTORS }}; do | |
| CONNECTOR_ENV_VAR="ROUTER__CONNECTORS__$(echo ${connector} | tr '[:lower:]' '[:upper:]')__BASE_URL" | |
| export ${CONNECTOR_ENV_VAR}="http://localhost:3010/${connector}" | |
| echo "${CONNECTOR_ENV_VAR}=http://localhost:3010/${connector}" >> $GITHUB_ENV | |
| done | |
| # Start the server in the background | |
| target/debug/router & | |
| SERVER_PID=$! | |
| echo "PID=${SERVER_PID}" >> $GITHUB_ENV | |
| # Wait for the server to start in port 8080 | |
| COUNT=0 | |
| while ! nc -z localhost 8080; do | |
| if [ $COUNT -gt 12 ]; then # Wait for up to 2 minutes (12 * 10 seconds) | |
| echo "Server did not start within a reasonable time. Exiting." | |
| kill ${SERVER_PID} | |
| exit 1 | |
| else | |
| COUNT=$((COUNT+1)) | |
| sleep 10 | |
| fi | |
| done | |
| - name: Run Cypress tests for Alpha Connectors | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| CYPRESS_BASEURL: "http://localhost:8080" | |
| ROUTER__SERVER__WORKERS: 4 | |
| PAYMENTS_CONNECTORS: ${{ env.ALPHA_PAYMENTS_CONNECTORS }} | |
| shell: bash -leuo pipefail {0} | |
| run: | | |
| # Run tests for alpha connectors | |
| scripts/execute_cypress.sh | |
| # Cleanup | |
| kill "${{ env.PID }}" | |
| kill "${{ env.MOCK_PID }}" | |
| - name: Upload Cypress test results for Alpha Connectors | |
| if: env.RUN_TESTS == 'true' && failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: cypress-test-results-alpha | |
| path: | | |
| cypress-tests/cypress/reports/ | |
| retention-days: 1 | |
| runner_v2: | |
| name: Run Cypress tests on v2 and generate coverage report | |
| needs: [check-cypress-prerequisites] | |
| if: ${{ needs.check-cypress-prerequisites.outputs.run-checks == 'true' }} | |
| runs-on: hyperswitch-runners | |
| permissions: | |
| pull-requests: write | |
| contents: read | |
| env: | |
| CODECOV_FILE: "lcov.info" | |
| # Use `sccache` for caching compilation artifacts | |
| RUSTC_WRAPPER: sccache | |
| services: | |
| redis: | |
| image: "public.ecr.aws/docker/library/redis:alpine" | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| postgres: | |
| image: "public.ecr.aws/docker/library/postgres:alpine" | |
| env: | |
| POSTGRES_USER: db_user | |
| POSTGRES_PASSWORD: db_pass | |
| POSTGRES_DB: hyperswitch_db | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| superposition: | |
| image: ghcr.io/juspay/superposition-demo:0.102.0 | |
| env: | |
| API_HOSTNAME: http://localhost:8081 | |
| REDIS_URL: redis://redis:6379 | |
| ports: | |
| - 8081:8080 | |
| steps: | |
| - name: Skip tests for PRs from forks | |
| shell: bash | |
| if: ${{ env.RUN_TESTS == 'false' }} | |
| run: echo 'Skipping tests for PRs from forks' | |
| - name: Checkout repository | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/checkout@v6 | |
| - name: Download Encrypted TOML from S3 and Decrypt | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.CONNECTOR_CREDS_AWS_ACCESS_KEY_ID }} | |
| AWS_REGION: ${{ secrets.CONNECTOR_CREDS_AWS_REGION }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CONNECTOR_CREDS_AWS_SECRET_ACCESS_KEY }} | |
| CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} | |
| CONNECTOR_CREDS_S3_BUCKET_URI: ${{ secrets.CONNECTOR_CREDS_S3_BUCKET_URI}} | |
| DESTINATION_FILE_NAME: "creds.json.gpg" | |
| S3_SOURCE_FILE_NAME: "b06ec672-ca83-405a-a834-856c3fe18dad.json.gpg" | |
| shell: bash | |
| run: | | |
| mkdir -p ".github/secrets" ".github/test" | |
| aws s3 cp "${CONNECTOR_CREDS_S3_BUCKET_URI}/${S3_SOURCE_FILE_NAME}" ".github/secrets/${DESTINATION_FILE_NAME}" | |
| gpg --quiet --batch --yes --decrypt --passphrase="${CONNECTOR_AUTH_PASSPHRASE}" --output ".github/test/creds.json" ".github/secrets/${DESTINATION_FILE_NAME}" | |
| - name: Set paths in env | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| shell: bash | |
| run: | | |
| echo "CYPRESS_CONNECTOR_AUTH_FILE_PATH=${{ github.workspace }}/.github/test/creds.json" >> $GITHUB_ENV | |
| - name: Fetch keys | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| TOML_PATH: "./config/development.toml" | |
| run: | | |
| LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' ${TOML_PATH}) | |
| echo "CYPRESS_ADMINAPIKEY=${LOCAL_ADMIN_API_KEY}" >> $GITHUB_ENV | |
| - name: Install mold linker | |
| if: ${{ runner.os == 'Linux' && env.RUN_TESTS == 'true' }} | |
| uses: rui314/setup-mold@v1 | |
| with: | |
| make-default: true | |
| - name: Install Rust | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: stable 2 weeks ago | |
| components: rustfmt | |
| - name: Install Protoc | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: arduino/setup-protoc@v3 | |
| with: | |
| repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Install sccache | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: sccache | |
| checksum: true | |
| - name: Install Diesel CLI | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: baptiste0928/cargo-install@v3.4.0 | |
| with: | |
| crate: diesel_cli | |
| features: postgres | |
| args: --no-default-features | |
| - name: Install grcov | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: grcov | |
| checksum: true | |
| - name: Install Just | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: just | |
| checksum: true | |
| - name: Install Node.js | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| - name: Install Cypress and dependencies | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| npm ci --prefix ./cypress-tests-v2 | |
| - name: Run database migrations | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| shell: bash | |
| env: | |
| DATABASE_URL: postgres://db_user:db_pass@localhost:5432/hyperswitch_db | |
| run: just migrate_v2 run --locked-schema | |
| - name: Insert card info into the database | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy cards_info FROM '.github/data/cards_info.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Insert GSM info into the database | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| run: | | |
| PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy gateway_status_map FROM '.github/data/gateway_status_map.csv' DELIMITER ',' CSV HEADER;" | |
| - name: Seed Superposition | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| SUPERPOSITION_URL: "http://localhost:8081" | |
| SEED_FILE: "./config/superposition_seed.toml" | |
| WORKSPACE_ID: "dev" | |
| ORG_ID: "localorg" | |
| run: bash scripts/seed_superposition.sh | |
| - name: Build project | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| RUSTFLAGS: "-Cinstrument-coverage" | |
| CARGO_BUILD_JOBS: 6 | |
| run: just build_v2 | |
| - name: Setup Local Server | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| LLVM_PROFILE_FILE: "coverage.profraw" | |
| # Apply seeded Superposition config quickly in CI (default is 300s). | |
| ROUTER__SUPERPOSITION__POLLING_INTERVAL: 10 | |
| run: | | |
| # Start the server in the background | |
| target/debug/router & | |
| SERVER_PID=$! | |
| echo "PID=${SERVER_PID}" >> $GITHUB_ENV | |
| # Wait for the server to start in port 8080 | |
| COUNT=0 | |
| while ! nc -z localhost 8080; do | |
| if [ $COUNT -gt 12 ]; then # Wait for up to 2 minutes (12 * 10 seconds) | |
| echo "Server did not start within a reasonable time. Exiting." | |
| kill ${SERVER_PID} | |
| exit 1 | |
| else | |
| COUNT=$((COUNT+1)) | |
| sleep 10 | |
| fi | |
| done | |
| - name: Run Cypress tests | |
| if: ${{ env.RUN_TESTS == 'true' }} | |
| env: | |
| CYPRESS_BASEURL: "http://localhost:8080" | |
| ROUTER__SERVER__WORKERS: 4 | |
| PAYMENTS_CONNECTORS: ${{ env.PAYMENTS_CONNECTORS }} | |
| shell: bash -leuo pipefail {0} | |
| # We aren't specifying `command` and `jobs` arguments currently | |
| run: scripts/execute_cypress.sh "" "" "cypress-tests-v2" | |
| - name: Stop running server | |
| if: ${{ env.RUN_TESTS == 'true' && (success() || failure()) }} | |
| run: | | |
| kill "${{ env.PID }}" | |
| - name: Upload Cypress test results | |
| if: env.RUN_TESTS == 'true' && failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: cypress-v2-test-results | |
| path: | | |
| cypress-tests-v2/cypress/reports/ | |
| retention-days: 1 | |
| # Notes: | |
| # - The `router` process must be killed (using SIGINT/SIGTERM) to generate the `coverage.profraw` file, otherwise the coverage will only be generated for the buildscripts | |
| # - Trying to generate branch coverage using "-Z coverage-options=branch" currently fails. Both grcov and cargo-llvm-cov crash when trying | |
| # to process the generated `.profraw` files. | |
| # - --keep-only argument is used to exclude external crates in generated lcov.info file (~500MiB -> ~70MiB) | |
| - name: Process coverage report | |
| if: ${{ env.RUN_TESTS == 'true' && github.event_name != 'merge_group' && (success() || failure()) }} | |
| run: grcov . --source-dir . --output-types lcov --output-path ${{ env.CODECOV_FILE }} --binary-path ./target/debug --keep-only "crates/*" | |
| - name: Upload coverage reports to Codecov | |
| uses: codecov/codecov-action@v6 | |
| if: ${{ env.RUN_TESTS == 'true' && github.event_name != 'merge_group' && (success() || failure()) }} | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: ${{ env.CODECOV_FILE }} | |
| disable_search: true |