tests and actions
This commit is contained in:
parent
c05feb37d3
commit
78f52ea7f4
17 changed files with 4140 additions and 3 deletions
325
hub/services/tests/test_pricing_edge_cases.py
Normal file
325
hub/services/tests/test_pricing_edge_cases.py
Normal file
|
@ -0,0 +1,325 @@
|
|||
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,
|
||||
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,
|
||||
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)
|
Loading…
Add table
Add a link
Reference in a new issue