|
import pytest
|
|
import pytest_asyncio
|
|
from decimal import Decimal
|
|
from datetime import date, datetime
|
|
from mywebdav.models import User
|
|
from mywebdav.billing.models import Invoice, InvoiceLineItem, PricingConfig, UsageAggregate, UserSubscription
|
|
from mywebdav.billing.invoice_generator import InvoiceGenerator
|
|
|
|
@pytest_asyncio.fixture
|
|
async def test_user():
|
|
user = await User.create(
|
|
username="testuser",
|
|
email="test@example.com",
|
|
hashed_password="hashed_password_here",
|
|
is_active=True
|
|
)
|
|
yield user
|
|
await user.delete()
|
|
|
|
@pytest_asyncio.fixture
|
|
async def pricing_config():
|
|
configs = []
|
|
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"
|
|
))
|
|
yield configs
|
|
for config in configs:
|
|
await config.delete()
|
|
|
|
@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,
|
|
storage_bytes_avg=1024 ** 3 * 50,
|
|
storage_bytes_peak=1024 ** 3 * 55,
|
|
bandwidth_up_bytes=1024 ** 3 * 10,
|
|
bandwidth_down_bytes=1024 ** 3 * 20
|
|
)
|
|
|
|
invoice = await InvoiceGenerator.generate_monthly_invoice(test_user, today.year, today.month)
|
|
|
|
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()
|
|
|
|
@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,
|
|
storage_bytes_avg=1024 ** 3 * 10,
|
|
storage_bytes_peak=1024 ** 3 * 12,
|
|
bandwidth_up_bytes=1024 ** 3 * 5,
|
|
bandwidth_down_bytes=1024 ** 3 * 10
|
|
)
|
|
|
|
invoice = await InvoiceGenerator.generate_monthly_invoice(test_user, today.year, today.month)
|
|
|
|
assert invoice is None
|
|
|
|
await UsageAggregate.filter(user=test_user).delete()
|
|
|
|
@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"),
|
|
status="draft"
|
|
)
|
|
|
|
finalized = await InvoiceGenerator.finalize_invoice(invoice)
|
|
|
|
assert finalized.status == "open"
|
|
|
|
await finalized.delete()
|
|
|
|
@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"),
|
|
status="open"
|
|
)
|
|
|
|
with pytest.raises(ValueError):
|
|
await InvoiceGenerator.finalize_invoice(invoice)
|
|
|
|
await invoice.delete()
|
|
|
|
@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"),
|
|
status="open"
|
|
)
|
|
|
|
paid = await InvoiceGenerator.mark_invoice_paid(invoice)
|
|
|
|
assert paid.status == "paid"
|
|
assert paid.paid_at is not None
|
|
|
|
await paid.delete()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invoice_with_tax(test_user, pricing_config):
|
|
# Update tax rate
|
|
updated = await PricingConfig.filter(config_key="tax_rate_default").update(config_value=Decimal("0.21"))
|
|
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")
|
|
|
|
today = date.today()
|
|
|
|
await UsageAggregate.create(
|
|
user=test_user,
|
|
date=today,
|
|
storage_bytes_avg=1024 ** 3 * 50,
|
|
storage_bytes_peak=1024 ** 3 * 55,
|
|
bandwidth_up_bytes=1024 ** 3 * 10,
|
|
bandwidth_down_bytes=1024 ** 3 * 20
|
|
)
|
|
|
|
invoice = await InvoiceGenerator.generate_monthly_invoice(test_user, today.year, today.month)
|
|
|
|
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()
|
|
await PricingConfig.filter(config_key="tax_rate_default").update(config_value=Decimal("0.0"))
|