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

feat: e2e tests #77

Merged
merged 15 commits into from
Jan 15, 2025
44 changes: 44 additions & 0 deletions .github/workflows/e2e_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Run E2E Tests

on: [pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}

- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true

- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v3
with:
path: ./.venv
key: venv-${{ runner.os }}-${{ matrix.python }}-${{ hashFiles('poetry.lock') }}

- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --with dev

- name: Run tests
env:
CDP_API_KEY_NAME: ${{ secrets.CDP_API_KEY_NAME }}
CDP_API_KEY_PRIVATE_KEY: ${{ secrets.CDP_API_KEY_PRIVATE_KEY }}
NETWORK_ID: ${{ secrets.NETWORK_ID}}
WALLET_DATA: ${{ secrets.WALLET_DATA }}
run: make e2e
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test:

.PHONY: e2e
e2e:
poetry run python -m tests.e2e
poetry run pytest -m "e2e"

.PHONY: repl
repl:
Expand Down
18 changes: 15 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ sphinx-autodoc-typehints = "^2.4.4"
myst-parser = "^4.0.0"
ruff-lsp = "^0.0.58"
python-lsp-server = "^1.12.0"
python-dotenv = "^1.0.1"

[build-system]
requires = ["poetry-core"]
Expand Down Expand Up @@ -65,10 +66,13 @@ known-first-party = ["cdp"]

[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q --cov=cdp --cov-report=term-missing"
addopts = "-ra -q --cov=cdp --cov-report=term-missing -m 'not e2e'"
testpaths = [
"tests",
]
markers = [
"e2e: e2e tests, requiring env, deselect with '-m \"not e2e\"'",
]

[tool.coverage.run]
omit = ["cdp/client/*"]
193 changes: 193 additions & 0 deletions tests/test_e2e.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import json
import os
import time
from decimal import Decimal

import pytest
from dotenv import load_dotenv

from cdp import Cdp
from cdp.wallet import Wallet
from cdp.wallet_data import WalletData

load_dotenv()


@pytest.fixture(scope="module", autouse=True)
def configure_cdp():
"""Configure CDP once for all tests."""
Cdp.configure(
api_key_name=os.environ["CDP_API_KEY_NAME"],
private_key=os.environ["CDP_API_KEY_PRIVATE_KEY"].replace("\\n", "\n"),
)


@pytest.fixture(scope="module")
def wallet_data():
"""Load wallet data once for all tests."""
wallet_data_str = os.environ.get("WALLET_DATA")
return json.loads(wallet_data_str)


@pytest.fixture(scope="module")
def imported_wallet(wallet_data):
"""Create imported wallet once for all tests."""
return Wallet.import_data(WalletData.from_dict(wallet_data))


@pytest.mark.e2e
def test_wallet_data(wallet_data):
"""Test wallet data format and required values."""
expected = {
"wallet_id": wallet_data["wallet_id"],
"network_id": wallet_data["network_id"],
"seed": wallet_data["seed"],
"default_address_id": wallet_data["default_address_id"]
}

for key, value in expected.items():
assert key in wallet_data
assert value is not None


@pytest.mark.e2e
def test_wallet_import(wallet_data):
"""Test wallet import functionality."""
wallet_id = wallet_data["wallet_id"]
network_id = wallet_data["network_id"]
default_address_id = wallet_data["default_address_id"]

imported_wallet = Wallet.import_data(WalletData.from_dict(wallet_data))

assert imported_wallet is not None
assert imported_wallet.id == wallet_id
assert imported_wallet.network_id == network_id
assert imported_wallet.default_address is not None
assert imported_wallet.default_address.address_id == default_address_id


@pytest.mark.e2e
def test_wallet_faucet(imported_wallet):
"""Test wallet faucet with ETH."""
initial_balances = imported_wallet.balances()
initial_eth_balance = Decimal(str(initial_balances.get("eth", 0)))

imported_wallet.faucet().wait()
time.sleep(1)

final_balances = imported_wallet.balances()
final_eth_balance = Decimal(str(final_balances.get("eth", 0)))
assert final_eth_balance > initial_eth_balance


@pytest.mark.e2e
def test_wallet_faucet_usdc(imported_wallet):
"""Test wallet faucet with USDC."""
initial_balances = imported_wallet.balances()
initial_usdc_balance = Decimal(str(initial_balances.get("usdc", 0)))

imported_wallet.faucet(asset_id="usdc").wait()
time.sleep(1)

final_balances = imported_wallet.balances()
final_usdc_balance = Decimal(str(final_balances.get("usdc", 0)))
assert final_usdc_balance > initial_usdc_balance


@pytest.mark.e2e
def test_wallet_transfer(imported_wallet):
"""Test wallet transfer."""
destination_wallet = Wallet.create()

initial_source_balance = Decimal(str(imported_wallet.balances().get("eth", 0)))
initial_dest_balance = Decimal(str(destination_wallet.balances().get("eth", 0)))

transfer = imported_wallet.transfer(
amount=Decimal("0.000000001"),
asset_id="eth",
destination=destination_wallet
)

transfer.wait()

assert transfer is not None
assert transfer.status.value == "complete"

final_source_balance = Decimal(str(imported_wallet.balances().get("eth", 0)))
final_dest_balance = Decimal(str(destination_wallet.balances().get("eth", 0)))

assert final_source_balance < initial_source_balance
assert final_dest_balance > initial_dest_balance


@pytest.mark.e2e
def test_transaction_history(imported_wallet):
"""Test transaction history retrieval."""
# create a transaction
destination_wallet = Wallet.create()
transfer = imported_wallet.transfer(
amount=Decimal("0.000000001"),
asset_id="eth",
destination=destination_wallet
).wait()

time.sleep(10)

transactions = imported_wallet.default_address.transactions()
matching_tx = None

for tx in transactions:
if tx.transaction_hash == transfer.transaction_hash:
matching_tx = tx
break

assert matching_tx is not None
assert matching_tx.status.value == "complete"


@pytest.mark.e2e
def test_wallet_export(imported_wallet):
"""Test wallet export."""
exported_wallet = imported_wallet.export_data()
assert exported_wallet.wallet_id is not None
assert exported_wallet.seed is not None
assert len(exported_wallet.seed) == 128

imported_wallet.save_seed_to_file("test_seed.json")
assert os.path.exists("test_seed.json")

with open("test_seed.json") as f:
saved_seed = json.loads(f.read())

assert saved_seed[exported_wallet.wallet_id] == {
"seed": exported_wallet.seed,
"encrypted": False,
"auth_tag": "",
"iv": "",
"network_id": exported_wallet.network_id
}

os.unlink("test_seed.json")


@pytest.mark.e2e
def test_wallet_addresses(imported_wallet):
"""Test wallet addresses retrieval."""
addresses = imported_wallet.addresses
assert addresses
assert imported_wallet.default_address in addresses


@pytest.mark.e2e
def test_wallet_balances(imported_wallet):
"""Test wallet balances retrieval."""
balances = imported_wallet.balances()
assert balances.get("eth") > 0


@pytest.mark.e2e
def test_historical_balances(imported_wallet):
"""Test historical balance retrieval."""
balances = list(imported_wallet.default_address.historical_balances("eth"))
assert balances
assert all(balance.amount > 0 for balance in balances)
Loading