Skip to content

Commit

Permalink
calculate invoice tests (#760)
Browse files Browse the repository at this point in the history
  • Loading branch information
diego-escobedo authored Mar 26, 2023
1 parent 69dc488 commit c3492f2
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 15 deletions.
8 changes: 6 additions & 2 deletions backend/metering_billing/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,18 @@ def generate_invoice_pdf_async(invoice_pk):

@shared_task
def calculate_invoice():
calculate_invoice_inner()


def calculate_invoice_inner():
# GENERAL PHILOSOPHY: this task is for periodic maintenance of ending susbcriptions. We only end and re-start subscriptions when they're scheduled to end, if for some other reason they end early then it is up to the other process to handle the invoice creationg and .
# get ending subs

from metering_billing.invoice import generate_invoice
from metering_billing.models import BillingRecord, Invoice, SubscriptionRecord

now_minus_30 = now_utc() + relativedelta(
minutes=-30
now_minus_30 = now_utc() - relativedelta(
minutes=30
) # grace period of 30 minutes for sending events
sub_records_to_bill = SubscriptionRecord.objects.filter(
Q(end_date__lt=now_minus_30)
Expand Down
4 changes: 4 additions & 0 deletions backend/metering_billing/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ def do_add_planversion_to_plan(plan):
charge_behavior=RecurringCharge.ChargeBehaviorType.PRORATE,
amount=30,
pricing_unit=plan_version.currency,
reset_interval_unit=RecurringCharge.IntervalLengthType.MONTH,
reset_interval_count=1,
invoicing_interval_unit=RecurringCharge.IntervalLengthType.MONTH,
invoicing_interval_count=1,
)
return plan_version

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import itertools
import json
import unittest.mock as mock
from datetime import timedelta
from decimal import Decimal

import pytest
from dateutil.relativedelta import relativedelta
from django.urls import reverse
from metering_billing.models import (
BillingRecord,
Expand All @@ -16,6 +18,7 @@
SubscriptionRecord,
)
from metering_billing.serializers.serializer_utils import DjangoJSONEncoder
from metering_billing.tasks import calculate_invoice_inner
from metering_billing.utils import now_utc
from metering_billing.utils.enums import PRICE_ADJUSTMENT_TYPE
from model_bakery import baker
Expand All @@ -24,7 +27,7 @@


@pytest.fixture
def draft_invoice_test_common_setup(
def invoice_test_common_setup(
generate_org_and_api_key,
add_users_to_org,
api_client_with_api_key_auth,
Expand All @@ -34,7 +37,11 @@ def draft_invoice_test_common_setup(
add_plan_version_to_plan,
add_subscription_record_to_org,
):
def do_draft_invoice_test_common_setup(*, auth_method):
def do_invoice_test_common_setup(
*,
auth_method,
make_plan_yearly=False,
):
setup_dict = {}
# set up organizations and api keys
org, key = generate_org_and_api_key()
Expand Down Expand Up @@ -92,6 +99,9 @@ def do_draft_invoice_test_common_setup(*, auth_method):
setup_dict["metrics"] = metric_set
product = add_product_to_org(org)
plan = add_plan_to_product(product)
if make_plan_yearly:
plan.plan_duration = "yearly"
plan.save()
plan_version = add_plan_version_to_plan(plan)
for i, (fmu, cpb, mupb) in enumerate(
zip([50, 0, 1], [5, 0.05, 2], [100, 1, 1])
Expand Down Expand Up @@ -124,13 +134,13 @@ def do_draft_invoice_test_common_setup(*, auth_method):

return setup_dict

return do_draft_invoice_test_common_setup
return do_invoice_test_common_setup


@pytest.mark.django_db(transaction=True)
class TestGenerateInvoice:
def test_generate_invoice(self, draft_invoice_test_common_setup):
setup_dict = draft_invoice_test_common_setup(auth_method="api_key")
def test_generate_invoice(self, invoice_test_common_setup):
setup_dict = invoice_test_common_setup(auth_method="api_key")

prev_invoices_len = Invoice.objects.filter(
payment_status=Invoice.PaymentStatus.DRAFT
Expand All @@ -148,12 +158,10 @@ def test_generate_invoice(self, draft_invoice_test_common_setup):

assert new_invoices_len == prev_invoices_len # don't generate from drafts

def test_generate_invoice_with_price_adjustments(
self, draft_invoice_test_common_setup
):
def test_generate_invoice_with_price_adjustments(self, invoice_test_common_setup):
# deleting inv objects because it marks it as already paid and we get 0s everywhere

setup_dict = draft_invoice_test_common_setup(auth_method="api_key")
setup_dict = invoice_test_common_setup(auth_method="api_key")
Invoice.objects.all().delete()
br = BillingRecord.objects.filter(recurring_charge__isnull=False).first()
br.next_invoicing_date = br.invoicing_dates[0]
Expand Down Expand Up @@ -228,8 +236,8 @@ def test_generate_invoice_with_price_adjustments(
after_cost = response.data["invoices"][0]["amount"]
assert Decimal("20") == after_cost

def test_generate_invoice_with_taxes(self, draft_invoice_test_common_setup):
setup_dict = draft_invoice_test_common_setup(auth_method="api_key")
def test_generate_invoice_with_taxes(self, invoice_test_common_setup):
setup_dict = invoice_test_common_setup(auth_method="api_key")

payload = {
"include_next_period": False,
Expand Down Expand Up @@ -270,8 +278,8 @@ def test_generate_invoice_with_taxes(self, draft_invoice_test_common_setup):
after_cost = response.data["invoices"][0]["amount"]
assert (before_cost * Decimal("1.2") - after_cost) < Decimal("0.01")

def test_generate_invoice_pdf(self, draft_invoice_test_common_setup):
setup_dict = draft_invoice_test_common_setup(auth_method="api_key")
def test_generate_invoice_pdf(self, invoice_test_common_setup):
setup_dict = invoice_test_common_setup(auth_method="api_key")
SubscriptionRecord.objects.all().delete()
Event.objects.all().delete()
payload = {
Expand Down Expand Up @@ -308,3 +316,54 @@ def test_generate_invoice_pdf(self, draft_invoice_test_common_setup):

result_invoice = Invoice.objects.order_by("-invoice_number").first()
assert result_invoice.invoice_pdf != ""


@pytest.mark.django_db(transaction=True)
class TestInvoiceTask:
def test_call_invoice_on_subscription_end(self, invoice_test_common_setup):
setup_dict = invoice_test_common_setup(auth_method="api_key")
mock_date = setup_dict["subscription_record"].end_date + relativedelta(
minutes=30, seconds=1
)
invoices_before = len(Invoice.objects.all())
with (
mock.patch(
"metering_billing.tasks.now_utc",
return_value=mock_date,
),
mock.patch(
"metering_billing.invoice.now_utc",
return_value=mock_date,
),
):
calculate_invoice_inner()
invoices_after = len(Invoice.objects.all())
assert invoices_after == invoices_before + 1

def test_call_invoice_on_intermediate_billing_record(
self, invoice_test_common_setup
):
setup_dict = invoice_test_common_setup(
auth_method="api_key", make_plan_yearly=True
)
sr_start_plus_month = (
setup_dict["subscription_record"].start_date
+ relativedelta(months=1)
+ relativedelta(minutes=30, seconds=1)
)
assert sr_start_plus_month < setup_dict["subscription_record"].end_date
mock_date = sr_start_plus_month + relativedelta(minutes=30)
invoices_before = len(Invoice.objects.all())
with (
mock.patch(
"metering_billing.tasks.now_utc",
return_value=mock_date,
),
mock.patch(
"metering_billing.invoice.now_utc",
return_value=mock_date,
),
):
calculate_invoice_inner()
invoices_after = len(Invoice.objects.all())
assert invoices_after == invoices_before + 1

0 comments on commit c3492f2

Please sign in to comment.