Skip to content

feat(connector): add Ilixium routing via unified connector service #39345

feat(connector): add Ilixium routing via unified connector service

feat(connector): add Ilixium routing via unified connector service #39345

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