svthalia/concrexit

View on GitHub
website/payments/tests/test_payables.py

Summary

Maintainability
A
0 mins
Test Coverage
from unittest.mock import MagicMock

from django.core.exceptions import ObjectDoesNotExist
from django.test import TestCase

from payments.models import Payment
from payments.payables import (
    NotRegistered,
    Payable,
    PaymentError,
    payables,
    prevent_saving,
    prevent_saving_related,
)
from payments.tests.__mocks__ import MockModel, MockPayable


class PayablesTest(TestCase):
    def test_registered_payable(self):
        payables.register(MockModel, MockPayable)
        self.assertIsInstance(payables.get_payable(MockModel), MockPayable)
        payables._unregister(MockModel)

    def test_not_registered_payable(self):
        with self.assertRaises(NotRegistered):
            payables.get_payable(MockModel)


class ImmutablePayablesTest(TestCase):
    def setUp(self):
        payables.register(MockModel, MockPayable)

    def tearDown(self):
        payables._unregister(MockModel)

    def test_register(self):
        payables._unregister(MockModel)
        with self.subTest("Register not immutable"):
            MockPayable.immutable_after_payment = False
            payables.register(MockModel, MockPayable)

        payables._unregister(MockModel)
        with self.subTest("Register immutable"):
            MockPayable.immutable_after_payment = True
            MockPayable.immutable_model_fields_after_payment = [
                "test_field",
            ]
            payables.register(MockModel, MockPayable)

    def test_prevent_unlinking_payment_from_payable(self):
        model_before = MockModel(payer=None)
        model_after = MockModel(payer=None)
        model_before.payment = Payment.objects.create(
            type=Payment.CARD, amount=2, notes="test payment", topic="test topic"
        )
        MockPayable.immutable_after_payment = True
        MockPayable.immutable_model_fields_after_payment = [
            "test_field",
        ]
        MockPayable.get_payment = MagicMock(return_value=model_before.payment)
        MockModel.objects.get = MagicMock(return_value=model_before)

        with self.subTest("Unlinking a payment"):
            model_after.payment = None
            self.assertRaises(PaymentError, prevent_saving, MockModel, model_after)

        with self.subTest("Changing an immutable model field after payment"):
            model_after.payment = model_before.payment
            model_after.test_field = "changed"
            self.assertRaises(PaymentError, prevent_saving, MockModel, model_after)

    def test_prevent_saving_no_existing_model(self):
        model = MockModel(payer=None)
        model.pk = None
        prevent_saving(MockModel, model)  # this should not crash

    def test_prevent_saving_not_immutable(self):
        MockPayable.immutable_after_payment = False
        model = MockModel(payer=None)
        model.pk = 1
        prevent_saving(MockModel, model)  # this should not crash

    def test_prevent_saving_model_with_pk_but_not_in_db(self):
        MockPayable.immutable_after_payment = True
        model = MockModel(payer=None)
        model.payment = Payment.objects.create(
            type=Payment.CARD, amount=2, notes="test payment", topic="test topic"
        )
        MockPayable.get_payment = MagicMock(return_value=model.payment)
        MockModel.DoesNotExist = ObjectDoesNotExist
        MockModel.objects.get = MagicMock(side_effect=MockModel.DoesNotExist)
        model.pk = 1
        prevent_saving(MockModel, model)  # this should not crash

    def test_allow_adding_a_payment_to_unpaid_model(self):
        MockPayable.immutable_after_payment = True
        model_before = MockModel(payer=None)
        model_before.payment = None

        model_after = MockModel(payer=None)
        model_after.payment = Payment.objects.create(
            type=Payment.CARD, amount=2, notes="test payment", topic="test topic"
        )

        MockModel.objects.get = MagicMock(return_value=model_before)
        prevent_saving(MockModel, model_after)

    def test_mutable_model(self):
        payable = payables.get_payable(MockModel)
        MockPayable.immutable_after_payment = False
        self.assertFalse(payable.immutable_after_payment)
        MockPayable.immutable_after_payment = True
        self.assertTrue(payable.immutable_after_payment)

        self.assertFalse(Payable.immutable_after_payment)

    def test_immutable_fields(self):
        payable = payables.get_payable(MockModel)
        MockPayable.immutable_model_fields_after_payment = ["test1", "test2"]
        self.assertEqual(
            ["test1", "test2"], payable.immutable_model_fields_after_payment
        )
        MockPayable.immutable_model_fields_after_payment = []
        self.assertEqual([], payable.immutable_model_fields_after_payment)

        self.assertEqual([], Payable.immutable_model_fields_after_payment)

    def test_prevent_saving_changed_related_model_field(self):
        MockModel2 = MockModel  # noqa: N806
        MockPayable.immutable_after_payment = True
        MockPayable.immutable_model_fields_after_payment = {MockModel2: ["test_field"]}
        model = MockModel(payer=None)
        model.payment = Payment.objects.create(
            type=Payment.CARD, amount=2, notes="test payment", topic="test topic"
        )

        related_model_before = MockModel2(payer=None)
        related_model_before.test_related_field = model
        related_model_before.test_field = "test"

        related_model_after = MockModel2(payer=None)
        related_model_after.test_related_field = model
        related_model_after.test_field = "changed"

        MockPayable.immutable_after_payment = True
        MockPayable.immutable_foreign_key_models = {MockModel2: ["test_related_field"]}

        with self.subTest("Prevent saving changed field in related model"):
            MockModel2.objects.get = MagicMock(return_value=related_model_before)
            self.assertRaises(
                PaymentError,
                prevent_saving_related("test_related_field"),
                MockModel2,
                related_model_after,
            )

        with self.subTest("Do nothing if field is not changed related model"):
            MockModel2.objects.get = MagicMock(return_value=related_model_before)
            related_model_after.test_field = "test"
            prevent_saving_related("test_related_field")(
                MockModel2, related_model_after
            )

        with self.subTest("Do nothing if parent is not immutable"):
            MockPayable.immutable_after_payment = False
            prevent_saving_related("test_related_field")(
                MockModel2, related_model_after
            )

        with self.subTest("Do nothing if parent is not paid"):
            MockPayable.immutable_after_payment = True
            model.payment = None
            prevent_saving_related("test_related_field")(
                MockModel2, related_model_after
            )

        with self.subTest(
            "Do nothing if related model changed fields are not labelled immutable"
        ):
            model.payment = Payment.objects.create(
                type=Payment.CARD, amount=2, notes="test payment", topic="test topic"
            )
            MockPayable.immutable_after_payment = True
            MockPayable.immutable_model_fields_after_payment = {MockModel2: []}
            prevent_saving_related("test_related_field")(
                MockModel2, related_model_after
            )

        with self.subTest("Error if model does not exist"):
            MockPayable.immutable_after_payment = True
            MockPayable.immutable_foreign_key_models = {
                MockModel2: ["test_related_field"]
            }
            MockModel2.DoesNotExist = ObjectDoesNotExist
            MockModel2.objects.get = MagicMock(side_effect=MockModel2.DoesNotExist)
            self.assertRaises(
                PaymentError,
                prevent_saving_related("test_related_field"),
                MockModel2,
                related_model_after,
            )