Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CI action to test endpoints from whitelisted chains and providers #5427

Merged
merged 13 commits into from
Oct 28, 2024
25 changes: 25 additions & 0 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Test RPC and LCD endpoints

on:
workflow_dispatch:

jobs:
test-endpoints:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r _tests/requirements.txt

- name: Run tests
run: |
pytest -v -n auto _tests
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
.DS_Store
.github/workflows/utility/__pycache__

node_modules/
node_modules/

_tests/__pycache__
3 changes: 3 additions & 0 deletions _tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest
pytest-xdist
requests
92 changes: 92 additions & 0 deletions _tests/test_endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
import requests
import pytest
from collections import namedtuple
import glob
import os
import json
import logging

# Setup basic configuration for logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

EndpointTest = namedtuple('EndpointTest', ['chain', 'endpoint', 'provider', 'address'])

# Set this to False to ignore the whitelist and process all providers
use_whitelist = True

# Whitelist for specific chains and providers
whitelist = {
"chains": [
"axelar",
"celestia",
"composable",
"cosmoshub",
"dydx",
"dymension",
"evmos",
"injective",
"neutron",
"noble",
"osmosis",
"stargaze",
"stride"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to ensure that our CI passes only when all providers are processed, we could also keep a tally of the number of successful chains validated, checking that it matches the length of the whitelisted chains at the end.

],
"providers": [
"Osmosis Foundation",
"Polkachu",
"CryptoCrew",
"forbole",
"Imperator.co",
"WhisperNode 🤐",
"chainlayer",
"Numia",
"Enigma",
"kjnodes",
"Stake&Relax 🦥",
"Allnodes ⚡️ Nodes & Staking",
"Lava",
"Golden Ratio Staking",
"Stargaze Foundation",
]
} if use_whitelist else {'chains': [], 'providers': []}

def log_request_details(test_case, response):
logging.info(f"Testing {test_case.chain}-{test_case.endpoint.upper()}-{test_case.provider}")
logging.info(f"Request URL: {response.url}")
logging.info(f"Response Status Code: {response.status_code}")
logging.info(f"Response Body: {response.text}")

def generate_endpoint_tests():
test_cases = []
logging.info(f"Current working directory: {os.getcwd()}")
files_found = glob.glob('**/chain.json', recursive=True)
for filename in files_found:
with open(filename) as f:
data = json.load(f)
chain_name = data.get('chain_name', 'unknown')
if 'apis' in data:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be helpful to print some warnings if this condition is false in case the structure changes and we start skipping the entries.

Applies to other if statements below too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

for api_type in ['rpc', 'rest']:
for api in data['apis'].get(api_type, []):
if 'provider' in api and (not use_whitelist or (chain_name in whitelist['chains'] and api['provider'] in whitelist['providers'])):
address = api['address']
if api_type == 'rpc':
address += '/status'
elif api_type == 'rest':
address += '/cosmos/base/tendermint/v1beta1/syncing'
test_cases.append(EndpointTest(chain=chain_name, endpoint=api_type, provider=api['provider'], address=address))
return test_cases

test_cases = generate_endpoint_tests()
if test_cases:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this check? Is it ever expected to not pass?

@pytest.mark.parametrize("test_case", test_cases, ids=lambda test_case: f"{test_case.chain.upper()}-{test_case.endpoint.upper()}-{test_case.provider}")
def test_endpoint_availability(test_case):
try:
response = requests.get(test_case.address, timeout=2)
log_request_details(test_case, response)
assert response.status_code == 200, f"{test_case.chain.upper()}-{test_case.endpoint.upper()}-{test_case.provider} endpoint not reachable"
except requests.exceptions.Timeout:
logging.error(f"{test_case.chain.upper()}-{test_case.endpoint.upper()}-{test_case.provider} endpoint timed out after 2 seconds")
pytest.fail(f"{test_case.chain.upper()}-{test_case.endpoint.upper()}-{test_case.provider} endpoint timed out after 2 seconds")
else:
logging.error("Skipping tests due to no valid test cases.")
Loading