Files
aitbc/tests/unit/test_dynamic_pricing.py
oib 0e6c9eda72 feat(coordinator-api): integrate dynamic pricing engine with GPU marketplace and add agent identity router
- Add DynamicPricingEngine and MarketDataCollector dependencies to GPU marketplace endpoints
- Implement dynamic pricing calculation for GPU registration with market_balance strategy
- Calculate real-time dynamic prices at booking time with confidence scores and pricing factors
- Enhance /marketplace/pricing/{model} endpoint with comprehensive dynamic pricing analysis
  - Add static vs dynamic price
2026-02-28 22:57:10 +01:00

612 lines
24 KiB
Python

"""
Unit Tests for Dynamic Pricing Engine
Tests pricing calculations, strategies, and market data processing
"""
import pytest
import asyncio
from datetime import datetime, timedelta
from unittest.mock import Mock, patch, AsyncMock
import numpy as np
from app.services.dynamic_pricing_engine import (
DynamicPricingEngine,
PricingStrategy,
ResourceType,
PriceConstraints,
PricingFactors,
MarketConditions,
PriceTrend
)
from app.domain.pricing_strategies import StrategyLibrary
class TestDynamicPricingEngine:
"""Test cases for DynamicPricingEngine"""
@pytest.fixture
def pricing_engine(self):
"""Create a pricing engine instance for testing"""
config = {
"min_price": 0.001,
"max_price": 1000.0,
"update_interval": 300,
"forecast_horizon": 72,
"max_volatility_threshold": 0.3,
"circuit_breaker_threshold": 0.5
}
engine = DynamicPricingEngine(config)
return engine
@pytest.fixture
def sample_market_conditions(self):
"""Create sample market conditions for testing"""
return MarketConditions(
region="us_west",
resource_type=ResourceType.GPU,
demand_level=0.8,
supply_level=0.6,
average_price=0.05,
price_volatility=0.15,
utilization_rate=0.75,
competitor_prices=[0.045, 0.055, 0.048, 0.052],
market_sentiment=0.2
)
@pytest.mark.asyncio
async def test_engine_initialization(self, pricing_engine):
"""Test engine initialization"""
await pricing_engine.initialize()
assert pricing_engine.min_price == 0.001
assert pricing_engine.max_price == 1000.0
assert pricing_engine.update_interval == 300
assert pricing_engine.forecast_horizon == 72
assert isinstance(pricing_engine.pricing_history, dict)
assert isinstance(pricing_engine.provider_strategies, dict)
assert isinstance(pricing_engine.price_constraints, dict)
@pytest.mark.asyncio
async def test_calculate_dynamic_price_basic(self, pricing_engine, sample_market_conditions):
"""Test basic dynamic price calculation"""
# Mock market conditions
with patch.object(pricing_engine, '_get_market_conditions', return_value=sample_market_conditions):
result = await pricing_engine.calculate_dynamic_price(
resource_id="test_gpu_1",
resource_type=ResourceType.GPU,
base_price=0.05,
strategy=PricingStrategy.MARKET_BALANCE,
region="us_west"
)
assert result.resource_id == "test_gpu_1"
assert result.resource_type == ResourceType.GPU
assert result.current_price == 0.05
assert result.recommended_price > 0
assert result.recommended_price <= pricing_engine.max_price
assert result.recommended_price >= pricing_engine.min_price
assert isinstance(result.price_trend, PriceTrend)
assert 0 <= result.confidence_score <= 1
assert isinstance(result.factors_exposed, dict)
assert isinstance(result.reasoning, list)
assert result.strategy_used == PricingStrategy.MARKET_BALANCE
@pytest.mark.asyncio
async def test_pricing_strategies_different_results(self, pricing_engine, sample_market_conditions):
"""Test that different strategies produce different results"""
with patch.object(pricing_engine, '_get_market_conditions', return_value=sample_market_conditions):
# Test aggressive growth strategy
result_growth = await pricing_engine.calculate_dynamic_price(
resource_id="test_gpu_1",
resource_type=ResourceType.GPU,
base_price=0.05,
strategy=PricingStrategy.AGGRESSIVE_GROWTH,
region="us_west"
)
# Test profit maximization strategy
result_profit = await pricing_engine.calculate_dynamic_price(
resource_id="test_gpu_1",
resource_type=ResourceType.GPU,
base_price=0.05,
strategy=PricingStrategy.PROFIT_MAXIMIZATION,
region="us_west"
)
# Results should be different
assert result_growth.recommended_price != result_profit.recommended_price
assert result_growth.strategy_used == PricingStrategy.AGGRESSIVE_GROWTH
assert result_profit.strategy_used == PricingStrategy.PROFIT_MAXIMIZATION
@pytest.mark.asyncio
async def test_price_constraints_application(self, pricing_engine, sample_market_conditions):
"""Test that price constraints are properly applied"""
constraints = PriceConstraints(
min_price=0.03,
max_price=0.08,
max_change_percent=0.2
)
with patch.object(pricing_engine, '_get_market_conditions', return_value=sample_market_conditions):
result = await pricing_engine.calculate_dynamic_price(
resource_id="test_gpu_1",
resource_type=ResourceType.GPU,
base_price=0.05,
strategy=PricingStrategy.PROFIT_MAXIMIZATION,
constraints=constraints,
region="us_west"
)
# Should respect constraints
assert result.recommended_price >= constraints.min_price
assert result.recommended_price <= constraints.max_price
@pytest.mark.asyncio
async def test_circuit_breaker_activation(self, pricing_engine, sample_market_conditions):
"""Test circuit breaker activation during high volatility"""
# Create high volatility conditions
high_volatility_conditions = MarketConditions(
region="us_west",
resource_type=ResourceType.GPU,
demand_level=0.9,
supply_level=0.3,
average_price=0.05,
price_volatility=0.6, # High volatility
utilization_rate=0.95,
competitor_prices=[0.045, 0.055, 0.048, 0.052],
market_sentiment=-0.3
)
# Add some pricing history
pricing_engine.pricing_history["test_gpu_1"] = [
Mock(price=0.05, timestamp=datetime.utcnow() - timedelta(minutes=10))
]
with patch.object(pricing_engine, '_get_market_conditions', return_value=high_volatility_conditions):
result = await pricing_engine.calculate_dynamic_price(
resource_id="test_gpu_1",
resource_type=ResourceType.GPU,
base_price=0.05,
strategy=PricingStrategy.MARKET_BALANCE,
region="us_west"
)
# Circuit breaker should be activated
assert "test_gpu_1" in pricing_engine.circuit_breakers
assert pricing_engine.circuit_breakers["test_gpu_1"] is True
@pytest.mark.asyncio
async def test_price_forecast_generation(self, pricing_engine):
"""Test price forecast generation"""
# Add historical data
base_time = datetime.utcnow()
for i in range(48): # 48 data points
pricing_engine.pricing_history["test_gpu_1"] = pricing_engine.pricing_history.get("test_gpu_1", [])
pricing_engine.pricing_history["test_gpu_1"].append(
Mock(
price=0.05 + (i * 0.001),
demand_level=0.6 + (i % 10) * 0.02,
supply_level=0.7 - (i % 8) * 0.01,
confidence=0.8,
strategy_used="market_balance",
timestamp=base_time - timedelta(hours=48-i)
)
)
forecast = await pricing_engine.get_price_forecast("test_gpu_1", 24)
assert len(forecast) == 24
for point in forecast:
assert hasattr(point, 'timestamp')
assert hasattr(point, 'price')
assert hasattr(point, 'demand_level')
assert hasattr(point, 'supply_level')
assert hasattr(point, 'confidence')
assert 0 <= point.confidence <= 1
assert point.price >= pricing_engine.min_price
assert point.price <= pricing_engine.max_price
@pytest.mark.asyncio
async def test_provider_strategy_management(self, pricing_engine):
"""Test setting and retrieving provider strategies"""
constraints = PriceConstraints(
min_price=0.02,
max_price=0.10
)
# Set strategy
success = await pricing_engine.set_provider_strategy(
provider_id="test_provider",
strategy=PricingStrategy.AGGRESSIVE_GROWTH,
constraints=constraints
)
assert success is True
assert pricing_engine.provider_strategies["test_provider"] == PricingStrategy.AGGRESSIVE_GROWTH
assert pricing_engine.price_constraints["test_provider"] == constraints
def test_demand_multiplier_calculation(self, pricing_engine):
"""Test demand multiplier calculation"""
# High demand
multiplier_high = pricing_engine._calculate_demand_multiplier(0.9, PricingStrategy.MARKET_BALANCE)
assert multiplier_high > 1.0
# Low demand
multiplier_low = pricing_engine._calculate_demand_multiplier(0.2, PricingStrategy.MARKET_BALANCE)
assert multiplier_low < 1.0
# Aggressive growth strategy should have lower multipliers
multiplier_growth = pricing_engine._calculate_demand_multiplier(0.8, PricingStrategy.AGGRESSIVE_GROWTH)
multiplier_balance = pricing_engine._calculate_demand_multiplier(0.8, PricingStrategy.MARKET_BALANCE)
assert multiplier_growth < multiplier_balance
def test_supply_multiplier_calculation(self, pricing_engine):
"""Test supply multiplier calculation"""
# Low supply (should increase prices)
multiplier_low_supply = pricing_engine._calculate_supply_multiplier(0.2, PricingStrategy.MARKET_BALANCE)
assert multiplier_low_supply > 1.0
# High supply (should decrease prices)
multiplier_high_supply = pricing_engine._calculate_supply_multiplier(0.9, PricingStrategy.MARKET_BALANCE)
assert multiplier_high_supply < 1.0
def test_time_multiplier_calculation(self, pricing_engine):
"""Test time-based multiplier calculation"""
# Test different hours
business_hour_multiplier = pricing_engine._calculate_time_multiplier()
# Mock different hours by temporarily changing the method
with patch('datetime.datetime') as mock_datetime:
mock_datetime.utcnow.return_value.hour = 14 # 2 PM business hour
business_hour_multiplier = pricing_engine._calculate_time_multiplier()
assert business_hour_multiplier > 1.0
mock_datetime.utcnow.return_value.hour = 3 # 3 AM late night
late_night_multiplier = pricing_engine._calculate_time_multiplier()
assert late_night_multiplier < 1.0
def test_competition_multiplier_calculation(self, pricing_engine):
"""Test competition-based multiplier calculation"""
competitor_prices = [0.045, 0.055, 0.048, 0.052]
base_price = 0.05
# Competitive response strategy
multiplier_competitive = pricing_engine._calculate_competition_multiplier(
base_price, competitor_prices, PricingStrategy.COMPETITIVE_RESPONSE
)
# Profit maximization strategy
multiplier_profit = pricing_engine._calculate_competition_multiplier(
base_price, competitor_prices, PricingStrategy.PROFIT_MAXIMIZATION
)
# Competitive strategy should be more responsive to competition
assert isinstance(multiplier_competitive, float)
assert isinstance(multiplier_profit, float)
def test_price_trend_determination(self, pricing_engine):
"""Test price trend determination"""
# Create increasing trend
pricing_engine.pricing_history["test_resource"] = [
Mock(price=0.05, timestamp=datetime.utcnow() - timedelta(minutes=50)),
Mock(price=0.051, timestamp=datetime.utcnow() - timedelta(minutes=40)),
Mock(price=0.052, timestamp=datetime.utcnow() - timedelta(minutes=30)),
Mock(price=0.053, timestamp=datetime.utcnow() - timedelta(minutes=20)),
Mock(price=0.054, timestamp=datetime.utcnow() - timedelta(minutes=10))
]
trend = pricing_engine._determine_price_trend("test_resource", 0.055)
assert trend == PriceTrend.INCREASING
# Create decreasing trend
pricing_engine.pricing_history["test_resource"] = [
Mock(price=0.055, timestamp=datetime.utcnow() - timedelta(minutes=50)),
Mock(price=0.054, timestamp=datetime.utcnow() - timedelta(minutes=40)),
Mock(price=0.053, timestamp=datetime.utcnow() - timedelta(minutes=30)),
Mock(price=0.052, timestamp=datetime.utcnow() - timedelta(minutes=20)),
Mock(price=0.051, timestamp=datetime.utcnow() - timedelta(minutes=10))
]
trend = pricing_engine._determine_price_trend("test_resource", 0.05)
assert trend == PriceTrend.DECREASING
def test_confidence_score_calculation(self, pricing_engine, sample_market_conditions):
"""Test confidence score calculation"""
factors = PricingFactors(
base_price=0.05,
demand_level=0.8,
supply_level=0.6,
market_volatility=0.1,
confidence_score=0.8
)
confidence = pricing_engine._calculate_confidence_score(factors, sample_market_conditions)
assert 0 <= confidence <= 1
assert isinstance(confidence, float)
def test_pricing_factors_calculation(self, pricing_engine, sample_market_conditions):
"""Test pricing factors calculation"""
factors = asyncio.run(pricing_engine._calculate_pricing_factors(
resource_id="test_gpu_1",
resource_type=ResourceType.GPU,
base_price=0.05,
strategy=PricingStrategy.MARKET_BALANCE,
market_conditions=sample_market_conditions
))
assert isinstance(factors, PricingFactors)
assert factors.base_price == 0.05
assert 0 <= factors.demand_multiplier <= 3.0
assert 0.8 <= factors.supply_multiplier <= 2.5
assert 0.7 <= factors.time_multiplier <= 1.5
assert 0.9 <= factors.performance_multiplier <= 1.3
assert 0.8 <= factors.competition_multiplier <= 1.4
assert 0.9 <= factors.sentiment_multiplier <= 1.2
assert 0.8 <= factors.regional_multiplier <= 1.3
def test_strategy_pricing_application(self, pricing_engine, sample_market_conditions):
"""Test strategy-specific pricing application"""
factors = PricingFactors(
base_price=0.05,
demand_multiplier=1.2,
supply_multiplier=1.1,
time_multiplier=1.0,
performance_multiplier=1.05,
competition_multiplier=0.95,
sentiment_multiplier=1.02,
regional_multiplier=1.0
)
# Test different strategies
price_aggressive = asyncio.run(pricing_engine._apply_strategy_pricing(
0.05, factors, PricingStrategy.AGGRESSIVE_GROWTH, sample_market_conditions
))
price_profit = asyncio.run(pricing_engine._apply_strategy_pricing(
0.05, factors, PricingStrategy.PROFIT_MAXIMIZATION, sample_market_conditions
))
# Should produce different results
assert price_aggressive != price_profit
assert price_aggressive > 0
assert price_profit > 0
def test_constraints_and_risk_application(self, pricing_engine):
"""Test constraints and risk management application"""
constraints = PriceConstraints(
min_price=0.03,
max_price=0.08,
max_change_percent=0.2
)
factors = PricingFactors(
base_price=0.05,
market_volatility=0.1
)
# Test normal price within constraints
normal_price = asyncio.run(pricing_engine._apply_constraints_and_risk(
"test_resource", 0.06, constraints, factors
))
assert 0.03 <= normal_price <= 0.08
# Test price above max constraint
high_price = asyncio.run(pricing_engine._apply_constraints_and_risk(
"test_resource", 0.10, constraints, factors
))
assert high_price <= constraints.max_price
# Test price below min constraint
low_price = asyncio.run(pricing_engine._apply_constraints_and_risk(
"test_resource", 0.01, constraints, factors
))
assert low_price >= constraints.min_price
def test_price_point_storage(self, pricing_engine):
"""Test price point storage in history"""
factors = PricingFactors(
base_price=0.05,
demand_level=0.8,
supply_level=0.6,
confidence_score=0.85
)
asyncio.run(pricing_engine._store_price_point(
"test_resource", 0.055, factors, PricingStrategy.MARKET_BALANCE
))
assert "test_resource" in pricing_engine.pricing_history
assert len(pricing_engine.pricing_history["test_resource"]) == 1
point = pricing_engine.pricing_history["test_resource"][0]
assert point.price == 0.055
assert point.demand_level == 0.8
assert point.supply_level == 0.6
assert point.confidence == 0.85
assert point.strategy_used == "market_balance"
def test_seasonal_factor_calculation(self, pricing_engine):
"""Test seasonal factor calculation"""
# Test morning hours
morning_factor = pricing_engine._calculate_seasonal_factor(9)
assert morning_factor > 1.0
# Test business peak
peak_factor = pricing_engine._calculate_seasonal_factor(14)
assert peak_factor > morning_factor
# Test late night
night_factor = pricing_engine._calculate_seasonal_factor(3)
assert night_factor < 1.0
def test_demand_supply_forecasting(self, pricing_engine):
"""Test demand and supply level forecasting"""
demand_history = [0.6, 0.7, 0.8, 0.75, 0.9, 0.85]
supply_history = [0.7, 0.6, 0.5, 0.55, 0.4, 0.45]
demand_forecast = pricing_engine._forecast_demand_level(demand_history, 1)
supply_forecast = pricing_engine._forecast_supply_level(supply_history, 1)
assert 0 <= demand_forecast <= 1
assert 0 <= supply_forecast <= 1
assert isinstance(demand_forecast, float)
assert isinstance(supply_forecast, float)
class TestPricingFactors:
"""Test cases for PricingFactors dataclass"""
def test_pricing_factors_creation(self):
"""Test PricingFactors creation with default values"""
factors = PricingFactors(base_price=0.05)
assert factors.base_price == 0.05
assert factors.demand_multiplier == 1.0
assert factors.supply_multiplier == 1.0
assert factors.time_multiplier == 1.0
assert factors.performance_multiplier == 1.0
assert factors.competition_multiplier == 1.0
assert factors.sentiment_multiplier == 1.0
assert factors.regional_multiplier == 1.0
assert factors.confidence_score == 0.8
assert factors.risk_adjustment == 0.0
def test_pricing_factors_with_custom_values(self):
"""Test PricingFactors creation with custom values"""
factors = PricingFactors(
base_price=0.05,
demand_multiplier=1.5,
supply_multiplier=0.8,
confidence_score=0.9
)
assert factors.base_price == 0.05
assert factors.demand_multiplier == 1.5
assert factors.supply_multiplier == 0.8
assert factors.confidence_score == 0.9
class TestPriceConstraints:
"""Test cases for PriceConstraints dataclass"""
def test_price_constraints_creation(self):
"""Test PriceConstraints creation with default values"""
constraints = PriceConstraints()
assert constraints.min_price is None
assert constraints.max_price is None
assert constraints.max_change_percent == 0.5
assert constraints.min_change_interval == 300
assert constraints.strategy_lock_period == 3600
def test_price_constraints_with_custom_values(self):
"""Test PriceConstraints creation with custom values"""
constraints = PriceConstraints(
min_price=0.02,
max_price=0.10,
max_change_percent=0.3
)
assert constraints.min_price == 0.02
assert constraints.max_price == 0.10
assert constraints.max_change_percent == 0.3
class TestMarketConditions:
"""Test cases for MarketConditions dataclass"""
def test_market_conditions_creation(self):
"""Test MarketConditions creation"""
conditions = MarketConditions(
region="us_west",
resource_type=ResourceType.GPU,
demand_level=0.8,
supply_level=0.6,
average_price=0.05,
price_volatility=0.15,
utilization_rate=0.75
)
assert conditions.region == "us_west"
assert conditions.resource_type == ResourceType.GPU
assert conditions.demand_level == 0.8
assert conditions.supply_level == 0.6
assert conditions.average_price == 0.05
assert conditions.price_volatility == 0.15
assert conditions.utilization_rate == 0.75
assert conditions.competitor_prices == []
assert conditions.market_sentiment == 0.0
assert isinstance(conditions.timestamp, datetime)
class TestStrategyLibrary:
"""Test cases for StrategyLibrary"""
def test_get_all_strategies(self):
"""Test getting all available strategies"""
strategies = StrategyLibrary.get_all_strategies()
assert isinstance(strategies, dict)
assert len(strategies) > 0
assert PricingStrategy.AGGRESSIVE_GROWTH in strategies
assert PricingStrategy.PROFIT_MAXIMIZATION in strategies
assert PricingStrategy.MARKET_BALANCE in strategies
# Check strategy configurations
for strategy_type, config in strategies.items():
assert config.strategy_type == strategy_type
assert config.name is not None
assert config.description is not None
assert config.parameters is not None
assert isinstance(config.parameters.base_multiplier, float)
def test_aggressive_growth_strategy(self):
"""Test aggressive growth strategy configuration"""
strategy = StrategyLibrary.get_aggressive_growth_strategy()
assert strategy.strategy_type == PricingStrategy.AGGRESSIVE_GROWTH
assert strategy.parameters.base_multiplier < 1.0 # Lower prices for growth
assert strategy.parameters.growth_target_rate > 0.2 # High growth target
assert strategy.risk_tolerance.value == "aggressive"
def test_profit_maximization_strategy(self):
"""Test profit maximization strategy configuration"""
strategy = StrategyLibrary.get_profit_maximization_strategy()
assert strategy.strategy_type == PricingStrategy.PROFIT_MAXIMIZATION
assert strategy.parameters.base_multiplier > 1.0 # Higher prices for profit
assert strategy.parameters.profit_target_margin > 0.3 # High profit target
assert strategy.parameters.demand_sensitivity > 0.5 # Demand sensitive
def test_market_balance_strategy(self):
"""Test market balance strategy configuration"""
strategy = StrategyLibrary.get_market_balance_strategy()
assert strategy.strategy_type == PricingStrategy.MARKET_BALANCE
assert strategy.parameters.base_multiplier == 1.0 # Balanced pricing
assert strategy.parameters.volatility_threshold < 0.2 # Lower volatility tolerance
assert strategy.risk_tolerance.value == "moderate"
if __name__ == "__main__":
pytest.main([__file__])