Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fca742d
test: add v1/v2 auto-adaptive protocol tests for Python and TypeScrip…
Hades-Ye Mar 10, 2026
f9898a7
fix: pass assets field through buildPaymentRequirementsFromOptions
Hades-Ye Mar 10, 2026
0d5013e
chore: remove Go facilitator from e2e tests
Hades-Ye Mar 11, 2026
043886d
fix: defer env validation until after scenario filtering in e2e test …
Hades-Ye Mar 11, 2026
f9e1d79
test: switch e2e EVM testnet from Base Sepolia to BSC Testnet with DH…
Hades-Ye Mar 11, 2026
69008f9
chore: gitignore vim swap files
Hades-Ye Mar 11, 2026
3cb1ce2
test: make SVM optional in e2e tests and add GitHub Actions workflow
Hades-Ye Mar 11, 2026
19542bf
test: make SVM optional and fix Python imports in E2E tests
Hades-Ye Mar 11, 2026
ba2dc87
feat: propagate EVM_RPC_URL to e2e clients and optimize python facili…
Hades-Ye Mar 11, 2026
46f121b
chore: update e2e client lock files
Hades-Ye Mar 11, 2026
a2cda19
chore: ignore e2e test results and local env files
Hades-Ye Mar 11, 2026
07e40c4
chore: resolve merge conflicts with dev/v2
Hades-Ye Mar 11, 2026
81c44ed
chore: update typescript lockfile to resolve CI failure
Hades-Ye Mar 11, 2026
7edcf67
chore: resolve CI failure
Hades-Ye Mar 11, 2026
a10aabc
fix(e2e): skip permit2 tests for python facilitator due to lack of su…
Hades-Ye Mar 11, 2026
ad99982
fix(e2e): resolve package renaming and module resolution issues in ne…
Hades-Ye Mar 11, 2026
7586b98
fix(e2e): configure DHLU token in next server proxy for BSC Testnet t…
Hades-Ye Mar 11, 2026
47493d9
fix(e2e): correct DHLU token decimals to 6 and use explicit asset inj…
Hades-Ye Mar 11, 2026
53a3572
feat(legacy): add bsc-testnet network support to legacy SDK
Hades-Ye Mar 11, 2026
dfcfead
feat(legacy): add bsc-testnet network support to legacy SDK for e2e t…
Hades-Ye Mar 11, 2026
fcccece
fix(e2e): resolve legacy client imports and add bsc/bsc-testnet netwo…
Hades-Ye Mar 11, 2026
1266423
fix(typescript): remove temporary verification scripts to pass CI lint
Hades-Ye Mar 11, 2026
8305933
fix(e2e): stabilize legacy-fastapi tests with explicit asset info and…
Hades-Ye Mar 12, 2026
2762add
fix(e2e): use fixed 2s delay and retry mechanism for better stability
Hades-Ye Mar 12, 2026
ce6a94b
fix(e2e): configure explicit DHLU token injection for all legacy servers
Hades-Ye Mar 12, 2026
099282e
chore(e2e): update flask lockfile after successful tests
Hades-Ye Mar 12, 2026
8b88234
fix(tron): resolve merge conflicts from dev/v2 and align engineering …
Hades-Ye Mar 12, 2026
d359a79
fix(tron): fix JSDoc lint errors and missing viem dependency
Hades-Ye Mar 12, 2026
e062db1
fix(ci): synchronize lockfile and format code across packages
Hades-Ye Mar 12, 2026
84555ba
style(tron): align parameter naming and JSDoc style with EVM mechanism
Hades-Ye Mar 12, 2026
8158f77
Merge pull request #10 from BofAI/feat/merge-dev-v2-resolved
Hades-Ye Mar 12, 2026
00e3116
chore: resolve merge conflicts with dev/v2 using 8158f77 version
Hades-Ye Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
367 changes: 367 additions & 0 deletions .github/workflows/e2e_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
# E2E Test Workflow for x402 Protocol
#
# This workflow runs the full end-to-end test suite which validates the x402
# payment protocol across multiple languages (TypeScript, Python),
# frameworks, transports (HTTP, MCP), and blockchain networks.
#
# Includes v1/v2 cross-version testing: legacy v1 servers are paired with
# new v2-capable clients to verify auto-adaptive protocol support.
#
# Trigger: Manual only (workflow_dispatch). Select the branch to test from
# the "Use workflow from" dropdown in the GitHub Actions UI.
#
# ============================================================================
# REQUIRED GITHUB SECRETS
# ============================================================================
#
# The following secrets must be configured in the repository settings
# (Settings > Secrets and variables > Actions) for this workflow to succeed.
#
# --- Wallet Keys (Required) ---
#
# CLIENT_EVM_PRIVATE_KEY:
# EVM private key for the client wallet that makes payments.
# Must be funded with testnet USDC on Base Sepolia.
# Format: 0x-prefixed hex string (e.g., 0x4df9...)
#
# FACILITATOR_EVM_PRIVATE_KEY:
# EVM private key for the facilitator wallet that verifies/settles payments.
# Format: 0x-prefixed hex string
#
# SERVER_EVM_ADDRESS:
# EVM address where server payments are sent (payee address).
# Format: 0x-prefixed hex address (e.g., 0x122F...)
#
# --- Optional Keys (for SVM/Aptos/Stellar) ---
#
# CLIENT_SVM_PRIVATE_KEY:
# Solana private key for the client wallet that makes payments.
# Must be funded with devnet USDC on Solana Devnet.
# Format: base58 encoded string
#
# FACILITATOR_SVM_PRIVATE_KEY:
# Solana private key for the facilitator wallet that verifies/settles payments.
# Format: base58 encoded string
#
# SERVER_SVM_ADDRESS:
# Solana address where server payments are sent (payee address).
# Format: base58 encoded public key
#
# CLIENT_APTOS_PRIVATE_KEY:
# Aptos private key for client wallet.
#
# CLIENT_STELLAR_PRIVATE_KEY:
# Stellar private key for client wallet.
#
# SERVER_APTOS_ADDRESS:
# Aptos address where server payments are sent.
#
# SERVER_STELLAR_ADDRESS:
# Stellar address where server payments are sent.
#
# FACILITATOR_APTOS_PRIVATE_KEY:
# Aptos private key for facilitator wallet.
#
# FACILITATOR_STELLAR_PRIVATE_KEY:
# Stellar private key for facilitator wallet.
#
# --- RPC URLs (Optional) ---
#
# These are optional. If not set, the test suite falls back to public defaults.
# Setting custom RPC URLs is recommended for reliability in CI.
#
# BSC_TESTNET_RPC_URL:
# Custom RPC URL for BSC Testnet.
# Default: https://data-seed-prebsc-1-s1.bnbchain.org:8545
#
# SOLANA_DEVNET_RPC_URL:
# Custom RPC URL for Solana Devnet.
# Default: https://api.devnet.solana.com
#
# ============================================================================

name: E2E Tests

on:
workflow_dispatch:
inputs:
branch:
description: "Branch to test (leave empty to use the branch selected above)"
required: false
default: ""
transport:
description: "Transports to test (comma-separated: http,mcp)"
required: false
default: "http"
facilitators:
description: "Facilitators to test (comma-separated: typescript,python)"
required: false
default: "typescript,python"
servers:
description: "Servers to test (comma-separated: express,hono,flask,fastapi,next)"
required: false
clients:
description: "Clients to test (comma-separated: axios,fetch,httpx,requests)"
required: false
families:
description: "Protocol families (comma-separated: evm,svm,aptos,stellar)"
required: false
minimize:
description: "Use coverage-based test minimization (--min)"
type: boolean
default: true
verbose:
description: "Enable verbose logging"
type: boolean
default: true

permissions:
contents: read
pull-requests: write

jobs:
e2e-tests:
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
# --- Validate Required Secrets ---
# Fail fast before installing anything if secrets are missing.

- name: Validate required secrets
run: |
MISSING=()

if [ -z "${{ secrets.CLIENT_EVM_PRIVATE_KEY }}" ]; then MISSING+=("CLIENT_EVM_PRIVATE_KEY"); fi
if [ -z "${{ secrets.FACILITATOR_EVM_PRIVATE_KEY }}" ]; then MISSING+=("FACILITATOR_EVM_PRIVATE_KEY"); fi
if [ -z "${{ secrets.SERVER_EVM_ADDRESS }}" ]; then MISSING+=("SERVER_EVM_ADDRESS"); fi

if [ ${#MISSING[@]} -gt 0 ]; then
echo "::error::Missing required secrets: ${MISSING[*]}"
echo ""
echo "The following secrets must be configured in Settings > Secrets and variables > Actions:"
for secret in "${MISSING[@]}"; do
echo " - $secret"
done
exit 1
fi

echo "All required secrets are present."

- uses: actions/checkout@v4
with:
ref: ${{ inputs.branch || github.ref }}

# --- Language Runtimes ---

- name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
with:
run_install: false

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
cache-dependency-path: |
e2e/pnpm-lock.yaml
typescript/pnpm-lock.yaml

- name: Setup Python (uv)
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Install Python
run: uv python install 3.13

# --- Install Dependencies ---

- name: Install and build TypeScript SDK
working-directory: ./typescript
run: |
pnpm install --frozen-lockfile
pnpm build

- name: Install E2E dependencies
working-directory: ./e2e
run: pnpm install --frozen-lockfile

# --- Build & Setup E2E Components ---
# setup.sh runs install.sh and build.sh for all servers, clients, and
# facilitators (TypeScript and Python components).
# --legacy flag includes v1 legacy implementations for cross-version testing.

- name: Setup E2E components (including legacy v1)
working-directory: ./e2e
run: ./setup.sh --legacy -v

# --- Write Environment File ---

- name: Create .env file
working-directory: ./e2e
run: |
{
echo "SERVER_EVM_ADDRESS=${{ secrets.SERVER_EVM_ADDRESS }}"
echo "SERVER_SVM_ADDRESS=${{ secrets.SERVER_SVM_ADDRESS }}"
echo "CLIENT_EVM_PRIVATE_KEY=${{ secrets.CLIENT_EVM_PRIVATE_KEY }}"
echo "CLIENT_SVM_PRIVATE_KEY=${{ secrets.CLIENT_SVM_PRIVATE_KEY }}"
echo "FACILITATOR_EVM_PRIVATE_KEY=${{ secrets.FACILITATOR_EVM_PRIVATE_KEY }}"
echo "FACILITATOR_SVM_PRIVATE_KEY=${{ secrets.FACILITATOR_SVM_PRIVATE_KEY }}"
echo "SERVER_APTOS_ADDRESS=${{ secrets.SERVER_APTOS_ADDRESS }}"
echo "SERVER_STELLAR_ADDRESS=${{ secrets.SERVER_STELLAR_ADDRESS }}"
echo "CLIENT_APTOS_PRIVATE_KEY=${{ secrets.CLIENT_APTOS_PRIVATE_KEY }}"
echo "CLIENT_STELLAR_PRIVATE_KEY=${{ secrets.CLIENT_STELLAR_PRIVATE_KEY }}"
echo "FACILITATOR_APTOS_PRIVATE_KEY=${{ secrets.FACILITATOR_APTOS_PRIVATE_KEY }}"
echo "FACILITATOR_STELLAR_PRIVATE_KEY=${{ secrets.FACILITATOR_STELLAR_PRIVATE_KEY }}"
} > .env

# --- Run E2E Tests ---
# Uses programmatic mode (CLI filter flags) to bypass the interactive
# prompt that `pnpm test` opens by default. Any --flag triggers
# programmatic mode in the test runner (see e2e/src/cli/args.ts).

- name: Run E2E tests
id: e2e
working-directory: ./e2e
run: |
ARGS=""

# Apply workflow_dispatch inputs (or defaults)
ARGS="$ARGS --transport=${{ inputs.transport || 'http' }}"
ARGS="$ARGS --facilitators=${{ inputs.facilitators || 'python,typescript' }}"

if [ -n "${{ inputs.servers }}" ]; then
ARGS="$ARGS --servers=${{ inputs.servers }}"
fi

if [ -n "${{ inputs.clients }}" ]; then
ARGS="$ARGS --clients=${{ inputs.clients }}"
fi

if [ -n "${{ inputs.families }}" ]; then
ARGS="$ARGS --families=${{ inputs.families }}"
fi

# Default to --min and -v when inputs are not provided (e.g. push trigger)
MINIMIZE="${{ inputs.minimize }}"
if [ "$MINIMIZE" = "true" ] || [ -z "$MINIMIZE" ]; then
ARGS="$ARGS --min"
fi

VERBOSE="${{ inputs.verbose }}"
if [ "$VERBOSE" = "true" ]; then
ARGS="$ARGS -v"
fi

ARGS="$ARGS --parallel"

ARGS="$ARGS --log-file=e2e-results.log --output-json=e2e-results.json"

echo "Running: pnpm test $ARGS"

# Run tests but capture exit code so we can still comment on the PR
set +e
pnpm test $ARGS
TEST_EXIT_CODE=$?
set -e

echo "test_exit_code=$TEST_EXIT_CODE" >> "$GITHUB_OUTPUT"
exit 0

# --- Upload Results ---

- name: Upload test log
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-test-log
path: |
e2e/e2e-results.log
e2e/e2e-results.json
retention-days: 14

# --- Comment on PR ---
# Find any open PR for the tested branch, delete previous E2E comments
# from this workflow, and post a new comment with results.

- name: Comment results on PR
if: always()
env:
GH_TOKEN: ${{ github.token }}
TEST_EXIT_CODE: ${{ steps.e2e.outputs.test_exit_code }}
run: |
BRANCH="${{ inputs.branch || github.ref_name }}"

# Find open PR for this branch
PR_NUMBER=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number' 2>/dev/null || true)

if [ -z "$PR_NUMBER" ]; then
echo "No open PR found for branch '$BRANCH', skipping comment."
exit 0
fi

echo "Found PR #$PR_NUMBER for branch '$BRANCH'"

# Delete previous E2E comments from this workflow
COMMENT_IDS=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
--paginate --jq '.[] | select(.body | contains("<!-- x402-e2e-results -->")) | .id' 2>/dev/null || true)

for CID in $COMMENT_IDS; do
echo "Deleting previous E2E comment $CID"
gh api --method DELETE "repos/${{ github.repository }}/issues/comments/$CID" 2>/dev/null || true
done

# Build comment body from JSON results
COMMENT_BODY="<!-- x402-e2e-results -->"$'\n'

if [ -f e2e/e2e-results.json ]; then
TOTAL=$(jq '.summary.total' e2e/e2e-results.json)
PASSED=$(jq '.summary.passed' e2e/e2e-results.json)
FAILED=$(jq '.summary.failed' e2e/e2e-results.json)
NETWORK=$(jq -r '.summary.networkMode' e2e/e2e-results.json)

if [ "$FAILED" = "0" ]; then
COMMENT_BODY+="## :white_check_mark: E2E Tests Passed"$'\n\n'
else
COMMENT_BODY+="## :x: E2E Tests Failed"$'\n\n'
fi

COMMENT_BODY+="**Network:** ${NETWORK} | **Total:** ${TOTAL} | **Passed:** ${PASSED} | **Failed:** ${FAILED}"$'\n\n'

# Failed test details (only shown when there are failures)
FAILED_DETAILS=$(jq -r '.results[] | select(.passed == false) | "| \(.testNumber) | \(.client) | \(.server) | \(.endpoint) | \(.facilitator) | \(.error // "Unknown") |"' e2e/e2e-results.json)
if [ -n "$FAILED_DETAILS" ]; then
COMMENT_BODY+="### Failed Tests"$'\n'
COMMENT_BODY+="| Test | Client | Server | Endpoint | Facilitator | Error |"$'\n'
COMMENT_BODY+="|---|---|---|---|---|---|"$'\n'
COMMENT_BODY+="${FAILED_DETAILS}"$'\n\n'
fi
else
# No JSON output — tests likely crashed before producing results
if [ "$TEST_EXIT_CODE" != "0" ]; then
COMMENT_BODY+="## :x: E2E Tests Failed"$'\n\n'
COMMENT_BODY+="Test runner exited with code ${TEST_EXIT_CODE} before producing results."$'\n'
COMMENT_BODY+="Check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details."$'\n'
else
COMMENT_BODY+="## :warning: E2E Tests — No Results"$'\n\n'
COMMENT_BODY+="No structured output was produced. Check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details."$'\n'
fi
fi

COMMENT_BODY+=$'\n'"<sub>Run: [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})</sub>"$'\n'

# Post new comment
gh pr comment "$PR_NUMBER" --body "$COMMENT_BODY"
echo "Posted E2E results comment on PR #$PR_NUMBER"

# --- Fail the workflow if tests failed ---

- name: Check test result
if: always()
run: |
if [ "${{ steps.e2e.outputs.test_exit_code }}" != "0" ]; then
echo "E2E tests failed with exit code ${{ steps.e2e.outputs.test_exit_code }}"
exit 1
fi
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,20 @@ dist/
# Agent / AI tool config
CLAUDE.md
.claude/

# Vim swap files
*.swp
*.swo
*~

# E2E test results and local config
e2e/*.json
**/.env-local
**/uv.lock
!python/x402/uv.lock
!e2e/clients/*/uv.lock
!e2e/facilitators/*/uv.lock
!e2e/servers/*/uv.lock
!examples/python/*/uv.lock
!examples/python/*/*/uv.lock
!examples/python/*/*/*/uv.lock
Loading
Loading