diff --git a/kfet/tests/test_tests_utils.py b/kfet/tests/test_tests_utils.py index 45ca2348..25046abb 100644 --- a/kfet/tests/test_tests_utils.py +++ b/kfet/tests/test_tests_utils.py @@ -1,3 +1,6 @@ +from decimal import Decimal +from unittest import mock + from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType @@ -5,9 +8,16 @@ from django.test import TestCase from gestioncof.models import CofProfile -from ..models import Account +from ..models import Account, Article, ArticleCategory, Checkout, Operation from .testcases import TestCaseMixin -from .utils import create_root, create_team, create_user, get_perms, user_add_perms +from .utils import ( + create_operation_group, + create_root, + create_team, + create_user, + get_perms, + user_add_perms, +) User = get_user_model() @@ -86,3 +96,80 @@ class PermHelpersTest(TestCaseMixin, TestCase): map(repr, [self.perm1, self.perm2, self.perm_team]), ordered=False, ) + + +class OperationHelpersTest(TestCase): + def test_create_operation_group(self): + operation_group = create_operation_group() + + on_acc = Account.objects.get(cofprofile__user__username="user") + checkout = Checkout.objects.get(name="Checkout") + self.assertDictEqual( + operation_group.__dict__, + { + "_checkout_cache": checkout, + "_on_acc_cache": on_acc, + "_state": mock.ANY, + "amount": 0, + "at": mock.ANY, + "checkout_id": checkout.pk, + "comment": "", + "id": mock.ANY, + "is_cof": False, + "on_acc_id": on_acc.pk, + "valid_by_id": None, + }, + ) + self.assertFalse(operation_group.opes.all()) + + def test_create_operation_group_with_content(self): + article_category = ArticleCategory.objects.create(name="Category") + article1 = Article.objects.create( + category=article_category, name="Article 1", price=Decimal("2.50") + ) + article2 = Article.objects.create( + category=article_category, name="Article 2", price=Decimal("4.00") + ) + operation_group = create_operation_group( + content=[ + { + "type": Operation.PURCHASE, + "amount": Decimal("-3.50"), + "article": article1, + "article_nb": 2, + }, + {"type": Operation.PURCHASE, "article": article2, "article_nb": 2}, + {"type": Operation.PURCHASE, "article": article2}, + {"type": Operation.DEPOSIT, "amount": Decimal("10.00")}, + {"type": Operation.WITHDRAW, "amount": Decimal("-1.00")}, + {"type": Operation.EDIT, "amount": Decimal("7.00")}, + ] + ) + + self.assertEqual(operation_group.amount, Decimal("0.50")) + + operation_list = list(operation_group.opes.all()) + # Passed args: with purchase, article, article_nb, amount + self.assertEqual(operation_list[0].type, Operation.PURCHASE) + self.assertEqual(operation_list[0].article, article1) + self.assertEqual(operation_list[0].article_nb, 2) + self.assertEqual(operation_list[0].amount, Decimal("-3.50")) + # Passed args: with purchase, article, article_nb; without amount + self.assertEqual(operation_list[1].type, Operation.PURCHASE) + self.assertEqual(operation_list[1].article, article2) + self.assertEqual(operation_list[1].article_nb, 2) + self.assertEqual(operation_list[1].amount, Decimal("-8.00")) + # Passed args: with purchase, article; without article_nb, amount + self.assertEqual(operation_list[2].type, Operation.PURCHASE) + self.assertEqual(operation_list[2].article, article2) + self.assertEqual(operation_list[2].article_nb, 1) + self.assertEqual(operation_list[2].amount, Decimal("-4.00")) + # Passed args: with deposit, amount + self.assertEqual(operation_list[3].type, Operation.DEPOSIT) + self.assertEqual(operation_list[3].amount, Decimal("10.00")) + # Passed args: with withdraw, amount + self.assertEqual(operation_list[4].type, Operation.WITHDRAW) + self.assertEqual(operation_list[4].amount, Decimal("-1.00")) + # Passed args: with edit, amount + self.assertEqual(operation_list[5].type, Operation.EDIT) + self.assertEqual(operation_list[5].amount, Decimal("7.00")) diff --git a/kfet/tests/utils.py b/kfet/tests/utils.py index f1b6933a..79ca1b5e 100644 --- a/kfet/tests/utils.py +++ b/kfet/tests/utils.py @@ -1,7 +1,21 @@ +from datetime import timedelta +from decimal import Decimal + from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission +from django.utils import timezone -from ..models import Account +from ..models import ( + Account, + Article, + ArticleCategory, + Checkout, + CheckoutStatement, + Inventory, + InventoryArticle, + Operation, + OperationGroup, +) User = get_user_model() @@ -184,3 +198,180 @@ def user_add_perms(user, perms_labels): # it to avoid using of the previous permissions cache. # https://docs.djangoproject.com/en/dev/topics/auth/default/#permission-caching return User.objects.get(pk=user.pk) + + +def create_checkout(**kwargs): + """ + Factory to create a checkout. + See defaults for unpassed arguments in code below. + """ + if "created_by" not in kwargs or "created_by_id" not in kwargs: + try: + team_account = Account.objects.get(cofprofile__user__username="team") + except Account.DoesNotExist: + team_account = create_team().profile.account_kfet + kwargs["created_by"] = team_account + kwargs.setdefault("name", "Checkout") + kwargs.setdefault("valid_from", timezone.now() - timedelta(days=14)) + kwargs.setdefault("valid_to", timezone.now() - timedelta(days=14)) + + return Checkout.objects.create(**kwargs) + + +def create_operation_group(content=None, **kwargs): + """ + Factory to create an OperationGroup and a set of related Operation. + + It aims to get objects for testing purposes with minimal setup, and + preserving consistency. + For this, it uses, and creates if necessary, default objects for unpassed + arguments. + + Args: + content: list of dict + Describe set of Operation to create along the OperationGroup. + Each item is passed to the Operation factory. + kwargs: + Used to control OperationGroup creation. + + """ + if content is None: + content = [] + + # Prepare OperationGroup creation. + + # Set 'checkout' for OperationGroup if unpassed. + if "checkout" not in kwargs and "checkout_id" not in kwargs: + try: + checkout = Checkout.objects.get(name="Checkout") + except Checkout.DoesNotExist: + checkout = create_checkout() + kwargs["checkout"] = checkout + + # Set 'on_acc' for OperationGroup if unpassed. + if "on_acc" not in kwargs and "on_acc_id" not in kwargs: + try: + on_acc = Account.objects.get(cofprofile__user__username="user") + except Account.DoesNotExist: + on_acc = create_user().profile.account_kfet + kwargs["on_acc"] = on_acc + + # Set 'is_cof' for OperationGroup if unpassed. + if "is_cof" not in kwargs: + # Use current is_cof status of 'on_acc'. + kwargs["is_cof"] = kwargs["on_acc"].cofprofile.is_cof + + # Create OperationGroup. + group = OperationGroup.objects.create(**kwargs) + + # We can now create objects referencing this OperationGroup. + + # Process set of related Operation. + if content: + # Create them. + operation_list = [] + for operation_kwargs in content: + operation = create_operation(group=group, **operation_kwargs) + operation_list.append(operation) + + # Update OperationGroup accordingly, for consistency. + for operation in operation_list: + if not operation.canceled_at: + group.amount += operation.amount + group.save() + + return group + + +def create_operation(**kwargs): + """ + Factory to create an Operation for testing purposes. + + If you give a 'group' (OperationGroup), it won't update it, you have do + this "manually". Prefer using OperationGroup factory to get a consistent + group with operations. + + """ + if "group" not in kwargs and "group_id" not in kwargs: + # To get a consistent OperationGroup (amount...) for the operation + # in-creation, prefer using create_operation_group factory with + # 'content'. + kwargs["group"] = create_operation_group() + + if "type" not in kwargs: + raise RuntimeError("Can't create an Operation without 'type'.") + + # Apply defaults for purchase + if kwargs["type"] == Operation.PURCHASE: + if "article" not in kwargs: + raise NotImplementedError( + "One could write a create_article factory. Right now, you must" + "pass an 'article'." + ) + + # Unpassed 'article_nb' defaults to 1. + kwargs.setdefault("article_nb", 1) + + # Unpassed 'amount' will use current article price and quantity. + if "amount" not in kwargs: + if "addcost_for" in kwargs or "addcost_amount" in kwargs: + raise NotImplementedError( + "One could handle the case where 'amount' is missing and " + "addcost applies. Right now, please pass an 'amount'." + ) + kwargs["amount"] = -kwargs["article"].price * kwargs["article_nb"] + + return Operation.objects.create(**kwargs) + + +def create_checkout_statement(**kwargs): + if "checkout" not in kwargs: + kwargs["checkout"] = create_checkout() + if "by" not in kwargs: + try: + team_account = Account.objects.get(cofprofile__user__username="team") + except Account.DoesNotExist: + team_account = create_team().profile.account_kfet + kwargs["by"] = team_account + kwargs.setdefault("balance_new", kwargs["checkout"].balance) + kwargs.setdefault("balance_old", kwargs["checkout"].balance) + kwargs.setdefault("amount_taken", Decimal(0)) + + return CheckoutStatement.objects.create(**kwargs) + + +def create_article(**kwargs): + kwargs.setdefault("name", "Article") + kwargs.setdefault("price", Decimal("2.50")) + kwargs.setdefault("stock", 20) + if "category" not in kwargs: + kwargs["category"] = create_article_category() + + return Article.objects.create(**kwargs) + + +def create_article_category(**kwargs): + kwargs.setdefault("name", "Category") + return ArticleCategory.objects.create(**kwargs) + + +def create_inventory(**kwargs): + if "by" not in kwargs: + try: + team_account = Account.objects.get(cofprofile__user__username="team") + except Account.DoesNotExist: + team_account = create_team().profile.account_kfet + kwargs["by"] = team_account + + return Inventory.objects.create(**kwargs) + + +def create_inventory_article(**kwargs): + if "inventory" not in kwargs: + kwargs["inventory"] = create_inventory() + if "article" not in kwargs: + kwargs["article"] = create_article() + kwargs.setdefault("stock_old", kwargs["article"].stock) + kwargs.setdefault("stock_new", kwargs["article"].stock) + + return InventoryArticle.objects.create(**kwargs)