Sith/counter/tests/test_account_dump.py
2024-11-19 00:48:35 +01:00

169 lines
6.6 KiB
Python

from collections.abc import Iterable
from datetime import timedelta
import freezegun
import pytest
from django.conf import settings
from django.core import mail
from django.core.management import call_command
from django.test import TestCase
from django.utils.timezone import now
from model_bakery import baker
from model_bakery.recipe import Recipe
from core.baker_recipes import subscriber_user, very_old_subscriber_user
from counter.management.commands.dump_accounts import Command as DumpCommand
from counter.management.commands.dump_warning_mail import Command as WarningCommand
from counter.models import AccountDump, Customer, Refilling, Selling
from subscription.models import Subscription
class TestAccountDump(TestCase):
@classmethod
def set_up_notified_users(cls):
"""Create the users which should be considered as dumpable"""
cls.notified_users = very_old_subscriber_user.make(_quantity=3)
baker.make(
Refilling,
amount=10,
customer=(u.customer for u in cls.notified_users),
date=now() - settings.SITH_ACCOUNT_INACTIVITY_DELTA - timedelta(days=1),
_quantity=len(cls.notified_users),
)
@classmethod
def set_up_not_notified_users(cls):
"""Create the users which should not be considered as dumpable"""
refill_recipe = Recipe(Refilling, amount=10)
cls.not_notified_users = [
subscriber_user.make(),
very_old_subscriber_user.make(), # inactive, but account already empty
very_old_subscriber_user.make(), # inactive, but with a recent transaction
very_old_subscriber_user.make(), # inactive, but already warned
]
refill_recipe.make(
customer=cls.not_notified_users[2].customer, date=now() - timedelta(days=1)
)
refill_recipe.make(
customer=cls.not_notified_users[3].customer,
date=now() - settings.SITH_ACCOUNT_INACTIVITY_DELTA - timedelta(days=1),
)
baker.make(
AccountDump,
customer=cls.not_notified_users[3].customer,
dump_operation=None,
)
class TestAccountDumpWarningMailCommand(TestAccountDump):
@classmethod
def setUpTestData(cls):
# delete existing accounts to avoid side effect
Customer.objects.all().delete()
cls.set_up_notified_users()
cls.set_up_not_notified_users()
def test_user_selection(self):
"""Test that the user to warn are well selected."""
users = list(WarningCommand._get_users())
assert len(users) == len(self.notified_users)
assert set(users) == set(self.notified_users)
def test_command(self):
"""The actual command test."""
call_command("dump_warning_mail")
# 1 already existing + 3 new account dump objects
assert AccountDump.objects.count() == 4
sent_mails = list(mail.outbox)
assert len(sent_mails) == 3
target_emails = {u.email for u in self.notified_users}
for sent in sent_mails:
assert len(sent.to) == 1
assert sent.to[0] in target_emails
class TestAccountDumpCommand(TestAccountDump):
@classmethod
def setUpTestData(cls):
with freezegun.freeze_time(
now() - settings.SITH_ACCOUNT_DUMP_DELTA - timedelta(hours=1)
):
# pretend the notifications happened enough time ago
# to make sure the accounts are dumpable right now
cls.set_up_notified_users()
AccountDump.objects.bulk_create(
[
AccountDump(customer=u.customer, warning_mail_sent_at=now())
for u in cls.notified_users
]
)
# One of the users reactivated its account
baker.make(
Subscription,
member=cls.notified_users[0],
subscription_start=now() - timedelta(days=1),
)
def assert_accounts_dumped(self, accounts: Iterable[Customer]):
"""Assert that the given accounts have been dumped"""
assert not (
AccountDump.objects.ongoing().filter(customer__in=accounts).exists()
)
for customer in accounts:
initial_amount = customer.amount
customer.refresh_from_db()
assert customer.amount == 0
operation: Selling = customer.buyings.order_by("date").last()
assert operation.unit_price == initial_amount
assert operation.counter_id == settings.SITH_COUNTER_ACCOUNT_DUMP_ID
assert operation.is_validated is True
dump = customer.dumps.last()
assert dump.dump_operation == operation
def test_user_selection(self):
"""Test that users to dump are well selected"""
# even reactivated users should be selected,
# because their pending AccountDump must be dealt with
users = list(DumpCommand._get_users())
assert len(users) == len(self.notified_users)
assert set(users) == set(self.notified_users)
def test_dump_accounts(self):
"""Test the _dump_accounts method"""
# the first user reactivated its account, thus should not be dumped
to_dump: set[Customer] = {u.customer for u in self.notified_users[1:]}
DumpCommand._dump_accounts(to_dump)
self.assert_accounts_dumped(to_dump)
def test_dump_account_with_active_users(self):
"""Test that the dump account method failed if given active users."""
active_user = subscriber_user.make()
active_user.customer.amount = 10
active_user.customer.save()
customers = {u.customer for u in self.notified_users}
customers.add(active_user.customer)
with pytest.raises(ValueError):
DumpCommand._dump_accounts(customers)
for customer in customers:
# all users should have kept their money
initial_amount = customer.amount
customer.refresh_from_db()
assert customer.amount == initial_amount
def test_command(self):
"""test the actual command"""
call_command("dump_accounts")
reactivated_user = self.notified_users[0]
# the pending operation should be deleted for reactivated users
assert not reactivated_user.customer.dumps.exists()
assert reactivated_user.customer.amount == 10
dumped_users = self.notified_users[1:]
self.assert_accounts_dumped([u.customer for u in dumped_users])
sent_mails = list(mail.outbox)
assert len(sent_mails) == 2
target_emails = {u.email for u in dumped_users}
for sent in sent_mails:
assert len(sent.to) == 1
assert sent.to[0] in target_emails