Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
Documentation Validation / validate-policies-strict (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
- Added logger initialization to EventRouter in events.py - Fixed datetime.timedelta references to use timedelta directly in security_hardening.py - Fixed StateTransition timestamp default_factory to use lambda in state.py - Fixed StateValidator.validate_transitions to only check source states exist - Moved cross_chain_bridge_enhanced.py to cross_chain/bridge_enhanced.py - Updated import paths in global_marketplace
403 lines
15 KiB
Python
403 lines
15 KiB
Python
"""
|
|
Tests for blockchain service layer
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
from datetime import datetime
|
|
|
|
from aitbc.blockchain_service import (
|
|
Block,
|
|
Transaction,
|
|
Account,
|
|
BlockchainService,
|
|
RPCBlockchainService,
|
|
BlockchainServiceFactory,
|
|
)
|
|
|
|
|
|
class TestDataClasses:
|
|
"""Tests for blockchain data classes"""
|
|
|
|
def test_block_creation(self):
|
|
"""Test Block dataclass creation"""
|
|
block = Block(
|
|
height=100,
|
|
hash="0xabc123",
|
|
parent_hash="0xdef456",
|
|
timestamp=1234567890,
|
|
transactions=[{"hash": "0xtx1"}],
|
|
miner="0xminer",
|
|
gas_used=1000,
|
|
gas_limit=2000
|
|
)
|
|
assert block.height == 100
|
|
assert block.hash == "0xabc123"
|
|
assert block.parent_hash == "0xdef456"
|
|
assert block.transactions == [{"hash": "0xtx1"}]
|
|
|
|
def test_block_optional_fields(self):
|
|
"""Test Block with optional fields None"""
|
|
block = Block(
|
|
height=1,
|
|
hash="0xabc",
|
|
parent_hash="0xdef",
|
|
timestamp=0,
|
|
transactions=[]
|
|
)
|
|
assert block.miner is None
|
|
assert block.gas_used is None
|
|
assert block.gas_limit is None
|
|
|
|
def test_transaction_creation(self):
|
|
"""Test Transaction dataclass creation"""
|
|
tx = Transaction(
|
|
hash="0xtx123",
|
|
from_address="0xfrom",
|
|
to_address="0xto",
|
|
value="1000000000000000000",
|
|
nonce=1,
|
|
gas=21000,
|
|
gas_price="1000000000",
|
|
input_data="0xdata",
|
|
block_hash="0xblock",
|
|
block_number=100,
|
|
status="success"
|
|
)
|
|
assert tx.hash == "0xtx123"
|
|
assert tx.from_address == "0xfrom"
|
|
assert tx.to_address == "0xto"
|
|
|
|
def test_transaction_optional_fields(self):
|
|
"""Test Transaction with optional fields None"""
|
|
tx = Transaction(
|
|
hash="0xtx",
|
|
from_address="0xfrom",
|
|
to_address="0xto",
|
|
value="0",
|
|
nonce=0,
|
|
gas=0
|
|
)
|
|
assert tx.gas_price is None
|
|
assert tx.input_data is None
|
|
assert tx.block_hash is None
|
|
|
|
def test_account_creation(self):
|
|
"""Test Account dataclass creation"""
|
|
account = Account(
|
|
address="0xaccount123",
|
|
balance=1000000000000000000,
|
|
nonce=5
|
|
)
|
|
assert account.address == "0xaccount123"
|
|
assert account.balance == 1000000000000000000
|
|
assert account.nonce == 5
|
|
|
|
|
|
class TestRPCBlockchainService:
|
|
"""Tests for RPCBlockchainService"""
|
|
|
|
def test_initialization(self):
|
|
"""Test RPCBlockchainService initialization"""
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient') as mock_client_class:
|
|
service = RPCBlockchainService("http://localhost:8006", timeout=30)
|
|
assert service.rpc_url == "http://localhost:8006"
|
|
mock_client_class.assert_called_once_with(
|
|
base_url="http://localhost:8006",
|
|
timeout=30
|
|
)
|
|
|
|
def test_get_block_by_height(self):
|
|
"""Test get block by height"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {
|
|
"height": 100,
|
|
"hash": "0xblock100",
|
|
"parent_hash": "0xblock99",
|
|
"timestamp": 1234567890,
|
|
"transactions": [{"hash": "0xtx1"}],
|
|
"miner": "0xminer",
|
|
"gas_used": 1000,
|
|
"gas_limit": 2000
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
block = service.get_block(100)
|
|
|
|
assert block.height == 100
|
|
assert block.hash == "0xblock100"
|
|
mock_client.get.assert_called_once_with("/rpc/blocks/100")
|
|
|
|
def test_get_block_by_hash(self):
|
|
"""Test get block by hash"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {
|
|
"height": 100,
|
|
"hash": "0xblockhash",
|
|
"parent_hash": "0xparent",
|
|
"timestamp": 1234567890,
|
|
"transactions": []
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
block = service.get_block("0xblockhash")
|
|
|
|
assert block.height == 100
|
|
mock_client.get.assert_called_once_with("/rpc/block/0xblockhash")
|
|
|
|
def test_get_block_with_missing_fields(self):
|
|
"""Test get block handles missing fields with defaults"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {
|
|
"height": 100,
|
|
"hash": "0xblock",
|
|
"parent_hash": "0xparent",
|
|
"timestamp": 0
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
block = service.get_block(100)
|
|
|
|
assert block.transactions == []
|
|
assert block.miner is None
|
|
assert block.gas_used is None
|
|
|
|
@patch('aitbc.blockchain_service.logger')
|
|
def test_get_block_error(self, mock_logger):
|
|
"""Test get block handles errors"""
|
|
mock_client = MagicMock()
|
|
mock_client.get.side_effect = Exception("Network error")
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
|
|
with pytest.raises(Exception):
|
|
service.get_block(100)
|
|
|
|
mock_logger.error.assert_called_once()
|
|
|
|
def test_get_head_block(self):
|
|
"""Test get head block"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {
|
|
"height": 200,
|
|
"hash": "0xhead",
|
|
"parent_hash": "0xprev",
|
|
"timestamp": 1234567890,
|
|
"transactions": []
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
block = service.get_head_block()
|
|
|
|
assert block.height == 200
|
|
assert block.hash == "0xhead"
|
|
mock_client.get.assert_called_once_with("/rpc/head")
|
|
|
|
def test_get_transaction(self):
|
|
"""Test get transaction by hash"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {
|
|
"hash": "0xtx123",
|
|
"from": "0xfrom",
|
|
"to": "0xto",
|
|
"value": "1000000000000000000",
|
|
"nonce": 1,
|
|
"gas": 21000,
|
|
"gas_price": "1000000000",
|
|
"input": "0xdata",
|
|
"block_hash": "0xblock",
|
|
"block_number": 100,
|
|
"status": "success"
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
tx = service.get_transaction("0xtx123")
|
|
|
|
assert tx.hash == "0xtx123"
|
|
assert tx.from_address == "0xfrom"
|
|
assert tx.to_address == "0xto"
|
|
mock_client.get.assert_called_once_with("/rpc/transaction/0xtx123")
|
|
|
|
def test_get_transaction_with_missing_fields(self):
|
|
"""Test get transaction handles missing fields"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {
|
|
"hash": "0xtx",
|
|
"from": "0xfrom",
|
|
"to": "0xto",
|
|
"value": "0",
|
|
"nonce": 0,
|
|
"gas": 0
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
tx = service.get_transaction("0xtx")
|
|
|
|
assert tx.gas_price is None
|
|
assert tx.input_data is None
|
|
assert tx.block_number is None
|
|
|
|
def test_get_account_balance(self):
|
|
"""Test get account balance"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {
|
|
"balance": "1000000000000000000",
|
|
"nonce": 5
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
account = service.get_account_balance("0xaccount")
|
|
|
|
assert account.address == "0xaccount"
|
|
assert account.balance == 1000000000000000000
|
|
assert account.nonce == 5
|
|
mock_client.get.assert_called_once_with("/rpc/account/0xaccount")
|
|
|
|
def test_get_account_balance_with_defaults(self):
|
|
"""Test get account balance with default values"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
account = service.get_account_balance("0xaccount")
|
|
|
|
assert account.balance == 0
|
|
assert account.nonce == 0
|
|
|
|
def test_send_transaction(self):
|
|
"""Test send transaction"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {"hash": "0xtxhash"}
|
|
mock_client.post.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
tx_hash = service.send_transaction({"from": "0xfrom", "to": "0xto"})
|
|
|
|
assert tx_hash == "0xtxhash"
|
|
mock_client.post.assert_called_once_with("/rpc/sendTx", json={"from": "0xfrom", "to": "0xto"})
|
|
|
|
def test_send_transaction_with_tx_hash_key(self):
|
|
"""Test send transaction with tx_hash key in response"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {"tx_hash": "0xtxhash"}
|
|
mock_client.post.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
tx_hash = service.send_transaction({})
|
|
|
|
assert tx_hash == "0xtxhash"
|
|
|
|
def test_send_transaction_no_hash_error(self):
|
|
"""Test send transaction raises error when no hash in response"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {}
|
|
mock_client.post.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
|
|
with pytest.raises(ValueError, match="Transaction hash not found"):
|
|
service.send_transaction({})
|
|
|
|
def test_get_status(self):
|
|
"""Test get node status"""
|
|
mock_client = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {
|
|
"status": "syncing",
|
|
"block_height": 100,
|
|
"peers": 5
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
with patch('aitbc.blockchain_service.AITBCHTTPClient', return_value=mock_client):
|
|
service = RPCBlockchainService("http://localhost:8006")
|
|
status = service.get_status()
|
|
|
|
assert status["status"] == "syncing"
|
|
assert status["block_height"] == 100
|
|
mock_client.get.assert_called_once_with("/rpc/status")
|
|
|
|
|
|
class TestBlockchainServiceFactory:
|
|
"""Tests for BlockchainServiceFactory"""
|
|
|
|
def test_create_rpc_service(self):
|
|
"""Test create RPC service"""
|
|
with patch('aitbc.blockchain_service.RPCBlockchainService') as mock_service_class:
|
|
factory = BlockchainServiceFactory()
|
|
service = factory.create_rpc_service("http://localhost:8006", timeout=60)
|
|
|
|
mock_service_class.assert_called_once_with("http://localhost:8006", 60)
|
|
|
|
def test_create_service_rpc(self):
|
|
"""Test create service with RPC type"""
|
|
with patch('aitbc.blockchain_service.BlockchainServiceFactory.create_rpc_service') as mock_create:
|
|
factory = BlockchainServiceFactory()
|
|
service = factory.create_service("rpc", rpc_url="http://localhost:8006")
|
|
|
|
mock_create.assert_called_once_with(rpc_url="http://localhost:8006")
|
|
|
|
def test_create_service_unknown_type(self):
|
|
"""Test create service with unknown type raises error"""
|
|
factory = BlockchainServiceFactory()
|
|
|
|
with pytest.raises(ValueError, match="Unknown service type"):
|
|
factory.create_service("unknown", rpc_url="http://localhost:8006")
|
|
|
|
def test_create_service_default_kwargs(self):
|
|
"""Test create service passes kwargs correctly"""
|
|
with patch('aitbc.blockchain_service.BlockchainServiceFactory.create_rpc_service') as mock_create:
|
|
factory = BlockchainServiceFactory()
|
|
service = factory.create_service("rpc", rpc_url="http://localhost:8006", timeout=45)
|
|
|
|
mock_create.assert_called_once_with(rpc_url="http://localhost:8006", timeout=45)
|
|
|
|
|
|
class TestBlockchainServiceAbstract:
|
|
"""Tests for BlockchainService abstract class"""
|
|
|
|
def test_blockchain_service_is_abstract(self):
|
|
"""Test BlockchainService cannot be instantiated directly"""
|
|
with pytest.raises(TypeError):
|
|
BlockchainService()
|
|
|
|
def test_blockchain_service_has_abstract_methods(self):
|
|
"""Test BlockchainService defines required abstract methods"""
|
|
assert hasattr(BlockchainService, 'get_block')
|
|
assert hasattr(BlockchainService, 'get_head_block')
|
|
assert hasattr(BlockchainService, 'get_transaction')
|
|
assert hasattr(BlockchainService, 'get_account_balance')
|
|
assert hasattr(BlockchainService, 'send_transaction')
|
|
assert hasattr(BlockchainService, 'get_status')
|