tests and actions
Some checks failed
Pricing Tests / Pricing Model Tests (push) Failing after 4s
Pricing Tests / Pricing Documentation Check (push) Failing after 3s

This commit is contained in:
Tobias Brunner 2025-06-20 10:46:11 +02:00
parent c05feb37d3
commit 78f52ea7f4
No known key found for this signature in database
17 changed files with 4140 additions and 3 deletions

View file

@ -0,0 +1,532 @@
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)