Remove outdated GPU marketplace endpoint and fix staking service logic

- Remove duplicate `/marketplace/gpu/{gpu_id}` endpoint from marketplace_gpu.py
- Remove marketplace_gpu router inclusion from main.py (already included elsewhere)
- Fix staking service staker_count logic to check existing stakes before increment/decrement
- Add minimum stake amount validation (100 AITBC)
- Add proper error handling for stake not found cases
- Fix staking pool update to commit and refresh after modifications
- Update CLI send_transaction to use chain
This commit is contained in:
aitbc
2026-04-13 22:07:51 +02:00
parent da630386cf
commit 7c51f3490b
140 changed files with 42080 additions and 267 deletions

332
tests/fixtures/staking_fixtures.py vendored Normal file
View File

@@ -0,0 +1,332 @@
"""
Shared fixtures for staking tests
Reusable fixtures for service and integration tests to avoid duplication
"""
import pytest
import sys
from datetime import datetime, timedelta
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from sqlmodel import SQLModel
# Add paths for imports
sys.path.insert(0, "/opt/aitbc/apps/coordinator-api/src")
from app.domain.bounty import (
AgentStake, AgentMetrics, StakingPool,
StakeStatus, PerformanceTier
)
from app.services.staking_service import StakingService
@pytest.fixture
def db_session():
"""Create in-memory SQLite database for testing"""
engine = create_engine("sqlite:///:memory:")
SQLModel.metadata.create_all(engine)
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
try:
yield session
finally:
session.close()
engine.dispose()
@pytest.fixture
def staking_service(db_session):
"""Create StakingService instance with test session"""
return StakingService(db_session)
@pytest.fixture
def agent_wallet():
"""Default test agent wallet address"""
return "0x1234567890123456789012345678901234567890"
@pytest.fixture
def staker_address():
"""Default test staker address"""
return "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
@pytest.fixture
def agent_metrics(agent_wallet):
"""Create test agent metrics with GOLD tier"""
return AgentMetrics(
agent_wallet=agent_wallet,
total_submissions=10,
successful_submissions=9,
average_accuracy=95.0,
current_tier=PerformanceTier.GOLD,
tier_score=85.0,
total_staked=0.0,
staker_count=0,
total_rewards_distributed=0.0,
last_update_time=datetime.utcnow()
)
@pytest.fixture
def agent_metrics_bronze(agent_wallet):
"""Create test agent metrics with BRONZE tier"""
return AgentMetrics(
agent_wallet=agent_wallet,
total_submissions=5,
successful_submissions=4,
average_accuracy=80.0,
current_tier=PerformanceTier.BRONZE,
tier_score=60.0,
total_staked=0.0,
staker_count=0,
total_rewards_distributed=0.0,
last_update_time=datetime.utcnow()
)
@pytest.fixture
def agent_metrics_diamond(agent_wallet):
"""Create test agent metrics with DIAMOND tier"""
return AgentMetrics(
agent_wallet=agent_wallet,
total_submissions=50,
successful_submissions=48,
average_accuracy=98.0,
current_tier=PerformanceTier.DIAMOND,
tier_score=95.0,
total_staked=0.0,
staker_count=0,
total_rewards_distributed=0.0,
last_update_time=datetime.utcnow()
)
@pytest.fixture
def staking_pool(db_session, agent_metrics):
"""Create test staking pool"""
pool = StakingPool(
agent_wallet=agent_metrics.agent_wallet,
total_staked=0.0,
total_rewards=0.0,
pool_apy=5.0,
staker_count=0,
active_stakers=[],
last_distribution_time=datetime.utcnow(),
distribution_frequency=1
)
db_session.add(pool)
db_session.commit()
db_session.refresh(pool)
return pool
@pytest.fixture
def stake_data():
"""Default stake creation data"""
return {
"amount": 1000.0,
"lock_period": 30,
"auto_compound": False
}
@pytest.fixture
def large_stake_data():
"""Large stake creation data"""
return {
"amount": 50000.0,
"lock_period": 90,
"auto_compound": True
}
@pytest.fixture
def small_stake_data():
"""Small stake creation data"""
return {
"amount": 100.0,
"lock_period": 7,
"auto_compound": False
}
@pytest.fixture
def invalid_stake_data():
"""Invalid stake creation data (below minimum)"""
return {
"amount": 50.0,
"lock_period": 30,
"auto_compound": False
}
@pytest.fixture
def created_stake(staking_service, agent_metrics, staker_address, stake_data):
"""Create a stake for testing"""
return staking_service.create_stake(
staker_address=staker_address,
agent_wallet=agent_metrics.agent_wallet,
amount=stake_data["amount"],
lock_period=stake_data["lock_period"],
auto_compound=stake_data["auto_compound"]
)
@pytest.fixture
def active_stake(db_session, agent_wallet, staker_address):
"""Create an active stake directly in database"""
stake = AgentStake(
stake_id="stake_test_001",
staker_address=staker_address,
agent_wallet=agent_wallet,
amount=1000.0,
lock_period=30,
start_time=datetime.utcnow(),
end_time=datetime.utcnow() + timedelta(days=30),
status=StakeStatus.ACTIVE,
accumulated_rewards=0.0,
last_reward_time=datetime.utcnow(),
current_apy=8.25,
agent_tier=PerformanceTier.GOLD,
performance_multiplier=1.5,
auto_compound=False
)
db_session.add(stake)
db_session.commit()
db_session.refresh(stake)
return stake
@pytest.fixture
def unbonding_stake(db_session, agent_wallet, staker_address):
"""Create an unbonding stake directly in database"""
stake = AgentStake(
stake_id="stake_test_002",
staker_address=staker_address,
agent_wallet=agent_wallet,
amount=1000.0,
lock_period=30,
start_time=datetime.utcnow() - timedelta(days=35),
end_time=datetime.utcnow() - timedelta(days=5),
status=StakeStatus.UNBONDING,
accumulated_rewards=50.0,
last_reward_time=datetime.utcnow() - timedelta(days=5),
current_apy=8.25,
agent_tier=PerformanceTier.GOLD,
performance_multiplier=1.5,
auto_compound=False,
unbonding_time=datetime.utcnow() - timedelta(days=5)
)
db_session.add(stake)
db_session.commit()
db_session.refresh(stake)
return stake
@pytest.fixture
def completed_stake(db_session, agent_wallet, staker_address):
"""Create a completed stake directly in database"""
stake = AgentStake(
stake_id="stake_test_003",
staker_address=staker_address,
agent_wallet=agent_wallet,
amount=1000.0,
lock_period=30,
start_time=datetime.utcnow() - timedelta(days=70),
end_time=datetime.utcnow() - timedelta(days=40),
status=StakeStatus.COMPLETED,
accumulated_rewards=100.0,
last_reward_time=datetime.utcnow() - timedelta(days=40),
current_apy=8.25,
agent_tier=PerformanceTier.GOLD,
performance_multiplier=1.5,
auto_compound=False,
unbonding_time=datetime.utcnow() - timedelta(days=40)
)
db_session.add(stake)
db_session.commit()
db_session.refresh(stake)
return stake
@pytest.fixture
def multiple_stakes(db_session, agent_wallet, staker_address):
"""Create multiple stakes for testing"""
stakes = []
# Stake 1: Active, 30-day lock
stake1 = AgentStake(
stake_id="stake_test_001",
staker_address=staker_address,
agent_wallet=agent_wallet,
amount=1000.0,
lock_period=30,
start_time=datetime.utcnow(),
end_time=datetime.utcnow() + timedelta(days=30),
status=StakeStatus.ACTIVE,
accumulated_rewards=0.0,
last_reward_time=datetime.utcnow(),
current_apy=8.25,
agent_tier=PerformanceTier.GOLD,
performance_multiplier=1.5,
auto_compound=False
)
# Stake 2: Active, 90-day lock with auto-compound
stake2 = AgentStake(
stake_id="stake_test_002",
staker_address=staker_address,
agent_wallet=agent_wallet,
amount=2000.0,
lock_period=90,
start_time=datetime.utcnow(),
end_time=datetime.utcnow() + timedelta(days=90),
status=StakeStatus.ACTIVE,
accumulated_rewards=0.0,
last_reward_time=datetime.utcnow(),
current_apy=10.0,
agent_tier=PerformanceTier.GOLD,
performance_multiplier=1.5,
auto_compound=True
)
db_session.add_all([stake1, stake2])
db_session.commit()
for stake in [stake1, stake2]:
db_session.refresh(stake)
stakes.append(stake)
return stakes
def calculate_expected_apy(base_apy=5.0, tier_multiplier=1.0, lock_multiplier=1.0):
"""Calculate expected APY based on parameters"""
apy = base_apy * tier_multiplier * lock_multiplier
return min(apy, 20.0) # Cap at 20%
def get_tier_multiplier(tier):
"""Get tier multiplier for APY calculation"""
multipliers = {
PerformanceTier.BRONZE: 1.0,
PerformanceTier.SILVER: 1.25,
PerformanceTier.GOLD: 1.5,
PerformanceTier.PLATINUM: 2.0,
PerformanceTier.DIAMOND: 3.0
}
return multipliers.get(tier, 1.0)
def get_lock_multiplier(lock_period_days):
"""Get lock period multiplier for APY calculation"""
if lock_period_days >= 365:
return 2.0
elif lock_period_days >= 90:
return 1.5
elif lock_period_days >= 30:
return 1.1
else:
return 1.0