kfet.tests -- Add tests for cancel_operations view

This commit is contained in:
Aurélien Delobelle 2018-10-04 14:33:03 +02:00
parent d7ca072af3
commit b69f1b6dbc

View file

@ -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):