✅ Phase 0: Pre-implementation checklist completed - Environment configurations (dev/staging/production) - Directory structure setup (logs, backups, monitoring) - Virtual environment with dependencies ✅ Master deployment script created - Single command deployment with validation - Progress tracking and rollback capability - Health checks and deployment reporting ✅ Validation script created - Module import validation - Basic functionality testing - Configuration and script verification ✅ Implementation fixes - Fixed dataclass import in consensus keys - Fixed async function syntax in tests - Updated deployment script for virtual environment 🚀 Ready for deployment: ./scripts/deploy-mesh-network.sh dev
403 lines
14 KiB
Python
403 lines
14 KiB
Python
"""
|
|
Tests for Escrow System
|
|
"""
|
|
|
|
import pytest
|
|
import asyncio
|
|
import time
|
|
from decimal import Decimal
|
|
from unittest.mock import Mock, patch
|
|
|
|
from aitbc_chain.contracts.escrow import EscrowManager, EscrowState, DisputeReason
|
|
|
|
class TestEscrowManager:
|
|
"""Test cases for escrow manager"""
|
|
|
|
def setup_method(self):
|
|
"""Setup test environment"""
|
|
self.escrow_manager = EscrowManager()
|
|
|
|
def test_create_contract(self):
|
|
"""Test escrow contract creation"""
|
|
success, message, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="job_001",
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0')
|
|
)
|
|
)
|
|
|
|
assert success, f"Contract creation failed: {message}"
|
|
assert contract_id is not None
|
|
|
|
# Check contract details
|
|
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
|
|
assert contract is not None
|
|
assert contract.job_id == "job_001"
|
|
assert contract.client_address == "0x1234567890123456789012345678901234567890"
|
|
assert contract.agent_address == "0x2345678901234567890123456789012345678901"
|
|
assert contract.amount > Decimal('100.0') # Includes platform fee
|
|
assert contract.state == EscrowState.CREATED
|
|
|
|
def test_create_contract_invalid_inputs(self):
|
|
"""Test contract creation with invalid inputs"""
|
|
success, message, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="", # Empty job ID
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0')
|
|
)
|
|
)
|
|
|
|
assert not success
|
|
assert contract_id is None
|
|
assert "invalid" in message.lower()
|
|
|
|
def test_create_contract_with_milestones(self):
|
|
"""Test contract creation with milestones"""
|
|
milestones = [
|
|
{
|
|
'milestone_id': 'milestone_1',
|
|
'description': 'Initial setup',
|
|
'amount': Decimal('30.0')
|
|
},
|
|
{
|
|
'milestone_id': 'milestone_2',
|
|
'description': 'Main work',
|
|
'amount': Decimal('50.0')
|
|
},
|
|
{
|
|
'milestone_id': 'milestone_3',
|
|
'description': 'Final delivery',
|
|
'amount': Decimal('20.0')
|
|
}
|
|
]
|
|
|
|
success, message, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="job_002",
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0'),
|
|
milestones=milestones
|
|
)
|
|
)
|
|
|
|
assert success
|
|
assert contract_id is not None
|
|
|
|
# Check milestones
|
|
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
|
|
assert len(contract.milestones) == 3
|
|
assert contract.milestones[0]['amount'] == Decimal('30.0')
|
|
assert contract.milestones[1]['amount'] == Decimal('50.0')
|
|
assert contract.milestones[2]['amount'] == Decimal('20.0')
|
|
|
|
def test_create_contract_invalid_milestones(self):
|
|
"""Test contract creation with invalid milestones"""
|
|
milestones = [
|
|
{
|
|
'milestone_id': 'milestone_1',
|
|
'description': 'Setup',
|
|
'amount': Decimal('30.0')
|
|
},
|
|
{
|
|
'milestone_id': 'milestone_2',
|
|
'description': 'Main work',
|
|
'amount': Decimal('80.0') # Total exceeds contract amount
|
|
}
|
|
]
|
|
|
|
success, message, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="job_003",
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0'),
|
|
milestones=milestones
|
|
)
|
|
)
|
|
|
|
assert not success
|
|
assert "milestones" in message.lower()
|
|
|
|
def test_fund_contract(self):
|
|
"""Test funding contract"""
|
|
# Create contract first
|
|
success, _, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="job_004",
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0')
|
|
)
|
|
)
|
|
|
|
assert success
|
|
|
|
# Fund contract
|
|
success, message = asyncio.run(
|
|
self.escrow_manager.fund_contract(contract_id, "tx_hash_001")
|
|
)
|
|
|
|
assert success, f"Contract funding failed: {message}"
|
|
|
|
# Check state
|
|
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
|
|
assert contract.state == EscrowState.FUNDED
|
|
|
|
def test_fund_already_funded_contract(self):
|
|
"""Test funding already funded contract"""
|
|
# Create and fund contract
|
|
success, _, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="job_005",
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0')
|
|
)
|
|
)
|
|
|
|
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
|
|
|
|
# Try to fund again
|
|
success, message = asyncio.run(
|
|
self.escrow_manager.fund_contract(contract_id, "tx_hash_002")
|
|
)
|
|
|
|
assert not success
|
|
assert "state" in message.lower()
|
|
|
|
def test_start_job(self):
|
|
"""Test starting job"""
|
|
# Create and fund contract
|
|
success, _, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="job_006",
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0')
|
|
)
|
|
)
|
|
|
|
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
|
|
|
|
# Start job
|
|
success, message = asyncio.run(self.escrow_manager.start_job(contract_id))
|
|
|
|
assert success, f"Job start failed: {message}"
|
|
|
|
# Check state
|
|
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
|
|
assert contract.state == EscrowState.JOB_STARTED
|
|
|
|
def test_complete_milestone(self):
|
|
"""Test completing milestone"""
|
|
milestones = [
|
|
{
|
|
'milestone_id': 'milestone_1',
|
|
'description': 'Setup',
|
|
'amount': Decimal('50.0')
|
|
},
|
|
{
|
|
'milestone_id': 'milestone_2',
|
|
'description': 'Delivery',
|
|
'amount': Decimal('50.0')
|
|
}
|
|
]
|
|
|
|
# Create contract with milestones
|
|
success, _, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="job_007",
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0'),
|
|
milestones=milestones
|
|
)
|
|
)
|
|
|
|
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
|
|
asyncio.run(self.escrow_manager.start_job(contract_id))
|
|
|
|
# Complete milestone
|
|
success, message = asyncio.run(
|
|
self.escrow_manager.complete_milestone(contract_id, "milestone_1")
|
|
)
|
|
|
|
assert success, f"Milestone completion failed: {message}"
|
|
|
|
# Check milestone status
|
|
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
|
|
milestone = contract.milestones[0]
|
|
assert milestone['completed']
|
|
assert milestone['completed_at'] is not None
|
|
|
|
def test_verify_milestone(self):
|
|
"""Test verifying milestone"""
|
|
milestones = [
|
|
{
|
|
'milestone_id': 'milestone_1',
|
|
'description': 'Setup',
|
|
'amount': Decimal('50.0')
|
|
}
|
|
]
|
|
|
|
# Create contract with milestone
|
|
success, _, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="job_008",
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0'),
|
|
milestones=milestones
|
|
)
|
|
)
|
|
|
|
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
|
|
asyncio.run(self.escrow_manager.start_job(contract_id))
|
|
asyncio.run(self.escrow_manager.complete_milestone(contract_id, "milestone_1"))
|
|
|
|
# Verify milestone
|
|
success, message = asyncio.run(
|
|
self.escrow_manager.verify_milestone(contract_id, "milestone_1", True, "Work completed successfully")
|
|
)
|
|
|
|
assert success, f"Milestone verification failed: {message}"
|
|
|
|
# Check verification status
|
|
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
|
|
milestone = contract.milestones[0]
|
|
assert milestone['verified']
|
|
assert milestone['verification_feedback'] == "Work completed successfully"
|
|
|
|
def test_create_dispute(self):
|
|
"""Test creating dispute"""
|
|
# Create and fund contract
|
|
success, _, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="job_009",
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0')
|
|
)
|
|
)
|
|
|
|
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
|
|
asyncio.run(self.escrow_manager.start_job(contract_id))
|
|
|
|
# Create dispute
|
|
evidence = [
|
|
{
|
|
'type': 'screenshot',
|
|
'description': 'Poor quality work',
|
|
'timestamp': time.time()
|
|
}
|
|
]
|
|
|
|
success, message = asyncio.run(
|
|
self.escrow_manager.create_dispute(
|
|
contract_id, DisputeReason.QUALITY_ISSUES, "Work quality is poor", evidence
|
|
)
|
|
)
|
|
|
|
assert success, f"Dispute creation failed: {message}"
|
|
|
|
# Check dispute status
|
|
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
|
|
assert contract.state == EscrowState.DISPUTED
|
|
assert contract.dispute_reason == DisputeReason.QUALITY_ISSUES
|
|
|
|
def test_resolve_dispute(self):
|
|
"""Test resolving dispute"""
|
|
# Create and fund contract
|
|
success, _, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="job_010",
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0')
|
|
)
|
|
)
|
|
|
|
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
|
|
asyncio.run(self.escrow_manager.start_job(contract_id))
|
|
|
|
# Create dispute
|
|
asyncio.run(
|
|
self.escrow_manager.create_dispute(
|
|
contract_id, DisputeReason.QUALITY_ISSUES, "Quality issues"
|
|
)
|
|
)
|
|
|
|
# Resolve dispute
|
|
resolution = {
|
|
'winner': 'client',
|
|
'client_refund': 0.8, # 80% refund
|
|
'agent_payment': 0.2 # 20% payment
|
|
}
|
|
|
|
success, message = asyncio.run(
|
|
self.escrow_manager.resolve_dispute(contract_id, resolution)
|
|
)
|
|
|
|
assert success, f"Dispute resolution failed: {message}"
|
|
|
|
# Check resolution
|
|
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
|
|
assert contract.state == EscrowState.RESOLVED
|
|
assert contract.resolution == resolution
|
|
|
|
def test_refund_contract(self):
|
|
"""Test refunding contract"""
|
|
# Create and fund contract
|
|
success, _, contract_id = asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id="job_011",
|
|
client_address="0x1234567890123456789012345678901234567890",
|
|
agent_address="0x2345678901234567890123456789012345678901",
|
|
amount=Decimal('100.0')
|
|
)
|
|
)
|
|
|
|
asyncio.run(self.escrow_manager.fund_contract(contract_id, "tx_hash_001"))
|
|
|
|
# Refund contract
|
|
success, message = asyncio.run(
|
|
self.escrow_manager.refund_contract(contract_id, "Client requested refund")
|
|
)
|
|
|
|
assert success, f"Refund failed: {message}"
|
|
|
|
# Check refund status
|
|
contract = asyncio.run(self.escrow_manager.get_contract_info(contract_id))
|
|
assert contract.state == EscrowState.REFUNDED
|
|
assert contract.refunded_amount > 0
|
|
|
|
def test_get_escrow_statistics(self):
|
|
"""Test getting escrow statistics"""
|
|
# Create multiple contracts
|
|
for i in range(5):
|
|
asyncio.run(
|
|
self.escrow_manager.create_contract(
|
|
job_id=f"job_{i:03d}",
|
|
client_address=f"0x123456789012345678901234567890123456789{i}",
|
|
agent_address=f"0x234567890123456789012345678901234567890{i}",
|
|
amount=Decimal('100.0')
|
|
)
|
|
)
|
|
|
|
stats = asyncio.run(self.escrow_manager.get_escrow_statistics())
|
|
|
|
assert 'total_contracts' in stats
|
|
assert 'active_contracts' in stats
|
|
assert 'disputed_contracts' in stats
|
|
assert 'state_distribution' in stats
|
|
assert 'total_amount' in stats
|
|
assert stats['total_contracts'] >= 5
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__])
|