From b69f1b6dbcd14753fdad2bf9973c4fa88290b3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Thu, 4 Oct 2018 14:33:03 +0200 Subject: [PATCH] kfet.tests -- Add tests for cancel_operations view --- kfet/tests/test_views.py | 812 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 809 insertions(+), 3 deletions(-) diff --git a/kfet/tests/test_views.py b/kfet/tests/test_views.py index bd57b6f8..e5ccfaa1 100644 --- a/kfet/tests/test_views.py +++ b/kfet/tests/test_views.py @@ -28,7 +28,16 @@ from ..models import ( TransferGroup, ) from .testcases import ViewTestCaseMixin -from .utils import create_team, create_user, get_perms, user_add_perms +from .utils import ( + create_checkout, + create_checkout_statement, + create_inventory_article, + create_operation_group, + create_team, + create_user, + get_perms, + user_add_perms, +) class AccountListViewTests(ViewTestCaseMixin, TestCase): @@ -2952,6 +2961,21 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase): class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase): + """ + Test cases for kpsul_cancel_operations view. + + To test valid requests, one should use '_assertResponseOk(response)' to get + hints about failure reasons, if any. + + At least one test per operation type should test the complete response and + behavior (HTTP, WebSocket, object updates, and object creations) + Other tests of the same operation type can only assert the specific + behavior differences. + + For invalid requests, response errors should be tested. + + """ + url_name = "kfet.kpsul.cancel_operations" url_expected = "/k-fet/k-psul/cancel_operations" @@ -2960,8 +2984,790 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase): auth_user = "team" auth_forbidden = [None, "user"] - def test_ok(self): - pass + with_liq = True + + def setUp(self): + super(KPsulCancelOperationsViewTests, self).setUp() + + self.checkout = create_checkout(balance=Decimal("100.00")) + # An Article, price=2.5, stock=20 + self.article = Article.objects.create( + category=ArticleCategory.objects.create(name="Category"), + name="Article", + price=Decimal("2.5"), + stock=20, + ) + # An Account, trigramme=000, balance=50 + # Do not assume user is cof, nor not cof. + self.account = self.accounts["user"] + self.account.balance = Decimal("50.00") + self.account.save() + + # Mock consumer of K-Psul websocket to catch what we're sending + kpsul_consumer_patcher = mock.patch("kfet.consumers.KPsul") + self.kpsul_consumer_mock = kpsul_consumer_patcher.start() + self.addCleanup(kpsul_consumer_patcher.stop) + + def _assertResponseOk(self, response): + """ + Asserts that status code of 'response' is 200, and returns the + deserialized content of the JSONResponse. + + In case status code is not 200, it prints the content of "errors" of + the response. + + """ + json_data = json.loads(getattr(response, "content", b"{}").decode("utf-8")) + try: + self.assertEqual(response.status_code, 200) + except AssertionError as exc: + msg = "Expected response is 200, got {}. Errors: {}".format( + response.status_code, json_data.get("errors") + ) + raise AssertionError(msg) from exc + return json_data + + def test_invalid_operation_not_int(self): + data = {"operations[]": ["a"]} + resp = self.client.post(self.url, data) + + self.assertEqual(resp.status_code, 400) + json_data = json.loads(resp.content.decode("utf-8")) + self.assertEqual(json_data["errors"], {}) + + def test_invalid_operation_not_exist(self): + data = {"operations[]": ["1000"]} + resp = self.client.post(self.url, data) + + self.assertEqual(resp.status_code, 400) + json_data = json.loads(resp.content.decode("utf-8")) + self.assertEqual(json_data["errors"], {"opes_notexisting": [1000]}) + + @mock.patch("django.utils.timezone.now") + def test_purchase(self, now_mock): + now_mock.return_value = self.now + group = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[ + {"type": Operation.PURCHASE, "article": self.article, "article_nb": 2} + ], + ) + operation = group.opes.get() + now_mock.return_value += timedelta(seconds=15) + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + json_data = self._assertResponseOk(resp) + + group = OperationGroup.objects.get() + self.assertDictEqual( + group.__dict__, + { + "_state": mock.ANY, + "amount": Decimal("0.00"), + "at": mock.ANY, + "checkout_id": self.checkout.pk, + "comment": "", + "id": mock.ANY, + "is_cof": False, + "on_acc_id": self.account.pk, + "valid_by_id": None, + }, + ) + operation = Operation.objects.get() + self.assertDictEqual( + operation.__dict__, + { + "_state": mock.ANY, + "addcost_amount": None, + "addcost_for_id": None, + "amount": Decimal("-5.00"), + "article_id": self.article.pk, + "article_nb": 2, + "canceled_at": self.now + timedelta(seconds=15), + "canceled_by_id": None, + "group_id": group.pk, + "id": mock.ANY, + "type": Operation.PURCHASE, + }, + ) + + self.assertDictEqual( + json_data, {"canceled": [operation.pk], "errors": {}, "warnings": {}} + ) + + self.account.refresh_from_db() + self.assertEqual(self.account.balance, Decimal("55.00")) + self.article.refresh_from_db() + self.assertEqual(self.article.stock, 22) + self.checkout.refresh_from_db() + self.assertEqual(self.checkout.balance, Decimal("100.00")) + + self.kpsul_consumer_mock.group_send.assert_called_with( + "kfet.kpsul", + { + "opegroups": [ + { + "cancellation": True, + "id": group.pk, + "amount": Decimal("0.00"), + "is_cof": False, + } + ], + "opes": [ + { + "cancellation": True, + "id": operation.pk, + "canceled_by__trigramme": None, + "canceled_at": self.now + timedelta(seconds=15), + } + ], + "checkouts": [], + "articles": [{"id": self.article.pk, "stock": 22}], + }, + ) + + def test_purchase_with_addcost(self): + # TODO(AD): L'état de la balance du compte destinataire de la majoration ne + # devrait pas empêcher l'annulation d'une opération. + addcost_user = create_user( + "addcost", "ADD", account_attrs={"balance": Decimal("10.00")} + ) + addcost_account = addcost_user.profile.account_kfet + group = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[ + { + "type": Operation.PURCHASE, + "article": self.article, + "article_nb": 2, + "amount": Decimal("-6.00"), + "addcost_amount": Decimal("1.00"), + "addcost_for": addcost_account, + } + ], + ) + operation = group.opes.get() + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + self._assertResponseOk(resp) + + self.account.refresh_from_db() + self.assertEqual(self.account.balance, Decimal("56.00")) + addcost_account.refresh_from_db() + self.assertEqual(addcost_account.balance, Decimal("9.00")) + + def test_purchase_cash(self): + group = create_operation_group( + on_acc=self.accounts["liq"], + checkout=self.checkout, + content=[ + { + "type": Operation.PURCHASE, + "article": self.article, + "article_nb": 2, + "amount": Decimal("-5.00"), + } + ], + ) + operation = group.opes.get() + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + self._assertResponseOk(resp) + + self.assertEqual(self.accounts["liq"].balance, Decimal("0.00")) + self.checkout.refresh_from_db() + self.assertEqual(self.checkout.balance, Decimal("95.00")) + + ws_data_checkouts = self.kpsul_consumer_mock.group_send.call_args[0][1][ + "checkouts" + ] + self.assertListEqual( + ws_data_checkouts, [{"id": self.checkout.pk, "balance": Decimal("95.00")}] + ) + + def test_purchase_cash_with_addcost(self): + # TODO(AD): L'état de la balance du compte destinataire de la majoration ne + # devrait pas empêcher l'annulation d'une opération. + addcost_user = create_user( + "addcost", "ADD", account_attrs={"balance": Decimal("10.00")} + ) + addcost_account = addcost_user.profile.account_kfet + group = create_operation_group( + on_acc=self.accounts["liq"], + checkout=self.checkout, + content=[ + { + "type": Operation.PURCHASE, + "article": self.article, + "article_nb": 2, + "amount": Decimal("-6.00"), + "addcost_amount": Decimal("1.00"), + "addcost_for": addcost_account, + } + ], + ) + operation = group.opes.get() + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + self._assertResponseOk(resp) + + self.checkout.refresh_from_db() + self.assertEqual(self.checkout.balance, Decimal("94.00")) + addcost_account.refresh_from_db() + self.assertEqual(addcost_account.balance, Decimal("9.00")) + + ws_data_checkouts = self.kpsul_consumer_mock.group_send.call_args[0][1][ + "checkouts" + ] + self.assertListEqual( + ws_data_checkouts, [{"id": self.checkout.pk, "balance": Decimal("94.00")}] + ) + + @mock.patch("django.utils.timezone.now") + def test_deposit(self, now_mock): + now_mock.return_value = self.now + group = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[{"type": Operation.DEPOSIT, "amount": Decimal("10.75")}], + ) + operation = group.opes.get() + now_mock.return_value += timedelta(seconds=15) + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + json_data = self._assertResponseOk(resp) + + group = OperationGroup.objects.get() + self.assertDictEqual( + group.__dict__, + { + "_state": mock.ANY, + "amount": Decimal("0.00"), + "at": mock.ANY, + "checkout_id": self.checkout.pk, + "comment": "", + "id": mock.ANY, + "is_cof": False, + "on_acc_id": self.account.pk, + "valid_by_id": None, + }, + ) + operation = Operation.objects.get() + self.assertDictEqual( + operation.__dict__, + { + "_state": mock.ANY, + "addcost_amount": None, + "addcost_for_id": None, + "amount": Decimal("10.75"), + "article_id": None, + "article_nb": None, + "canceled_at": self.now + timedelta(seconds=15), + "canceled_by_id": None, + "group_id": group.pk, + "id": mock.ANY, + "type": Operation.DEPOSIT, + }, + ) + + self.assertDictEqual( + json_data, {"canceled": [operation.pk], "errors": {}, "warnings": {}} + ) + + self.account.refresh_from_db() + self.assertEqual(self.account.balance, Decimal("39.25")) + self.article.refresh_from_db() + self.assertEqual(self.article.stock, 20) + self.checkout.refresh_from_db() + self.assertEqual(self.checkout.balance, Decimal("89.25")) + + self.kpsul_consumer_mock.group_send.assert_called_with( + "kfet.kpsul", + { + "opegroups": [ + { + "cancellation": True, + "id": group.pk, + "amount": Decimal("0.00"), + "is_cof": False, + } + ], + "opes": [ + { + "cancellation": True, + "id": operation.pk, + "canceled_by__trigramme": None, + "canceled_at": self.now + timedelta(seconds=15), + } + ], + "checkouts": [{"id": self.checkout.pk, "balance": Decimal("89.25")}], + "articles": [], + }, + ) + + @mock.patch("django.utils.timezone.now") + def test_withdraw(self, now_mock): + now_mock.return_value = self.now + group = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[{"type": Operation.WITHDRAW, "amount": Decimal("-10.75")}], + ) + operation = group.opes.get() + now_mock.return_value += timedelta(seconds=15) + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + json_data = self._assertResponseOk(resp) + + group = OperationGroup.objects.get() + self.assertDictEqual( + group.__dict__, + { + "_state": mock.ANY, + "amount": Decimal("0.00"), + "at": mock.ANY, + "checkout_id": self.checkout.pk, + "comment": "", + "id": mock.ANY, + "is_cof": False, + "on_acc_id": self.account.pk, + "valid_by_id": None, + }, + ) + operation = Operation.objects.get() + self.assertDictEqual( + operation.__dict__, + { + "_state": mock.ANY, + "addcost_amount": None, + "addcost_for_id": None, + "amount": Decimal("-10.75"), + "article_id": None, + "article_nb": None, + "canceled_at": self.now + timedelta(seconds=15), + "canceled_by_id": None, + "group_id": group.pk, + "id": mock.ANY, + "type": Operation.WITHDRAW, + }, + ) + + self.assertDictEqual( + json_data, {"canceled": [operation.pk], "errors": {}, "warnings": {}} + ) + + self.account.refresh_from_db() + self.assertEqual(self.account.balance, Decimal("60.75")) + self.article.refresh_from_db() + self.assertEqual(self.article.stock, 20) + self.checkout.refresh_from_db() + self.assertEqual(self.checkout.balance, Decimal("110.75")) + + self.kpsul_consumer_mock.group_send.assert_called_with( + "kfet.kpsul", + { + "opegroups": [ + { + "cancellation": True, + "id": group.pk, + "amount": Decimal("0.00"), + "is_cof": False, + } + ], + "opes": [ + { + "cancellation": True, + "id": operation.pk, + "canceled_by__trigramme": None, + "canceled_at": self.now + timedelta(seconds=15), + } + ], + "checkouts": [{"id": self.checkout.pk, "balance": Decimal("110.75")}], + "articles": [], + }, + ) + + @mock.patch("django.utils.timezone.now") + def test_edit(self, now_mock): + now_mock.return_value = self.now + group = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[{"type": Operation.EDIT, "amount": Decimal("-10.75")}], + ) + operation = group.opes.get() + now_mock.return_value += timedelta(seconds=15) + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + json_data = self._assertResponseOk(resp) + + group = OperationGroup.objects.get() + self.assertDictEqual( + group.__dict__, + { + "_state": mock.ANY, + "amount": Decimal("0.00"), + "at": mock.ANY, + "checkout_id": self.checkout.pk, + "comment": "", + "id": mock.ANY, + "is_cof": False, + "on_acc_id": self.account.pk, + "valid_by_id": None, + }, + ) + operation = Operation.objects.get() + self.assertDictEqual( + operation.__dict__, + { + "_state": mock.ANY, + "addcost_amount": None, + "addcost_for_id": None, + "amount": Decimal("-10.75"), + "article_id": None, + "article_nb": None, + "canceled_at": self.now + timedelta(seconds=15), + "canceled_by_id": None, + "group_id": group.pk, + "id": mock.ANY, + "type": Operation.EDIT, + }, + ) + + self.assertDictEqual( + json_data, {"canceled": [operation.pk], "errors": {}, "warnings": {}} + ) + + self.account.refresh_from_db() + self.assertEqual(self.account.balance, Decimal("60.75")) + self.article.refresh_from_db() + self.assertEqual(self.article.stock, 20) + self.checkout.refresh_from_db() + self.assertEqual(self.checkout.balance, Decimal("100.00")) + + self.kpsul_consumer_mock.group_send.assert_called_with( + "kfet.kpsul", + { + "opegroups": [ + { + "cancellation": True, + "id": group.pk, + "amount": Decimal("0.00"), + "is_cof": False, + } + ], + "opes": [ + { + "cancellation": True, + "id": operation.pk, + "canceled_by__trigramme": None, + "canceled_at": self.now + timedelta(seconds=15), + } + ], + "checkouts": [], + "articles": [], + }, + ) + + @mock.patch("django.utils.timezone.now") + def test_old_operations(self, now_mock): + kfet_config.set(cancel_duration=timedelta(minutes=10)) + user_add_perms(self.users["team"], ["kfet.cancel_old_operations"]) + now_mock.return_value = self.now + group = create_operation_group( + at=self.now, + on_acc=self.account, + checkout=self.checkout, + content=[{"type": Operation.WITHDRAW, "amount": Decimal("-10.75")}], + ) + operation = group.opes.get() + now_mock.return_value += timedelta(minutes=10, seconds=1) + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + json_data = self._assertResponseOk(resp) + self.assertEqual(len(json_data["canceled"]), 1) + + @mock.patch("django.utils.timezone.now") + def test_invalid_old_operations_requires_perm(self, now_mock): + kfet_config.set(cancel_duration=timedelta(minutes=10)) + now_mock.return_value = self.now + group = create_operation_group( + at=self.now, + on_acc=self.account, + checkout=self.checkout, + content=[{"type": Operation.WITHDRAW, "amount": Decimal("-10.75")}], + ) + operation = group.opes.get() + now_mock.return_value += timedelta(minutes=10, seconds=1) + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + self.assertEqual(resp.status_code, 403) + json_data = json.loads(resp.content.decode("utf-8")) + self.assertEqual( + json_data["errors"], + {"missing_perms": ["Annuler des commandes non récentes"]}, + ) + + def test_already_canceled(self): + group = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[ + { + "type": Operation.WITHDRAW, + "amount": Decimal("-10.75"), + "canceled_at": timezone.now(), + } + ], + ) + operation = group.opes.get() + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + json_data = self._assertResponseOk(resp) + + self.assertDictEqual( + json_data["warnings"], {"already_canceled": [operation.pk]} + ) + + self.account.refresh_from_db() + self.assertEqual(self.account.balance, Decimal("50.00")) + self.checkout.refresh_from_db() + self.assertEqual(self.checkout.balance, Decimal("100.00")) + + @mock.patch("django.utils.timezone.now") + def test_checkout_before_last_statement(self, now_mock): + now_mock.return_value = self.now + group = create_operation_group( + at=self.now, + on_acc=self.account, + checkout=self.checkout, + content=[{"type": Operation.WITHDRAW, "amount": Decimal("-10.75")}], + ) + operation = group.opes.get() + now_mock.return_value += timedelta(seconds=30) + create_checkout_statement(checkout=self.checkout) + now_mock.return_value += timedelta(seconds=30) + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + json_data = self._assertResponseOk(resp) + + self.assertEqual(len(json_data["canceled"]), 1) + + self.account.refresh_from_db() + self.assertEqual(self.account.balance, Decimal("60.75")) + self.checkout.refresh_from_db() + self.assertEqual(self.checkout.balance, Decimal("100.00")) + + @mock.patch("django.utils.timezone.now") + def test_article_before_last_inventory(self, now_mock): + now_mock.return_value = self.now + group = create_operation_group( + at=self.now, + on_acc=self.account, + checkout=self.checkout, + content=[ + {"type": Operation.PURCHASE, "article": self.article, "article_nb": 2} + ], + ) + operation = group.opes.get() + now_mock.return_value += timedelta(seconds=30) + create_inventory_article(article=self.article) + now_mock.return_value += timedelta(seconds=30) + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + json_data = self._assertResponseOk(resp) + + self.assertEqual(len(json_data["canceled"]), 1) + + self.account.refresh_from_db() + self.assertEqual(self.account.balance, Decimal("55.00")) + self.article.refresh_from_db() + self.assertEqual(self.article.stock, 20) + + def test_negative(self): + kfet_config.set(overdraft_amount=Decimal("40.00")) + user_add_perms(self.users["team"], ["kfet.perform_negative_operations"]) + self.account.balance = Decimal("-20.00") + self.account.save() + group = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[{"type": Operation.DEPOSIT, "amount": Decimal("10.75")}], + ) + operation = group.opes.get() + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + json_data = self._assertResponseOk(resp) + + self.assertEqual(len(json_data["canceled"]), 1) + + self.account.refresh_from_db() + self.assertEqual(self.account.balance, Decimal("-30.75")) + self.checkout.refresh_from_db() + self.assertEqual(self.checkout.balance, Decimal("89.25")) + + def test_invalid_negative_above_thresholds(self): + kfet_config.set(overdraft_amount=Decimal("5.00")) + user_add_perms(self.users["team"], ["kfet.perform_negative_operations"]) + self.account.balance = Decimal("-20.00") + self.account.save() + group = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[{"type": Operation.DEPOSIT, "amount": Decimal("10.75")}], + ) + operation = group.opes.get() + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + self.assertEqual(resp.status_code, 403) + json_data = json.loads(resp.content.decode("utf-8")) + self.assertEqual(json_data["errors"], {"negative": [self.account.trigramme]}) + + def test_invalid_negative_requires_perms(self): + kfet_config.set(overdraft_amount=Decimal("40.00")) + self.account.balance = Decimal("-20.00") + self.account.save() + group = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[{"type": Operation.DEPOSIT, "amount": Decimal("10.75")}], + ) + operation = group.opes.get() + + data = {"operations[]": [str(operation.pk)]} + resp = self.client.post(self.url, data) + + self.assertEqual(resp.status_code, 403) + json_data = json.loads(resp.content.decode("utf-8")) + self.assertEqual( + json_data["errors"], + {"missing_perms": ["Enregistrer des commandes en négatif"]}, + ) + + def test_partial_0(self): + group = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[ + {"type": Operation.PURCHASE, "article": self.article, "article_nb": 2}, + {"type": Operation.DEPOSIT, "amount": Decimal("10.75")}, + {"type": Operation.EDIT, "amount": Decimal("-6.00")}, + { + "type": Operation.WITHDRAW, + "amount": Decimal("-10.75"), + "canceled_at": timezone.now(), + }, + ], + ) + operation1 = group.opes.get(type=Operation.PURCHASE) + operation2 = group.opes.get(type=Operation.EDIT) + operation3 = group.opes.get(type=Operation.WITHDRAW) + + data = { + "operations[]": [str(operation1.pk), str(operation2.pk), str(operation3.pk)] + } + resp = self.client.post(self.url, data) + + json_data = self._assertResponseOk(resp) + + group.refresh_from_db() + self.assertEqual(group.amount, Decimal("10.75")) + self.assertEqual(group.opes.exclude(canceled_at=None).count(), 3) + + self.assertDictEqual( + json_data, + { + "canceled": [operation1.pk, operation2.pk], + "warnings": {"already_canceled": [operation3.pk]}, + "errors": {}, + }, + ) + + self.account.refresh_from_db() + self.assertEqual(self.account.balance, Decimal("61.00")) + self.article.refresh_from_db() + self.assertEqual(self.article.stock, 22) + self.checkout.refresh_from_db() + self.assertEqual(self.checkout.balance, Decimal("100.00")) + + def test_multi_0(self): + group1 = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[ + {"type": Operation.PURCHASE, "article": self.article, "article_nb": 2}, + {"type": Operation.DEPOSIT, "amount": Decimal("10.75")}, + {"type": Operation.EDIT, "amount": Decimal("-6.00")}, + ], + ) + operation11 = group1.opes.get(type=Operation.PURCHASE) + group2 = create_operation_group( + on_acc=self.account, + checkout=self.checkout, + content=[ + {"type": Operation.PURCHASE, "article": self.article, "article_nb": 5}, + {"type": Operation.DEPOSIT, "amount": Decimal("3.00")}, + ], + ) + operation21 = group2.opes.get(type=Operation.PURCHASE) + operation22 = group2.opes.get(type=Operation.DEPOSIT) + + data = { + "operations[]": [ + str(operation11.pk), + str(operation21.pk), + str(operation22.pk), + ] + } + resp = self.client.post(self.url, data) + + json_data = self._assertResponseOk(resp) + + group1.refresh_from_db() + self.assertEqual(group1.amount, Decimal("4.75")) + self.assertEqual(group1.opes.exclude(canceled_at=None).count(), 1) + group2.refresh_from_db() + self.assertEqual(group2.amount, Decimal(0)) + self.assertEqual(group2.opes.exclude(canceled_at=None).count(), 2) + + self.assertEqual(len(json_data["canceled"]), 3) + + self.account.refresh_from_db() + self.assertEqual(self.account.balance, Decimal("64.50")) + self.article.refresh_from_db() + self.assertEqual(self.article.stock, 27) + self.checkout.refresh_from_db() + self.assertEqual(self.checkout.balance, Decimal("97.00")) class KPsulArticlesData(ViewTestCaseMixin, TestCase):