hosted/wlhosted/integrations/tests.py
2023-01-10 09:50:56 +01:00

369 lines
14 KiB
Python

#
# Copyright © Michal Čihař <michal@weblate.org>
#
# This file is part of Weblate <https://weblate.org/>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
from time import sleep
import httpretty
from dateutil.relativedelta import relativedelta
from django.core import mail
from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse
from django.utils import timezone
from weblate.billing.models import Billing, Invoice, Plan
from weblate.trans.tests.utils import create_test_user
from wlhosted.integrations.tasks import pending_payments, recurring_payments
from wlhosted.payments.backends import get_backend
from wlhosted.payments.models import Customer, Payment
from wlhosted.payments.tests import setup_dirs
class PaymentTest(TestCase):
databases = "__all__"
def setUp(self):
Payment.objects.all().delete()
Customer.objects.all().delete()
self.user = create_test_user()
self.client.login(username="testuser", password="testpassword")
self.plan_a = Plan.objects.create(
name="Plan A", slug="plan-a", price=19, yearly_price=199, public=True
)
self.plan_b = Plan.objects.create(
name="Plan B", slug="plan-b", price=49, yearly_price=499, public=True
)
self.plan_c = Plan.objects.create(
name="Plan C", slug="plan-c", price=9, yearly_price=99, public=False
)
self.plan_d = Plan.objects.create(
name="Plan D", slug="plan-d", price=0, yearly_price=0, public=True
)
setup_dirs()
@override_settings(PAYMENT_REDIRECT_URL="http://example.com/payment")
def create_payment(self, **kwargs):
params = {"plan": self.plan_a.id, "period": "y"}
params.update(kwargs)
response = self.client.post(reverse("create-billing"), params)
self.assertRedirects(
response, "http://example.com/payment", fetch_redirect_response=False
)
def create_trial(self):
bill = Billing.objects.create(state=Billing.STATE_TRIAL, plan=self.plan_b)
bill.owners.add(self.user)
return bill
def test_create(self):
response = self.client.get(reverse("create-billing"))
self.assertContains(response, "Plan A")
self.assertContains(response, "Plan B")
self.assertNotContains(response, "Plan C")
self.assertNotContains(response, "Plan D")
self.create_payment(period="y")
self.assertEqual(Payment.objects.count(), 1)
self.assertEqual(Customer.objects.count(), 1)
payment = Payment.objects.all()[0]
self.assertEqual(payment.amount, self.plan_a.yearly_price)
self.assertEqual(payment.extra, {"plan": self.plan_a.pk, "period": "y"})
self.create_payment(period="m")
self.assertEqual(Payment.objects.count(), 2)
self.assertEqual(Customer.objects.count(), 1)
payment = Payment.objects.exclude(uuid=payment.uuid)[0]
self.assertEqual(payment.amount, self.plan_a.price)
def test_pending_payments(self):
self.test_create()
Payment.objects.all().update(state=Payment.ACCEPTED)
pending_payments()
self.assertFalse(Payment.objects.filter(state=Payment.ACCEPTED).exists())
def test_existing_billing(self):
bill = self.create_trial()
bill.removal = timezone.now()
bill.save(update_fields=["removal"])
bill_args = {"billing": bill.pk}
# Test default selection
response = self.client.get(reverse("create-billing"))
self.assertContains(response, "Trial")
# Test manual selection
response = self.client.get(reverse("create-billing"), bill_args)
self.assertContains(response, "Trial")
# Test invalid selection
response = self.client.get(reverse("create-billing"), {"billing": "x"})
self.assertNotContains(response, "Trial")
# Create payment for billing
self.create_payment(**bill_args)
payment = Payment.objects.all()[0]
bill_args["plan"] = self.plan_a.pk
bill_args["period"] = "y"
# The billing should be stored in the payment
self.assertEqual(payment.extra, bill_args)
# Accept the payment
Payment.objects.all().update(state=Payment.ACCEPTED)
pending_payments()
# User should get notification that project scheduled for removal is now paid
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "[Weblate] Your billing plan was paid")
def test_error_handling(self):
response = self.client.post(reverse("create-billing"))
self.assertContains(response, "This field is required")
with override_settings(PAYMENT_ENABLED=False):
response = self.client.post(
reverse("create-billing"), {"plan": self.plan_a.id, "period": "y"}
)
self.assertRedirects(response, reverse("create-billing"))
@override_settings(PAYMENT_REDIRECT_URL="http://example.com/payment")
def test_payment_redirects(self):
# Invalid UUID
self.assertRedirects(
self.client.get(reverse("create-billing"), {"payment": "i"}),
reverse("create-billing"),
)
self.create_payment()
payment = Payment.objects.all()[0]
bill_url = reverse("billing")
create_url = reverse("create-billing")
pay_url = "http://example.com/payment"
params = create_url, {"payment": payment.uuid}
# New should redirect to payment interface
self.assertRedirects(
self.client.get(*params), pay_url, fetch_redirect_response=False
)
# Pending should redirect to billings
payment.state = Payment.PENDING
payment.save()
self.assertRedirects(self.client.get(*params), bill_url)
# Accepted should redirect to billings
payment.state = Payment.ACCEPTED
payment.save()
response = self.client.get(*params, follow=True)
bill_url = Billing.objects.get().get_absolute_url()
self.assertRedirects(response, bill_url)
# Processed should redirect to billings
payment.state = Payment.PROCESSED
payment.save()
self.assertRedirects(self.client.get(*params, follow=True), bill_url)
# Rejected should redirect to create
payment.state = Payment.REJECTED
payment.save()
self.assertRedirects(self.client.get(*params), create_url)
# Non existing should redirect to create
payment.delete()
self.assertRedirects(self.client.get(*params), create_url)
def do_complete(self, **kwargs):
self.create_payment(**kwargs)
payment = Payment.objects.all()[0]
payment.state = Payment.ACCEPTED
payment.save()
response = self.client.get(
reverse("create-billing"), {"payment": payment.uuid}, follow=True
)
if "billing" in kwargs:
billing = Billing.objects.get(pk=kwargs["billing"])
else:
billing = Billing.objects.all()[0]
self.assertRedirects(response, billing.get_absolute_url())
return billing
def test_complete(self):
bill = self.do_complete()
self.assertEqual(bill.state, Billing.STATE_ACTIVE)
self.assertEqual(bill.plan, self.plan_a)
def test_complete_monthly(self):
self.do_complete(period="m")
bill = Billing.objects.all()[0]
self.assertEqual(bill.state, Billing.STATE_ACTIVE)
self.assertEqual(bill.plan, self.plan_a)
def test_complete_trial(self):
bill = self.create_trial()
bill = self.do_complete(billing=bill.pk)
self.assertEqual(bill.state, Billing.STATE_ACTIVE)
self.assertEqual(bill.plan, self.plan_a)
def test_complete_second(self):
bill = self.create_trial()
now = timezone.now()
Invoice.objects.create(
billing=bill, start=now, end=now + relativedelta(months=1), amount=10
)
old_i = bill.invoice_set.all()[0]
bill = self.do_complete(billing=bill.pk)
self.assertEqual(bill.state, Billing.STATE_ACTIVE)
self.assertEqual(bill.plan, self.plan_a)
self.assertEqual(bill.invoice_set.count(), 2)
new_i = bill.invoice_set.exclude(pk=old_i.pk)[0]
self.assertLess(old_i.end, new_i.start)
def prepare_recurring(self, method):
self.create_payment(period="y")
payment = Payment.objects.all()[0]
# Complete the payment
backend = get_backend(method)(payment)
backend.initiate(None, "", "")
Customer.objects.update(
name="Michal Čihař",
address="Zdiměřická 1439",
city="149 00 Praha 4",
country="CZ",
vat="CZ8003280318",
)
backend.complete(None)
response = self.client.get(
reverse("create-billing"), {"payment": payment.uuid}, follow=True
)
billing = Billing.objects.all()[0]
self.assertRedirects(response, billing.get_absolute_url())
# Check recurrence is stored
bill = Billing.objects.all()[0]
invoices = bill.invoice_set.count()
# Fake end of last invoice
last_invoice = bill.invoice_set.order_by("-start")[0]
last_invoice.end = timezone.now() - relativedelta(days=7)
last_invoice.save()
return payment, bill, invoices
def run_recurring(self):
# Invoke recurring payment
httpretty.register_uri(httpretty.POST, "http://example.com/payment", body="")
recurring_payments()
@override_settings(
PAYMENT_DEBUG=True, PAYMENT_REDIRECT_URL="http://example.com/payment"
)
@httpretty.activate
def test_recurring(self):
"""Test recurring payments."""
payment, bill, invoices = self.prepare_recurring("pay")
self.assertEqual(bill.payment["recurring"], str(payment.pk))
self.run_recurring()
# Complete the payment (we've faked the payment server above)
recure_payment = Payment.objects.exclude(pk=payment.pk)[0]
backend = get_backend("pay")(recure_payment)
backend.initiate(None, "", "")
backend.complete(None)
# Process pending payments
pending_payments()
# There should be additional invoice on the billing
self.assertEqual(invoices + 1, bill.invoice_set.count())
@override_settings(PAYMENT_DEBUG=True)
def test_recurring_none(self):
"""Test method without support for recurring payments."""
# The pending method does not support recurring payments
payment, bill, _invoices = self.prepare_recurring("pending")
self.assertNotIn("recurring", bill.payment)
self.run_recurring()
# There should be no new payment
self.assertFalse(Payment.objects.exclude(pk=payment.pk).exists())
@override_settings(PAYMENT_DEBUG=True)
def test_recurring_invalid(self):
"""Test handling of invalid (removed) method."""
payment, bill, _invoices = self.prepare_recurring("pay")
self.assertEqual(bill.payment["recurring"], str(payment.pk))
# Fake payment menthod
payment.details["backend"] = "invalid"
payment.save()
self.run_recurring()
# There should be no new payment
self.assertFalse(Payment.objects.exclude(pk=payment.pk).exists())
# Recurrence should be disabled
bill = Billing.objects.get(pk=bill.pk)
self.assertNotIn("recurring", bill.payment)
@override_settings(
PAYMENT_DEBUG=True, PAYMENT_REDIRECT_URL="http://example.com/payment"
)
@httpretty.activate
def test_recurring_one_error(self):
"""Test handling of single failed recurring payments."""
payment, bill, invoices = self.prepare_recurring("pay")
self.assertEqual(bill.payment["recurring"], str(payment.pk))
Payment.objects.create(
repeat=payment, customer=payment.customer, state=Payment.REJECTED, amount=1
)
self.run_recurring()
# Complete the payment (we've faked the payment server above)
recure_payment = Payment.objects.exclude(pk=payment.pk).exclude(amount=1)[0]
backend = get_backend("pay")(recure_payment)
backend.initiate(None, "", "")
backend.complete(None)
# Process pending payments
pending_payments()
# There should be additional invoice on the billing
self.assertEqual(invoices + 1, bill.invoice_set.count())
@override_settings(PAYMENT_DEBUG=True)
def test_recurring_more_error(self):
"""Test handling of more failed recurring payments."""
payment, bill, _invoices = self.prepare_recurring("pay")
self.assertEqual(bill.payment["recurring"], str(payment.pk))
Payment.objects.create(
repeat=payment, customer=payment.customer, state=Payment.PROCESSED, amount=1
)
# Ensure rest is after procesed one
sleep(1)
Payment.objects.create(
repeat=payment, customer=payment.customer, state=Payment.REJECTED, amount=1
)
Payment.objects.create(
repeat=payment, customer=payment.customer, state=Payment.REJECTED, amount=1
)
Payment.objects.create(
repeat=payment, customer=payment.customer, state=Payment.REJECTED, amount=1
)
self.run_recurring()
# There should be no new payment
self.assertFalse(
Payment.objects.exclude(pk=payment.pk).exclude(amount=1).exists()
)
# Recurrence should be disabled
bill = Billing.objects.get(pk=bill.pk)
self.assertNotIn("recurring", bill.payment)