296 lines
11 KiB
YAML
296 lines
11 KiB
YAML
name: PR Pricing Validation
|
||
|
||
on:
|
||
pull_request:
|
||
types: [opened, synchronize, reopened]
|
||
paths:
|
||
- "hub/services/models/pricing.py"
|
||
- "hub/services/tests/test_pricing*.py"
|
||
- "hub/services/views/**"
|
||
- "hub/services/forms.py"
|
||
- "hub/services/admin/**"
|
||
|
||
jobs:
|
||
pricing-validation:
|
||
name: Validate Pricing Changes
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- name: Checkout PR branch
|
||
uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: Set up Python
|
||
uses: actions/setup-python@v4
|
||
with:
|
||
python-version: "3.13"
|
||
|
||
- name: Install uv
|
||
uses: astral-sh/setup-uv@v3
|
||
with:
|
||
enable-cache: true
|
||
cache-dependency-glob: "uv.lock"
|
||
|
||
- name: Install dependencies
|
||
run: |
|
||
uv sync --extra dev
|
||
|
||
- name: Check for pricing model migrations
|
||
run: |
|
||
echo "::group::Checking for required database migrations"
|
||
|
||
# Check if pricing models were changed
|
||
if git diff --name-only origin/main...HEAD | grep -q "hub/services/models/pricing.py"; then
|
||
echo "📝 Pricing models were modified, checking for migrations..."
|
||
|
||
# Check if there are new migration files
|
||
if git diff --name-only origin/main...HEAD | grep -q "hub/services/migrations/"; then
|
||
echo "✅ Found migration files in the PR"
|
||
git diff --name-only origin/main...HEAD | grep "hub/services/migrations/" | head -5
|
||
else
|
||
echo "⚠️ Pricing models were changed but no migrations found"
|
||
echo "Please run: uv run --extra dev manage.py makemigrations"
|
||
echo "This will be treated as a warning, not a failure"
|
||
fi
|
||
else
|
||
echo "ℹ️ No pricing model changes detected"
|
||
fi
|
||
echo "::endgroup::"
|
||
|
||
- name: Run pricing tests with coverage
|
||
env:
|
||
DJANGO_SETTINGS_MODULE: hub.settings
|
||
run: |
|
||
echo "::group::Running pricing tests with coverage tracking"
|
||
|
||
# Run tests with coverage
|
||
uv run coverage run --source='hub/services/models/pricing,hub/services/views' \
|
||
manage.py test \
|
||
hub.services.tests.test_pricing \
|
||
hub.services.tests.test_pricing_edge_cases \
|
||
hub.services.tests.test_pricing_integration \
|
||
--verbosity=2
|
||
|
||
# Generate coverage report
|
||
uv run coverage report --show-missing --fail-under=85
|
||
|
||
# Generate HTML coverage report
|
||
uv run coverage html
|
||
|
||
echo "::endgroup::"
|
||
|
||
- name: Upload coverage report
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: pr-pricing-coverage
|
||
path: htmlcov/
|
||
retention-days: 7
|
||
|
||
- name: Detect pricing calculation changes
|
||
run: |
|
||
echo "::group::Analyzing pricing calculation changes"
|
||
|
||
# Check if critical pricing methods were modified
|
||
CRITICAL_METHODS=(
|
||
"calculate_discount"
|
||
"calculate_final_price"
|
||
"get_price"
|
||
"get_unit_rate"
|
||
"get_base_fee"
|
||
)
|
||
|
||
echo "🔍 Checking for changes to critical pricing methods..."
|
||
|
||
changed_methods=()
|
||
for method in "${CRITICAL_METHODS[@]}"; do
|
||
if git diff origin/main...HEAD -- hub/services/models/pricing.py | grep -q "def $method"; then
|
||
changed_methods+=("$method")
|
||
echo "⚠️ Critical method '$method' was modified"
|
||
fi
|
||
done
|
||
|
||
if [ ${#changed_methods[@]} -gt 0 ]; then
|
||
echo ""
|
||
echo "🚨 CRITICAL PRICING METHODS CHANGED:"
|
||
printf ' - %s\n' "${changed_methods[@]}"
|
||
echo ""
|
||
echo "📋 Extra validation required:"
|
||
echo " 1. All pricing tests must pass"
|
||
echo " 2. Manual testing of price calculations recommended"
|
||
echo " 3. Consider adding regression tests for specific scenarios"
|
||
echo ""
|
||
echo "This will not fail the build but requires careful review."
|
||
else
|
||
echo "✅ No critical pricing methods were modified"
|
||
fi
|
||
|
||
echo "::endgroup::"
|
||
|
||
- name: Validate test additions
|
||
run: |
|
||
echo "::group::Validating test additions for pricing changes"
|
||
|
||
# Check if new pricing features have corresponding tests
|
||
python3 << 'EOF'
|
||
import subprocess
|
||
import re
|
||
|
||
def get_git_diff():
|
||
result = subprocess.run(
|
||
['git', 'diff', 'origin/main...HEAD', '--', 'hub/services/models/pricing.py'],
|
||
capture_output=True, text=True
|
||
)
|
||
return result.stdout
|
||
|
||
def get_test_diff():
|
||
result = subprocess.run(
|
||
['git', 'diff', 'origin/main...HEAD', '--', 'hub/services/tests/test_pricing*.py'],
|
||
capture_output=True, text=True
|
||
)
|
||
return result.stdout
|
||
|
||
pricing_diff = get_git_diff()
|
||
test_diff = get_test_diff()
|
||
|
||
# Look for new methods in pricing models
|
||
new_methods = re.findall(r'^\+\s*def\s+(\w+)', pricing_diff, re.MULTILINE)
|
||
new_classes = re.findall(r'^\+class\s+(\w+)', pricing_diff, re.MULTILINE)
|
||
|
||
# Look for new test methods
|
||
new_test_methods = re.findall(r'^\+\s*def\s+(test_\w+)', test_diff, re.MULTILINE)
|
||
|
||
print("📊 Analysis of pricing changes:")
|
||
if new_classes:
|
||
print(f" New classes: {', '.join(new_classes)}")
|
||
if new_methods:
|
||
print(f" New methods: {', '.join(new_methods)}")
|
||
if new_test_methods:
|
||
print(f" New test methods: {', '.join(new_test_methods)}")
|
||
|
||
if (new_classes or new_methods) and not new_test_methods:
|
||
print("⚠️ New pricing functionality detected but no new tests found")
|
||
print(" Consider adding tests for new features")
|
||
elif new_test_methods:
|
||
print("✅ New tests found alongside pricing changes")
|
||
else:
|
||
print("ℹ️ No new pricing functionality detected")
|
||
EOF
|
||
|
||
echo "::endgroup::"
|
||
|
||
- name: Run backward compatibility check
|
||
env:
|
||
DJANGO_SETTINGS_MODULE: hub.settings
|
||
run: |
|
||
echo "::group::Checking backward compatibility of pricing changes"
|
||
|
||
# Create a simple backward compatibility test
|
||
cat << 'EOF' > check_compatibility.py
|
||
import os
|
||
import django
|
||
from decimal import Decimal
|
||
|
||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hub.settings')
|
||
django.setup()
|
||
|
||
from hub.services.models.base import Currency, Term
|
||
from hub.services.models.providers import CloudProvider
|
||
from hub.services.models.services import Service
|
||
from hub.services.models.pricing import VSHNAppCatPrice, VSHNAppCatBaseFee, VSHNAppCatUnitRate
|
||
|
||
print("🔄 Testing backward compatibility of pricing API...")
|
||
|
||
try:
|
||
# Test basic model creation (should work with existing API)
|
||
provider = CloudProvider.objects.create(
|
||
name="BC Test", slug="bc-test", description="Test", website="https://test.com"
|
||
)
|
||
service = Service.objects.create(
|
||
name="BC Service", slug="bc-service", description="Test", features="Test"
|
||
)
|
||
|
||
price_config = VSHNAppCatPrice.objects.create(
|
||
service=service,
|
||
variable_unit=VSHNAppCatPrice.VariableUnit.RAM,
|
||
term=Term.MTH
|
||
)
|
||
|
||
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('5.0000')
|
||
)
|
||
|
||
# Test basic price calculation
|
||
result = price_config.calculate_final_price(Currency.CHF, 'GA', 4)
|
||
|
||
if result and 'total_price' in result:
|
||
print(f"✅ Basic price calculation works: {result['total_price']} CHF")
|
||
else:
|
||
print("❌ Price calculation API may have changed")
|
||
exit(1)
|
||
|
||
# Test price retrieval methods
|
||
base_fee = price_config.get_base_fee(Currency.CHF)
|
||
unit_rate = price_config.get_unit_rate(Currency.CHF, 'GA')
|
||
|
||
if base_fee and unit_rate:
|
||
print("✅ Price retrieval methods work correctly")
|
||
else:
|
||
print("❌ Price retrieval API may have changed")
|
||
exit(1)
|
||
|
||
print("🎉 Backward compatibility check passed!")
|
||
|
||
except Exception as e:
|
||
print(f"❌ Backward compatibility issue detected: {e}")
|
||
exit(1)
|
||
EOF
|
||
|
||
uv run python check_compatibility.py
|
||
echo "::endgroup::"
|
||
|
||
- name: Generate pricing test summary
|
||
if: always()
|
||
run: |
|
||
echo "::group::Pricing Test Summary"
|
||
|
||
echo "## 🧮 Pricing Test Results" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
|
||
# Count test files and methods
|
||
total_test_files=$(find hub/services/tests -name "test_pricing*.py" | wc -l)
|
||
total_test_methods=$(grep -r "def test_" hub/services/tests/test_pricing*.py | wc -l)
|
||
|
||
echo "- **Test Files**: $total_test_files pricing-specific test files" >> $GITHUB_STEP_SUMMARY
|
||
echo "- **Test Methods**: $total_test_methods individual test methods" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
|
||
# Check if any pricing files were changed
|
||
if git diff --name-only origin/main...HEAD | grep -q "pricing"; then
|
||
echo "### 📝 Pricing-Related Changes Detected" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "The following pricing-related files were modified:" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
git diff --name-only origin/main...HEAD | grep "pricing" | sed 's/^/- /' >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "✅ All pricing tests have been executed to validate these changes." >> $GITHUB_STEP_SUMMARY
|
||
else
|
||
echo "### ℹ️ No Pricing Changes" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "No pricing-related files were modified in this PR." >> $GITHUB_STEP_SUMMARY
|
||
fi
|
||
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||
echo "*Pricing validation completed at $(date)*" >> $GITHUB_STEP_SUMMARY
|
||
|
||
echo "::endgroup::"
|