website/hub/services/tests/test_pricing_edge_cases.py

327 lines
11 KiB
Python

from decimal import Decimal
from django.test import TestCase
from django.core.exceptions import ValidationError
from django.utils import timezone
from datetime import timedelta
from ..models.base import Currency, Term, Unit
from ..models.providers import CloudProvider
from ..models.services import Service
from ..models.pricing import (
ComputePlan,
ComputePlanPrice,
StoragePlan,
StoragePlanPrice,
ProgressiveDiscountModel,
DiscountTier,
VSHNAppCatPrice,
VSHNAppCatBaseFee,
VSHNAppCatUnitRate,
VSHNAppCatAddon,
VSHNAppCatAddonBaseFee,
VSHNAppCatAddonUnitRate,
ExternalPricePlans,
)
class PricingEdgeCasesTestCase(TestCase):
"""Test edge cases and error conditions in pricing models"""
def setUp(self):
self.cloud_provider = CloudProvider.objects.create(
name="Test Provider",
slug="test-provider",
description="Test cloud provider",
website="https://test.com",
)
self.service = Service.objects.create(
name="Test Service",
slug="test-service",
description="Test service",
features="Test features",
)
def test_discount_tier_validation_overlapping_ranges(self):
"""Test that overlapping discount tiers can be created (business logic may handle conflicts)"""
discount_model = ProgressiveDiscountModel.objects.create(
name="Test Discount", active=True
)
# Create first tier
DiscountTier.objects.create(
discount_model=discount_model,
min_units=0,
max_units=20,
discount_percent=Decimal("5.00"),
)
# Create overlapping tier - should be allowed at model level
# Business logic in calculate_discount should handle this appropriately
DiscountTier.objects.create(
discount_model=discount_model,
min_units=15,
max_units=30,
discount_percent=Decimal("10.00"),
)
# Should be able to create both tiers
self.assertEqual(discount_model.tiers.count(), 2)
def test_discount_calculation_with_zero_units(self):
"""Test discount calculation with zero units"""
discount_model = ProgressiveDiscountModel.objects.create(
name="Test Discount", active=True
)
DiscountTier.objects.create(
discount_model=discount_model,
min_units=0,
max_units=10,
discount_percent=Decimal("0.00"),
)
result = discount_model.calculate_discount(Decimal("10.00"), 0)
self.assertEqual(result, 0)
def test_discount_calculation_with_very_large_numbers(self):
"""Test discount calculation with very large numbers"""
discount_model = ProgressiveDiscountModel.objects.create(
name="Test Discount", active=True
)
DiscountTier.objects.create(
discount_model=discount_model,
min_units=0,
max_units=None,
discount_percent=Decimal("10.00"),
)
large_units = 1000000
base_rate = Decimal("0.001")
result = discount_model.calculate_discount(base_rate, large_units)
expected = base_rate * Decimal("0.9") * large_units
self.assertEqual(result, expected)
def test_price_calculation_with_inactive_discount_model(self):
"""Test price calculation when discount model is inactive"""
discount_model = ProgressiveDiscountModel.objects.create(
name="Inactive Discount", active=False # Inactive
)
DiscountTier.objects.create(
discount_model=discount_model,
min_units=0,
max_units=None,
discount_percent=Decimal("50.00"), # Large discount
)
price_config = VSHNAppCatPrice.objects.create(
service=self.service,
variable_unit=VSHNAppCatPrice.VariableUnit.RAM,
term=Term.MTH,
discount_model=discount_model,
)
VSHNAppCatBaseFee.objects.create(
vshn_appcat_price_config=price_config,
currency=Currency.CHF,
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
amount=Decimal("50.00"),
)
VSHNAppCatUnitRate.objects.create(
vshn_appcat_price_config=price_config,
currency=Currency.CHF,
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
amount=Decimal("10.0000"),
)
result = price_config.calculate_final_price(
Currency.CHF, VSHNAppCatPrice.ServiceLevel.GUARANTEED, 5
)
# Should ignore discount since model is inactive
expected_total = Decimal("50.00") + (Decimal("10.0000") * 5)
self.assertEqual(result["total_price"], expected_total)
def test_addon_with_missing_prices(self):
"""Test addon behavior when price data is missing"""
price_config = VSHNAppCatPrice.objects.create(
service=self.service,
variable_unit=VSHNAppCatPrice.VariableUnit.RAM,
term=Term.MTH,
)
# Create addon but no corresponding price objects
addon = VSHNAppCatAddon.objects.create(
vshn_appcat_price_config=price_config,
name="Incomplete Addon",
addon_type=VSHNAppCatAddon.AddonType.BASE_FEE,
mandatory=True,
)
# Should return None when price doesn't exist
price = addon.get_price(Currency.CHF)
self.assertIsNone(price)
def test_compute_plan_with_validity_dates(self):
"""Test compute plan with validity date ranges"""
now = timezone.now()
past_date = now - timedelta(days=30)
future_date = now + timedelta(days=30)
# Create plan that is not yet valid
future_plan = ComputePlan.objects.create(
name="Future Plan",
vcpus=4.0,
ram=8.0,
cpu_mem_ratio=0.5,
cloud_provider=self.cloud_provider,
valid_from=future_date,
valid_to=None,
)
# Create plan that has expired
expired_plan = ComputePlan.objects.create(
name="Expired Plan",
vcpus=2.0,
ram=4.0,
cpu_mem_ratio=0.5,
cloud_provider=self.cloud_provider,
valid_from=past_date,
valid_to=past_date + timedelta(days=10),
)
# Plans should still be created and queryable
self.assertTrue(ComputePlan.objects.filter(name="Future Plan").exists())
self.assertTrue(ComputePlan.objects.filter(name="Expired Plan").exists())
def test_decimal_precision_edge_cases(self):
"""Test decimal precision in price calculations"""
price_config = VSHNAppCatPrice.objects.create(
service=self.service,
variable_unit=VSHNAppCatPrice.VariableUnit.RAM,
term=Term.MTH,
)
VSHNAppCatBaseFee.objects.create(
vshn_appcat_price_config=price_config,
currency=Currency.CHF,
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
amount=Decimal("0.01"), # Very small base fee
)
VSHNAppCatUnitRate.objects.create(
vshn_appcat_price_config=price_config,
currency=Currency.CHF,
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
amount=Decimal("0.0001"), # Very small unit rate
)
result = price_config.calculate_final_price(
Currency.CHF,
VSHNAppCatPrice.ServiceLevel.GUARANTEED,
1000, # Large quantity
)
expected_total = Decimal("0.01") + (Decimal("0.0001") * 1000)
self.assertEqual(result["total_price"], expected_total)
def test_external_price_comparison_model(self):
"""Test ExternalPricePlans model functionality"""
compute_plan = ComputePlan.objects.create(
name="Comparison Plan",
vcpus=2.0,
ram=4.0,
cpu_mem_ratio=0.5,
cloud_provider=self.cloud_provider,
)
external_price = ExternalPricePlans.objects.create(
plan_name="Competitor Plan",
description="Competitor offering",
cloud_provider=self.cloud_provider,
service=self.service,
currency=Currency.USD,
term=Term.MTH,
amount=Decimal("99.99"),
vcpus=2.0,
ram=4.0,
storage=50.0,
)
external_price.compare_to.add(compute_plan)
self.assertEqual(
str(external_price), "Test Provider - Test Service - Competitor Plan"
)
self.assertTrue(external_price.compare_to.filter(id=compute_plan.id).exists())
def test_unique_constraints_enforcement(self):
"""Test that unique constraints are properly enforced"""
compute_plan = ComputePlan.objects.create(
name="Test Plan",
vcpus=2.0,
ram=4.0,
cpu_mem_ratio=0.5,
cloud_provider=self.cloud_provider,
)
# Create first price
ComputePlanPrice.objects.create(
compute_plan=compute_plan, currency=Currency.CHF, amount=Decimal("100.00")
)
# Try to create duplicate - should raise IntegrityError
with self.assertRaises(Exception): # IntegrityError or ValidationError
ComputePlanPrice.objects.create(
compute_plan=compute_plan,
currency=Currency.CHF,
amount=Decimal("200.00"),
)
def test_addon_ordering_and_active_filtering(self):
"""Test addon ordering and active status filtering"""
price_config = VSHNAppCatPrice.objects.create(
service=self.service,
variable_unit=VSHNAppCatPrice.VariableUnit.RAM,
term=Term.MTH,
)
# Create addons with different orders and active status
addon1 = VSHNAppCatAddon.objects.create(
vshn_appcat_price_config=price_config,
name="Third Addon",
addon_type=VSHNAppCatAddon.AddonType.BASE_FEE,
order=3,
active=True,
)
addon2 = VSHNAppCatAddon.objects.create(
vshn_appcat_price_config=price_config,
name="First Addon",
addon_type=VSHNAppCatAddon.AddonType.BASE_FEE,
order=1,
active=True,
)
addon3 = VSHNAppCatAddon.objects.create(
vshn_appcat_price_config=price_config,
name="Inactive Addon",
addon_type=VSHNAppCatAddon.AddonType.BASE_FEE,
order=2,
active=False,
)
# Test ordering
ordered_addons = price_config.addons.all().order_by("order", "name")
self.assertEqual(ordered_addons[0], addon2) # order=1
self.assertEqual(ordered_addons[1], addon3) # order=2
self.assertEqual(ordered_addons[2], addon1) # order=3
# Test active filtering
active_addons = price_config.addons.filter(active=True)
self.assertEqual(active_addons.count(), 2)
self.assertNotIn(addon3, active_addons)