Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |
| class TestEthereumAdapter: | |
| """Tests for EthereumAdapter""" | |
| 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 | |
| ) | |
| 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") | |
| class TestPolygonAdapter: | |
| """Tests for PolygonAdapter""" | |
| 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 | |
| class TestBSCAdapter: | |
| """Tests for BSCAdapter""" | |
| 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 | |
| class TestAvalancheAdapter: | |
| """Tests for AvalancheAdapter""" | |
| 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 | |
| class TestFantomAdapter: | |
| """Tests for FantomAdapter""" | |
| 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 | |
| class TestOracleFulfillmentService: | |
| """Tests for OracleFulfillmentService""" | |
| 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 | |
| class TestOracleFulfillmentWorkflow: | |
| """Integration tests for oracle fulfillment workflow""" | |
| 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"]) | |