223 lines
5.9 KiB
Python
Raw Normal View History

2025-11-10 15:46:40 +01:00
import pytest
2025-11-13 22:53:40 +01:00
import pytest_asyncio
2025-11-10 15:46:40 +01:00
from decimal import Decimal
from datetime import date, datetime
2025-11-13 20:42:43 +01:00
from mywebdav.models import User
2025-11-13 23:22:05 +01:00
from mywebdav.billing.models import (
Invoice,
InvoiceLineItem,
PricingConfig,
UsageAggregate,
UserSubscription,
)
2025-11-13 20:42:43 +01:00
from mywebdav.billing.invoice_generator import InvoiceGenerator
2025-11-10 15:46:40 +01:00
2025-11-13 23:22:05 +01:00
2025-11-13 22:53:40 +01:00
@pytest_asyncio.fixture
2025-11-10 15:46:40 +01:00
async def test_user():
user = await User.create(
username="testuser",
email="test@example.com",
hashed_password="hashed_password_here",
2025-11-13 23:22:05 +01:00
is_active=True,
2025-11-10 15:46:40 +01:00
)
yield user
await user.delete()
2025-11-13 23:22:05 +01:00
2025-11-13 22:53:40 +01:00
@pytest_asyncio.fixture
2025-11-10 15:46:40 +01:00
async def pricing_config():
configs = []
2025-11-13 23:22:05 +01:00
configs.append(
await PricingConfig.create(
config_key="storage_per_gb_month",
config_value=Decimal("0.0045"),
description="Storage cost per GB per month",
unit="per_gb_month",
)
)
configs.append(
await PricingConfig.create(
config_key="bandwidth_egress_per_gb",
config_value=Decimal("0.009"),
description="Bandwidth egress cost per GB",
unit="per_gb",
)
)
configs.append(
await PricingConfig.create(
config_key="free_tier_storage_gb",
config_value=Decimal("15"),
description="Free tier storage in GB",
unit="gb",
)
)
configs.append(
await PricingConfig.create(
config_key="free_tier_bandwidth_gb",
config_value=Decimal("15"),
description="Free tier bandwidth in GB per month",
unit="gb",
)
)
configs.append(
await PricingConfig.create(
config_key="tax_rate_default",
config_value=Decimal("0.0"),
description="Default tax rate",
unit="percentage",
)
)
2025-11-10 15:46:40 +01:00
yield configs
for config in configs:
await config.delete()
2025-11-13 23:22:05 +01:00
2025-11-10 15:46:40 +01:00
@pytest.mark.asyncio
async def test_generate_monthly_invoice_with_usage(test_user, pricing_config):
today = date.today()
await UsageAggregate.create(
user=test_user,
date=today,
2025-11-13 23:22:05 +01:00
storage_bytes_avg=1024**3 * 50,
storage_bytes_peak=1024**3 * 55,
bandwidth_up_bytes=1024**3 * 10,
bandwidth_down_bytes=1024**3 * 20,
2025-11-10 15:46:40 +01:00
)
2025-11-13 23:22:05 +01:00
invoice = await InvoiceGenerator.generate_monthly_invoice(
test_user, today.year, today.month
)
2025-11-10 15:46:40 +01:00
assert invoice is not None
assert invoice.user_id == test_user.id
assert invoice.status == "draft"
assert invoice.total > 0
line_items = await invoice.line_items.all()
assert len(line_items) > 0
await invoice.delete()
await UsageAggregate.filter(user=test_user).delete()
2025-11-13 23:22:05 +01:00
2025-11-10 15:46:40 +01:00
@pytest.mark.asyncio
async def test_generate_monthly_invoice_below_free_tier(test_user, pricing_config):
today = date.today()
await UsageAggregate.create(
user=test_user,
date=today,
2025-11-13 23:22:05 +01:00
storage_bytes_avg=1024**3 * 10,
storage_bytes_peak=1024**3 * 12,
bandwidth_up_bytes=1024**3 * 5,
bandwidth_down_bytes=1024**3 * 10,
2025-11-10 15:46:40 +01:00
)
2025-11-13 23:22:05 +01:00
invoice = await InvoiceGenerator.generate_monthly_invoice(
test_user, today.year, today.month
)
2025-11-10 15:46:40 +01:00
assert invoice is None
await UsageAggregate.filter(user=test_user).delete()
2025-11-13 23:22:05 +01:00
2025-11-10 15:46:40 +01:00
@pytest.mark.asyncio
async def test_finalize_invoice(test_user, pricing_config):
invoice = await Invoice.create(
user=test_user,
invoice_number="INV-000001-202311",
period_start=date(2023, 11, 1),
period_end=date(2023, 11, 30),
subtotal=Decimal("10.00"),
tax=Decimal("0.00"),
total=Decimal("10.00"),
2025-11-13 23:22:05 +01:00
status="draft",
2025-11-10 15:46:40 +01:00
)
finalized = await InvoiceGenerator.finalize_invoice(invoice)
assert finalized.status == "open"
await finalized.delete()
2025-11-13 23:22:05 +01:00
2025-11-10 15:46:40 +01:00
@pytest.mark.asyncio
async def test_finalize_invoice_already_finalized(test_user, pricing_config):
invoice = await Invoice.create(
user=test_user,
invoice_number="INV-000002-202311",
period_start=date(2023, 11, 1),
period_end=date(2023, 11, 30),
subtotal=Decimal("10.00"),
tax=Decimal("0.00"),
total=Decimal("10.00"),
2025-11-13 23:22:05 +01:00
status="open",
2025-11-10 15:46:40 +01:00
)
with pytest.raises(ValueError):
await InvoiceGenerator.finalize_invoice(invoice)
await invoice.delete()
2025-11-13 23:22:05 +01:00
2025-11-10 15:46:40 +01:00
@pytest.mark.asyncio
async def test_mark_invoice_paid(test_user, pricing_config):
invoice = await Invoice.create(
user=test_user,
invoice_number="INV-000003-202311",
period_start=date(2023, 11, 1),
period_end=date(2023, 11, 30),
subtotal=Decimal("10.00"),
tax=Decimal("0.00"),
total=Decimal("10.00"),
2025-11-13 23:22:05 +01:00
status="open",
2025-11-10 15:46:40 +01:00
)
paid = await InvoiceGenerator.mark_invoice_paid(invoice)
assert paid.status == "paid"
assert paid.paid_at is not None
await paid.delete()
2025-11-13 23:22:05 +01:00
2025-11-10 15:46:40 +01:00
@pytest.mark.asyncio
2025-11-13 22:53:40 +01:00
async def test_invoice_with_tax(test_user, pricing_config):
# Update tax rate
2025-11-13 23:22:05 +01:00
updated = await PricingConfig.filter(config_key="tax_rate_default").update(
config_value=Decimal("0.21")
)
2025-11-13 22:53:40 +01:00
assert updated == 1 # Should update 1 row
# Verify the update worked
tax_config = await PricingConfig.get(config_key="tax_rate_default")
assert tax_config.config_value == Decimal("0.21")
2025-11-10 15:46:40 +01:00
today = date.today()
await UsageAggregate.create(
user=test_user,
date=today,
2025-11-13 23:22:05 +01:00
storage_bytes_avg=1024**3 * 50,
storage_bytes_peak=1024**3 * 55,
bandwidth_up_bytes=1024**3 * 10,
bandwidth_down_bytes=1024**3 * 20,
2025-11-10 15:46:40 +01:00
)
2025-11-13 23:22:05 +01:00
invoice = await InvoiceGenerator.generate_monthly_invoice(
test_user, today.year, today.month
)
2025-11-10 15:46:40 +01:00
assert invoice is not None
assert invoice.tax > 0
assert invoice.total == invoice.subtotal + invoice.tax
await invoice.delete()
await UsageAggregate.filter(user=test_user).delete()
2025-11-13 23:22:05 +01:00
await PricingConfig.filter(config_key="tax_rate_default").update(
config_value=Decimal("0.0")
)