qcrypt-rng / tests /unit /test_blockchain.py
rocRevyAreGoals15's picture
fixing ui
86deab0
"""
Unit tests for Blockchain Chain Adapters and Oracle Fulfillment Service
Tests for Ethereum, Polygon, BSC, Avalanche, Fantom adapters and oracle service.
"""
import pytest
import asyncio
from unittest.mock import Mock, patch, MagicMock, AsyncMock
import sys
from pathlib import Path
import time
sys.path.append(str(Path(__file__).parent.parent.parent))
from app.blockchain.base import (
ChainAdapter,
ChainConfig,
TransactionStatus,
TransactionReceipt
)
from app.blockchain.ethereum import EthereumAdapter
from app.blockchain.polygon import PolygonAdapter
from app.blockchain.bsc import BSCAdapter
from app.blockchain.avalanche import AvalancheAdapter
from app.blockchain.fantom import FantomAdapter
from app.blockchain.oracle_service import (
OracleFulfillmentService,
OracleRequest,
FulfillmentStatus,
get_oracle_fulfillment_service
)
class TestChainConfig:
"""Test ChainConfig dataclass"""
def test_chain_config_creation(self):
"""Test creating chain config"""
config = ChainConfig(
rpc_url="https://example.com/rpc",
chain_id=1,
explorer_url="https://explorer.com",
currency_symbol="ETH",
private_key="0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
gas_price_gwei=20,
confirmations_required=6
)
assert config.rpc_url == "https://example.com/rpc"
assert config.chain_id == 1
assert config.explorer_url == "https://explorer.com"
assert config.currency_symbol == "ETH"
assert config.gas_price_gwei == 20
assert config.confirmations_required == 6
def test_chain_config_defaults(self):
"""Test chain config default values"""
config = ChainConfig(
rpc_url="https://example.com/rpc",
chain_id=1,
explorer_url="https://explorer.com",
currency_symbol="ETH"
)
assert config.private_key is None
assert config.gas_price_gwei is None
assert config.gas_limit is None
assert config.confirmations_required == 3
class TestTransactionStatus:
"""Test TransactionStatus enum"""
def test_transaction_status_values(self):
"""Test transaction status values"""
assert TransactionStatus.PENDING.value == "pending"
assert TransactionStatus.SUBMITTED.value == "submitted"
assert TransactionStatus.CONFIRMED.value == "confirmed"
assert TransactionStatus.FAILED.value == "failed"
assert TransactionStatus.REVERTED.value == "reverted"
class TestTransactionReceipt:
"""Test TransactionReceipt dataclass"""
def test_receipt_creation(self):
"""Test creating transaction receipt"""
receipt = TransactionReceipt(
tx_hash="0xabc123...",
status=TransactionStatus.CONFIRMED,
block_number=12345678,
confirmations=6,
gas_used=21000,
timestamp=time.time()
)
assert receipt.tx_hash == "0xabc123..."
assert receipt.status == TransactionStatus.CONFIRMED
assert receipt.block_number == 12345678
assert receipt.confirmations == 6
@pytest.mark.asyncio
class TestEthereumAdapter:
"""Tests for EthereumAdapter"""
@pytest.fixture
def eth_config(self):
"""Create Ethereum config"""
return ChainConfig(
rpc_url="https://mainnet.infura.io/v3/test",
chain_id=1,
explorer_url="https://etherscan.io",
currency_symbol="ETH",
private_key="0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
confirmations_required=3
)
@pytest.fixture
def eth_adapter(self, eth_config):
"""Create Ethereum adapter"""
with patch('app.blockchain.ethereum.WEB3_AVAILABLE', False):
adapter = EthereumAdapter(eth_config)
return adapter
async def test_adapter_initialization(self, eth_adapter):
"""Test adapter initialization"""
assert eth_adapter.config.chain_id == 1
assert eth_adapter.config.currency_symbol == "ETH"
async def test_get_gas_price_no_web3(self, eth_adapter):
"""Test gas price without web3"""
gas_price = await eth_adapter.get_gas_price()
assert gas_price == 20_000_000_000 # 20 gwei default
async def test_estimate_gas_no_web3(self, eth_adapter):
"""Test gas estimation without web3"""
gas = await eth_adapter.estimate_gas(
"0x1234567890123456789012345678901234567890",
"0x"
)
assert gas == 21000 # Base transaction gas
async def test_submit_transaction_no_web3(self, eth_adapter):
"""Test transaction submission without web3"""
tx_hash = await eth_adapter.submit_transaction(
"0x1234567890123456789012345678901234567890",
"0x",
0
)
assert tx_hash.startswith("0x")
assert len(tx_hash) == 66 # 0x + 64 hex chars
async def test_get_transaction_status_no_web3(self, eth_adapter):
"""Test transaction status without web3"""
receipt = await eth_adapter.get_transaction_status("0xabc123...")
assert receipt.tx_hash == "0xabc123..."
assert receipt.status == TransactionStatus.CONFIRMED
async def test_get_block_number_no_web3(self, eth_adapter):
"""Test block number without web3"""
block = await eth_adapter.get_block_number()
assert block == 0
async def test_get_contract_balance_no_web3(self, eth_adapter):
"""Test contract balance without web3"""
balance = await eth_adapter.get_contract_balance(
"0x1234567890123456789012345678901234567890"
)
assert balance == 0
async def test_get_explorer_url(self, eth_adapter):
"""Test explorer URL generation"""
url = eth_adapter.get_explorer_url("0xabc123...")
assert url == "https://etherscan.io/tx/0xabc123..."
async def test_get_address_url(self, eth_adapter):
"""Test address URL generation"""
url = eth_adapter.get_address_url("0x1234567890...")
assert url == "https://etherscan.io/address/0x1234567890..."
async def test_get_chain_info(self, eth_adapter):
"""Test chain info"""
info = await eth_adapter.get_chain_info()
assert info["chain_id"] == 1
assert info["chain_name"] == "Ethereum"
assert info["currency"] == "ETH"
async def test_commit_randomness_no_web3(self, eth_adapter):
"""Test commit randomness without web3"""
tx_hash = await eth_adapter.commit_randomness(
"0x1234567890123456789012345678901234567890",
"0xreq123",
"0xcommit123"
)
assert tx_hash.startswith("0x")
assert len(tx_hash) == 66
async def test_reveal_randomness_no_web3(self, eth_adapter):
"""Test reveal randomness without web3"""
tx_hash = await eth_adapter.reveal_randomness(
"0x1234567890123456789012345678901234567890",
"0xreq123",
"0x1234567890abcdef"
)
assert tx_hash.startswith("0x")
@pytest.mark.asyncio
class TestPolygonAdapter:
"""Tests for PolygonAdapter"""
@pytest.fixture
def polygon_adapter(self):
"""Create Polygon adapter"""
with patch('app.blockchain.ethereum.WEB3_AVAILABLE', False):
return PolygonAdapter()
async def test_polygon_config(self, polygon_adapter):
"""Test Polygon configuration"""
assert polygon_adapter.config.chain_id == 137
assert polygon_adapter.config.currency_symbol == "MATIC"
assert polygon_adapter.config.explorer_url == "https://polygonscan.com"
async def test_polygon_gas_price(self, polygon_adapter):
"""Test Polygon gas price"""
gas_price = await polygon_adapter.get_gas_price()
assert gas_price >= 30_000_000_000 # 30 gwei minimum
@pytest.mark.asyncio
class TestBSCAdapter:
"""Tests for BSCAdapter"""
@pytest.fixture
def bsc_adapter(self):
"""Create BSC adapter"""
with patch('app.blockchain.ethereum.WEB3_AVAILABLE', False):
return BSCAdapter()
async def test_bsc_config(self, bsc_adapter):
"""Test BSC configuration"""
assert bsc_adapter.config.chain_id == 56
assert bsc_adapter.config.currency_symbol == "BNB"
assert bsc_adapter.config.explorer_url == "https://bscscan.com"
async def test_bsc_gas_price(self, bsc_adapter):
"""Test BSC gas price"""
gas_price = await bsc_adapter.get_gas_price()
assert gas_price >= 3_000_000_000 # 3 gwei minimum
@pytest.mark.asyncio
class TestAvalancheAdapter:
"""Tests for AvalancheAdapter"""
@pytest.fixture
def avax_adapter(self):
"""Create Avalanche adapter"""
with patch('app.blockchain.ethereum.WEB3_AVAILABLE', False):
return AvalancheAdapter()
async def test_avalanche_config(self, avax_adapter):
"""Test Avalanche configuration"""
assert avax_adapter.config.chain_id == 43114
assert avax_adapter.config.currency_symbol == "AVAX"
assert avax_adapter.config.explorer_url == "https://snowtrace.io"
assert avax_adapter.config.confirmations_required == 3 # Fast finality
@pytest.mark.asyncio
class TestFantomAdapter:
"""Tests for FantomAdapter"""
@pytest.fixture
def ftm_adapter(self):
"""Create Fantom adapter"""
with patch('app.blockchain.ethereum.WEB3_AVAILABLE', False):
return FantomAdapter()
async def test_fantom_config(self, ftm_adapter):
"""Test Fantom configuration"""
assert ftm_adapter.config.chain_id == 250
assert ftm_adapter.config.currency_symbol == "FTM"
assert ftm_adapter.config.explorer_url == "https://ftmscan.com"
class TestChainAdapterFunctions:
"""Test chain adapter helper functions"""
def test_get_chain_adapter_ethereum(self):
"""Test getting Ethereum adapter"""
from app.blockchain import get_chain_adapter
config = ChainConfig(
rpc_url="https://example.com",
chain_id=1,
explorer_url="https://etherscan.io",
currency_symbol="ETH"
)
adapter = get_chain_adapter("ethereum", config)
assert isinstance(adapter, EthereumAdapter)
def test_get_chain_adapter_polygon(self):
"""Test getting Polygon adapter"""
from app.blockchain import get_chain_adapter
config = ChainConfig(
rpc_url="https://polygon-rpc.com",
chain_id=137,
explorer_url="https://polygonscan.com",
currency_symbol="MATIC"
)
adapter = get_chain_adapter("polygon", config)
assert isinstance(adapter, PolygonAdapter)
def test_get_chain_adapter_invalid(self):
"""Test getting invalid adapter"""
from app.blockchain import get_chain_adapter
config = ChainConfig(
rpc_url="https://example.com",
chain_id=1,
explorer_url="https://example.com",
currency_symbol="ETH"
)
with pytest.raises(ValueError, match="Unsupported chain"):
get_chain_adapter("invalid_chain", config)
def test_get_supported_chains(self):
"""Test getting supported chains"""
from app.blockchain import get_supported_chains
chains = get_supported_chains()
assert "ethereum" in chains
assert "polygon" in chains
assert "bsc" in chains
assert "avalanche" in chains
assert "fantom" in chains
assert chains["ethereum"]["chain_id"] == 1
assert chains["polygon"]["chain_id"] == 137
assert chains["bsc"]["chain_id"] == 56
@pytest.mark.asyncio
class TestOracleFulfillmentService:
"""Tests for OracleFulfillmentService"""
@pytest.fixture
def oracle_service(self):
"""Create oracle service"""
return OracleFulfillmentService()
async def test_service_creation(self, oracle_service):
"""Test service creation"""
assert oracle_service.requests == {}
assert oracle_service.chain_adapters == {}
async def test_configure_chain(self, oracle_service):
"""Test chain configuration"""
config = ChainConfig(
rpc_url="https://example.com",
chain_id=1,
explorer_url="https://etherscan.io",
currency_symbol="ETH"
)
result = oracle_service.configure_chain("ethereum", config)
assert result is True
assert "ethereum" in oracle_service.chain_adapters
async def test_create_request(self, oracle_service):
"""Test creating oracle request"""
request = await oracle_service.create_request(
chain="ethereum",
contract_address="0x1234567890123456789012345678901234567890",
num_bytes=32,
num_qubits=16
)
assert request.request_id.startswith("oracle_ethereum_")
assert request.chain == "ethereum"
assert request.num_bytes == 32
assert request.num_qubits == 16
assert request.status == FulfillmentStatus.PENDING
async def test_get_request_status(self, oracle_service):
"""Test getting request status"""
request = await oracle_service.create_request(
chain="ethereum",
contract_address="0x1234567890123456789012345678901234567890",
num_bytes=32
)
status = oracle_service.get_request_status(request.request_id)
assert status is not None
assert status["request_id"] == request.request_id
assert status["chain"] == "ethereum"
assert status["status"] == "pending"
async def test_get_request_status_not_found(self, oracle_service):
"""Test getting non-existent request status"""
status = oracle_service.get_request_status("non_existent_id")
assert status is None
async def test_get_all_requests(self, oracle_service):
"""Test getting all requests"""
# Create multiple requests
await oracle_service.create_request("ethereum", "0x1234", 32)
await oracle_service.create_request("ethereum", "0x1234", 32)
await oracle_service.create_request("ethereum", "0x1234", 32)
requests = oracle_service.get_all_requests()
assert len(requests) == 3
async def test_get_supported_chains(self, oracle_service):
"""Test getting supported chains"""
chains = oracle_service.get_supported_chains()
assert len(chains) == 5
assert "ethereum" in chains
assert "polygon" in chains
async def test_fulfill_request_no_adapter(self, oracle_service):
"""Test fulfillment without adapter configured"""
request = await oracle_service.create_request(
chain="ethereum",
contract_address="0x1234",
num_bytes=32
)
result = await oracle_service.fulfill_request(request.request_id)
assert result is False
assert request.status == FulfillmentStatus.FAILED
assert request.error is not None
async def test_singleton_pattern(self):
"""Test singleton pattern"""
service1 = get_oracle_fulfillment_service()
service2 = get_oracle_fulfillment_service()
assert service1 is service2
@pytest.mark.asyncio
class TestOracleFulfillmentWorkflow:
"""Integration tests for oracle fulfillment workflow"""
@pytest.fixture
def configured_service(self):
"""Create configured oracle service"""
service = OracleFulfillmentService()
config = ChainConfig(
rpc_url="https://example.com",
chain_id=1,
explorer_url="https://etherscan.io",
currency_symbol="ETH"
)
# Configure with mocked adapter
with patch('app.blockchain.ethereum.WEB3_AVAILABLE', False):
service.configure_chain("ethereum", config)
return service
async def test_request_lifecycle(self, configured_service):
"""Test complete request lifecycle"""
# Create request
request = await configured_service.create_request(
chain="ethereum",
contract_address="0x1234567890123456789012345678901234567890",
num_bytes=32,
num_qubits=16
)
# Check initial status
status = configured_service.get_request_status(request.request_id)
assert status["status"] == "pending"
# Try to fulfill (will fail without real adapter)
result = await configured_service.fulfill_request(request.request_id)
# Should fail because web3 is not available
assert result is False
# Check final status
status = configured_service.get_request_status(request.request_id)
assert status["status"] == "failed"
class TestFulfillmentStatus:
"""Test FulfillmentStatus enum"""
def test_status_values(self):
"""Test status values"""
assert FulfillmentStatus.PENDING.value == "pending"
assert FulfillmentStatus.COMMIT_SUBMITTED.value == "commit_submitted"
assert FulfillmentStatus.COMMIT_CONFIRMED.value == "commit_confirmed"
assert FulfillmentStatus.REVEAL_SUBMITTED.value == "reveal_submitted"
assert FulfillmentStatus.REVEAL_CONFIRMED.value == "reveal_confirmed"
assert FulfillmentStatus.COMPLETED.value == "completed"
assert FulfillmentStatus.FAILED.value == "failed"
class TestOracleRequest:
"""Test OracleRequest dataclass"""
def test_request_creation(self):
"""Test creating oracle request"""
request = OracleRequest(
request_id="test_123",
chain="ethereum",
contract_address="0x1234",
num_bytes=32,
num_qubits=16
)
assert request.request_id == "test_123"
assert request.chain == "ethereum"
assert request.num_bytes == 32
assert request.num_qubits == 16
assert request.status == FulfillmentStatus.PENDING
assert request.commitment is None
assert request.randomness is None
def test_request_with_values(self):
"""Test creating request with values"""
request = OracleRequest(
request_id="test_123",
chain="ethereum",
contract_address="0x1234",
num_bytes=32,
num_qubits=16,
status=FulfillmentStatus.COMPLETED,
commitment="0xabc123",
randomness="0x456def",
commit_tx_hash="0xcommit",
reveal_tx_hash="0xreveal"
)
assert request.status == FulfillmentStatus.COMPLETED
assert request.commitment == "0xabc123"
assert request.randomness == "0x456def"
assert request.commit_tx_hash == "0xcommit"
assert request.reveal_tx_hash == "0xreveal"
if __name__ == "__main__":
pytest.main([__file__, "-v", "-s"])