297 lines
11 KiB
YAML
297 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::"
|