from decimal import Decimal from django.test import TestCase from django.utils import timezone from ..models.base import Currency, Term, Unit from ..models.providers import CloudProvider from ..models.services import Service, Category from ..models.pricing import ( ComputePlan, ComputePlanPrice, ComputePlanGroup, StoragePlan, StoragePlanPrice, ProgressiveDiscountModel, DiscountTier, VSHNAppCatPrice, VSHNAppCatBaseFee, VSHNAppCatUnitRate, VSHNAppCatAddon, VSHNAppCatAddonBaseFee, VSHNAppCatAddonUnitRate, ExternalPricePlans, ) class PricingIntegrationTestCase(TestCase): """Integration tests for pricing models working together""" def setUp(self): """Set up test data for integration tests""" # Create cloud provider self.cloud_provider = CloudProvider.objects.create( name="VSHN Cloud", slug="vshn-cloud", description="Swiss cloud provider", website="https://vshn.ch", is_featured=True, ) # Create service category self.database_category = Category.objects.create( name="Databases", slug="databases", description="Database services" ) # Create database service self.postgresql_service = Service.objects.create( name="PostgreSQL", slug="postgresql", description="Managed PostgreSQL database service", tagline="Reliable, scalable PostgreSQL", features="High availability, automated backups, monitoring", is_featured=True, ) self.postgresql_service.categories.add(self.database_category) # Create compute plan group self.standard_group = ComputePlanGroup.objects.create( name="Standard", description="Standard compute plans", node_label="standard", order=1, ) # Create multiple compute plans self.small_plan = ComputePlan.objects.create( name="Small", vcpus=1.0, ram=2.0, cpu_mem_ratio=0.5, cloud_provider=self.cloud_provider, group=self.standard_group, term=Term.MTH, active=True, ) self.medium_plan = ComputePlan.objects.create( name="Medium", vcpus=2.0, ram=4.0, cpu_mem_ratio=0.5, cloud_provider=self.cloud_provider, group=self.standard_group, term=Term.MTH, active=True, ) self.large_plan = ComputePlan.objects.create( name="Large", vcpus=4.0, ram=8.0, cpu_mem_ratio=0.5, cloud_provider=self.cloud_provider, group=self.standard_group, term=Term.MTH, active=True, ) # Create storage plan self.ssd_storage = StoragePlan.objects.create( name="SSD Storage", cloud_provider=self.cloud_provider, term=Term.MTH, unit=Unit.GIB, ) # Create progressive discount model for AppCat self.ram_discount_model = ProgressiveDiscountModel.objects.create( name="RAM Volume Discount", description="Progressive discount for RAM usage", active=True, ) # Create discount tiers DiscountTier.objects.create( discount_model=self.ram_discount_model, min_units=0, max_units=8, discount_percent=Decimal("0.00"), # 0-7 GiB: no discount ) DiscountTier.objects.create( discount_model=self.ram_discount_model, min_units=8, max_units=32, discount_percent=Decimal("10.00"), # 8-31 GiB: 10% discount ) DiscountTier.objects.create( discount_model=self.ram_discount_model, min_units=32, max_units=None, discount_percent=Decimal("20.00"), # 32+ GiB: 20% discount ) def test_complete_pricing_setup(self): """Test complete pricing setup for all models""" # Set up compute plan prices ComputePlanPrice.objects.create( compute_plan=self.small_plan, currency=Currency.CHF, amount=Decimal("50.00") ) ComputePlanPrice.objects.create( compute_plan=self.medium_plan, currency=Currency.CHF, amount=Decimal("100.00"), ) ComputePlanPrice.objects.create( compute_plan=self.large_plan, currency=Currency.CHF, amount=Decimal("200.00"), ) # Set up storage pricing StoragePlanPrice.objects.create( storage_plan=self.ssd_storage, currency=Currency.CHF, amount=Decimal("0.20"), # 0.20 CHF per GiB ) # Verify all prices are retrievable self.assertEqual(self.small_plan.get_price(Currency.CHF), Decimal("50.00")) self.assertEqual(self.medium_plan.get_price(Currency.CHF), Decimal("100.00")) self.assertEqual(self.large_plan.get_price(Currency.CHF), Decimal("200.00")) self.assertEqual(self.ssd_storage.get_price(Currency.CHF), Decimal("0.20")) def test_multi_currency_pricing(self): """Test pricing in multiple currencies""" # Set up prices in CHF, EUR, and USD currencies_and_rates = [ (Currency.CHF, Decimal("100.00")), (Currency.EUR, Decimal("95.00")), (Currency.USD, Decimal("110.00")), ] for currency, amount in currencies_and_rates: ComputePlanPrice.objects.create( compute_plan=self.medium_plan, currency=currency, amount=amount ) # Verify all currencies are available for currency, expected_amount in currencies_and_rates: self.assertEqual(self.medium_plan.get_price(currency), expected_amount) def test_appcat_service_with_complete_pricing(self): """Test complete AppCat service pricing with all features""" # Create AppCat price configuration appcat_price = VSHNAppCatPrice.objects.create( service=self.postgresql_service, variable_unit=VSHNAppCatPrice.VariableUnit.RAM, term=Term.MTH, discount_model=self.ram_discount_model, ha_replica_min=1, ha_replica_max=3, public_display_enabled=True, ) # Set up base fees for different currencies base_fees = [ (Currency.CHF, Decimal("25.00")), (Currency.EUR, Decimal("22.50")), (Currency.USD, Decimal("27.50")), ] for currency, amount in base_fees: VSHNAppCatBaseFee.objects.create( vshn_appcat_price_config=appcat_price, currency=currency, amount=amount ) # Set up unit rates for different service levels and currencies unit_rates = [ (Currency.CHF, VSHNAppCatPrice.ServiceLevel.BEST_EFFORT, Decimal("3.5000")), (Currency.CHF, VSHNAppCatPrice.ServiceLevel.GUARANTEED, Decimal("5.0000")), (Currency.EUR, VSHNAppCatPrice.ServiceLevel.BEST_EFFORT, Decimal("3.2000")), (Currency.EUR, VSHNAppCatPrice.ServiceLevel.GUARANTEED, Decimal("4.5000")), (Currency.USD, VSHNAppCatPrice.ServiceLevel.BEST_EFFORT, Decimal("3.8000")), (Currency.USD, VSHNAppCatPrice.ServiceLevel.GUARANTEED, Decimal("5.5000")), ] for currency, service_level, amount in unit_rates: VSHNAppCatUnitRate.objects.create( vshn_appcat_price_config=appcat_price, currency=currency, service_level=service_level, amount=amount, ) # Create mandatory addon (backup) backup_addon = VSHNAppCatAddon.objects.create( vshn_appcat_price_config=appcat_price, name="Automated Backup", description="Daily automated backups with 30-day retention", commercial_description="Never lose your data with automated daily backups", addon_type=VSHNAppCatAddon.AddonType.BASE_FEE, mandatory=True, order=1, ) VSHNAppCatAddonBaseFee.objects.create( addon=backup_addon, currency=Currency.CHF, amount=Decimal("15.00") ) # Create optional addon (monitoring) monitoring_addon = VSHNAppCatAddon.objects.create( vshn_appcat_price_config=appcat_price, name="Advanced Monitoring", description="Detailed monitoring with custom alerts", commercial_description="Get insights into your database performance", addon_type=VSHNAppCatAddon.AddonType.UNIT_RATE, mandatory=False, order=2, ) VSHNAppCatAddonUnitRate.objects.create( addon=monitoring_addon, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("0.5000"), ) # Test price calculation scenarios # Scenario 1: Small setup (4 GiB RAM, no discount) result_small = appcat_price.calculate_final_price( Currency.CHF, VSHNAppCatPrice.ServiceLevel.GUARANTEED, 4 ) # Base: 25 + (5 * 4) = 45 # Mandatory backup: 15 # Total: 60 self.assertEqual(result_small["total_price"], Decimal("60.00")) self.assertEqual(result_small["addon_total"], Decimal("15.00")) self.assertEqual(len(result_small["addon_breakdown"]), 1) # Scenario 2: Medium setup (16 GiB RAM, partial discount) result_medium = appcat_price.calculate_final_price( Currency.CHF, VSHNAppCatPrice.ServiceLevel.GUARANTEED, 16 ) # First 8 GiB at full rate: 5 * 8 = 40 # Next 8 GiB at 90% (10% discount): 5 * 0.9 * 8 = 36 # Unit cost: 76 # Base: 25 + 76 = 101 # Mandatory backup: 15 # Total: 116 self.assertEqual(result_medium["total_price"], Decimal("116.00")) # Scenario 3: Large setup with optional addon (40 GiB RAM, full discount tiers) result_large = appcat_price.calculate_final_price( Currency.CHF, VSHNAppCatPrice.ServiceLevel.GUARANTEED, 40, addon_ids=[monitoring_addon.id], ) # First 8 GiB at full rate: 5 * 8 = 40 # Next 24 GiB at 90% (10% discount): 5 * 0.9 * 24 = 108 # Next 8 GiB at 80% (20% discount): 5 * 0.8 * 8 = 32 # Unit cost: 180 # Base: 25 + 180 = 205 # Mandatory backup: 15 # Optional monitoring: 0.5 * 40 = 20 # Total: 240 self.assertEqual(result_large["total_price"], Decimal("240.00")) self.assertEqual(result_large["addon_total"], Decimal("35.00")) self.assertEqual(len(result_large["addon_breakdown"]), 2) def test_external_price_comparison_integration(self): """Test external price comparison with internal pricing""" # Set up internal pricing appcat_price = VSHNAppCatPrice.objects.create( service=self.postgresql_service, variable_unit=VSHNAppCatPrice.VariableUnit.RAM, term=Term.MTH, ) VSHNAppCatBaseFee.objects.create( vshn_appcat_price_config=appcat_price, currency=Currency.USD, amount=Decimal("30.00"), ) VSHNAppCatUnitRate.objects.create( vshn_appcat_price_config=appcat_price, currency=Currency.USD, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("4.0000"), ) # Create external competitor pricing aws_provider = CloudProvider.objects.create( name="AWS", slug="aws", description="Amazon Web Services", website="https://aws.amazon.com", ) external_price = ExternalPricePlans.objects.create( plan_name="RDS PostgreSQL db.t3.medium", description="AWS RDS PostgreSQL instance", source="https://aws.amazon.com/rds/postgresql/pricing/", cloud_provider=aws_provider, service=self.postgresql_service, vshn_appcat_price=appcat_price, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, currency=Currency.USD, term=Term.MTH, amount=Decimal("62.56"), # Monthly cost for db.t3.medium vcpus=2.0, ram=4.0, # 4 GiB RAM storage=20.0, # 20 GiB storage included competitor_sla="99.95%", replicas=1, ) # Compare internal vs external pricing for equivalent setup internal_result = appcat_price.calculate_final_price( Currency.USD, VSHNAppCatPrice.ServiceLevel.GUARANTEED, 4, # 4 GiB RAM to match external offering ) # Internal: 30 + (4 * 4) = 46 USD internal_price = internal_result["total_price"] external_price_amount = external_price.amount self.assertEqual(internal_price, Decimal("46.00")) self.assertEqual(external_price_amount, Decimal("62.56")) # Verify our pricing is competitive self.assertLess(internal_price, external_price_amount) def test_service_availability_with_pricing(self): """Test service availability based on pricing configuration""" # Create service with pricing but not enabled for public display redis_service = Service.objects.create( name="Redis", slug="redis", description="In-memory data store", features="High performance caching", ) redis_price = VSHNAppCatPrice.objects.create( service=redis_service, variable_unit=VSHNAppCatPrice.VariableUnit.RAM, term=Term.MTH, public_display_enabled=False, # Private pricing ) VSHNAppCatBaseFee.objects.create( vshn_appcat_price_config=redis_price, currency=Currency.CHF, amount=Decimal("20.00"), ) # Service should exist but not be publicly available for pricing self.assertFalse(redis_price.public_display_enabled) # Enable public display redis_price.public_display_enabled = True redis_price.save() self.assertTrue(redis_price.public_display_enabled) def test_pricing_model_relationships(self): """Test all pricing model relationships work correctly""" # Verify cloud provider relationships self.assertEqual(self.cloud_provider.compute_plans.count(), 3) self.assertEqual(self.cloud_provider.storage_plans.count(), 1) # Verify service relationships self.assertTrue(hasattr(self.postgresql_service, "vshn_appcat_price")) # Verify compute plan group relationships self.assertEqual(self.standard_group.compute_plans.count(), 3) # Create and verify discount model relationships appcat_price = VSHNAppCatPrice.objects.create( service=self.postgresql_service, variable_unit=VSHNAppCatPrice.VariableUnit.RAM, term=Term.MTH, discount_model=self.ram_discount_model, ) self.assertEqual(self.ram_discount_model.price_configs.count(), 1) self.assertEqual(self.ram_discount_model.tiers.count(), 3) # Test cascade deletions work properly service_id = self.postgresql_service.id appcat_price_id = appcat_price.id # Delete service should cascade to appcat price self.postgresql_service.delete() with self.assertRaises(VSHNAppCatPrice.DoesNotExist): VSHNAppCatPrice.objects.get(id=appcat_price_id) def test_comprehensive_pricing_scenario(self): """Test a comprehensive real-world pricing scenario""" # Company needs PostgreSQL with high availability # Requirements: 16 GiB RAM, automated backups, monitoring, SSL appcat_price = VSHNAppCatPrice.objects.create( service=self.postgresql_service, variable_unit=VSHNAppCatPrice.VariableUnit.RAM, term=Term.MTH, discount_model=self.ram_discount_model, ha_replica_min=2, ha_replica_max=3, public_display_enabled=True, ) # Set up pricing VSHNAppCatBaseFee.objects.create( vshn_appcat_price_config=appcat_price, currency=Currency.CHF, amount=Decimal("40.00"), # Base fee for managed service ) VSHNAppCatUnitRate.objects.create( vshn_appcat_price_config=appcat_price, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("6.0000"), # CHF per GiB RAM ) # Create all required addons backup_addon = VSHNAppCatAddon.objects.create( vshn_appcat_price_config=appcat_price, name="Enterprise Backup", addon_type=VSHNAppCatAddon.AddonType.BASE_FEE, mandatory=True, order=1, ) VSHNAppCatAddonBaseFee.objects.create( addon=backup_addon, currency=Currency.CHF, amount=Decimal("25.00") ) monitoring_addon = VSHNAppCatAddon.objects.create( vshn_appcat_price_config=appcat_price, name="Advanced Monitoring", addon_type=VSHNAppCatAddon.AddonType.UNIT_RATE, mandatory=False, order=2, ) VSHNAppCatAddonUnitRate.objects.create( addon=monitoring_addon, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("0.7500"), ) ssl_addon = VSHNAppCatAddon.objects.create( vshn_appcat_price_config=appcat_price, name="SSL Certificate", addon_type=VSHNAppCatAddon.AddonType.BASE_FEE, mandatory=False, order=3, ) VSHNAppCatAddonBaseFee.objects.create( addon=ssl_addon, currency=Currency.CHF, amount=Decimal("18.00") ) # Calculate final price with all selected addons result = appcat_price.calculate_final_price( Currency.CHF, VSHNAppCatPrice.ServiceLevel.GUARANTEED, 16, # 16 GiB RAM addon_ids=[monitoring_addon.id, ssl_addon.id], ) # Expected calculation: # Base fee: 40.00 # RAM cost: First 8 at 6.00 = 48.00, Next 8 at 5.40 (10% discount) = 43.20 # RAM total: 91.20 # Mandatory backup: 25.00 # Optional monitoring: 0.75 * 16 = 12.00 # Optional SSL: 18.00 # Total: 40.00 + 91.20 + 25.00 + 12.00 + 18.00 = 186.20 self.assertEqual(result["total_price"], Decimal("186.20")) self.assertEqual(result["addon_total"], Decimal("55.00")) self.assertEqual(len(result["addon_breakdown"]), 3) # Verify addon breakdown details addon_names = [addon["name"] for addon in result["addon_breakdown"]] self.assertIn("Enterprise Backup", addon_names) self.assertIn("Advanced Monitoring", addon_names) self.assertIn("SSL Certificate", addon_names)