Add initial Django tests for multiple apps

This commit introduces Django tests for several applications within your project. My goal was to cover the most important elements of each app.

Work Performed:

I analyzed and added tests for the following apps:
- apps.users: User authentication and profile management.
- apps.transactions: CRUD operations for transactions, categories, tags, entities, installment plans, and recurring transactions.
- apps.currencies: Management of currencies, exchange rates, and exchange rate services.
- apps.accounts: CRUD operations for accounts and account groups, including sharing.
- apps.common: Various utilities like custom fields, template tags, decorators, and management commands.
- apps.net_worth: Net worth calculation logic and display views.
- apps.import_app: Import profile validation, import service logic, and basic file processing.
- apps.export_app: Data export functionality using ModelResources and view logic for CSV/ZIP.
- apps.api: Core API endpoints for transactions and accounts, including permissions.

I also planned to cover:
- apps.rules
- apps.calendar_view
- apps.dca
This commit is contained in:
google-labs-jules[bot]
2025-06-15 20:12:37 +00:00
parent 3395a96949
commit 02f6bb0c29
9 changed files with 2298 additions and 119 deletions
+285 -2
View File
@@ -1,3 +1,286 @@
from django.test import TestCase
import datetime
from decimal import Decimal
from collections import OrderedDict
import json # Added for view tests
# Create your tests here.
from django.test import TestCase, Client
from django.contrib.auth import get_user_model
from django.utils import timezone
from django.template.defaultfilters import date as date_filter
from django.urls import reverse # Added for view tests
from dateutil.relativedelta import relativedelta # Added for date calculations
from apps.currencies.models import Currency
from apps.accounts.models import Account, AccountGroup
from apps.transactions.models import Transaction
from apps.net_worth.utils.calculate_net_worth import (
calculate_historical_currency_net_worth,
calculate_historical_account_balance,
)
# Mocking get_current_user from thread_local
from apps.common.middleware.thread_local import get_current_user, set_current_user
User = get_user_model()
class BaseNetWorthTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(email="networthuser@example.com", password="password")
self.other_user = User.objects.create_user(email="othernetworth@example.com", password="password")
# Set current user for thread_local middleware
set_current_user(self.user)
self.client = Client()
self.client.login(email="networthuser@example.com", password="password")
self.currency_usd = Currency.objects.create(code="USD", name="US Dollar", decimal_places=2)
self.currency_eur = Currency.objects.create(code="EUR", name="Euro", decimal_places=2)
self.account_group_main = AccountGroup.objects.create(name="Main Group", owner=self.user)
self.account_usd_1 = Account.objects.create(
name="USD Account 1", currency=self.currency_usd, owner=self.user, group=self.account_group_main
)
self.account_usd_2 = Account.objects.create(
name="USD Account 2", currency=self.currency_usd, owner=self.user, group=self.account_group_main
)
self.account_eur_1 = Account.objects.create(
name="EUR Account 1", currency=self.currency_eur, owner=self.user, group=self.account_group_main
)
# Public account for visibility tests
self.account_public_usd = Account.objects.create(
name="Public USD Account", currency=self.currency_usd, visibility=Account.Visibility.PUBLIC
)
def tearDown(self):
# Clear current user
set_current_user(None)
class CalculateNetWorthUtilsTests(BaseNetWorthTest):
def test_calculate_historical_currency_net_worth_no_transactions(self):
qs = Transaction.objects.none()
result = calculate_historical_currency_net_worth(qs)
current_month_str = date_filter(timezone.localdate(timezone.now()), "b Y")
next_month_str = date_filter(timezone.localdate(timezone.now()) + relativedelta(months=1), "b Y")
self.assertIn(current_month_str, result)
self.assertIn(next_month_str, result)
expected_currencies_present = {"US Dollar", "Euro"} # Based on created accounts for self.user
actual_currencies_in_result = set()
if result and result[current_month_str]: # Check if current_month_str key exists and has data
actual_currencies_in_result = set(result[current_month_str].keys())
self.assertTrue(expected_currencies_present.issubset(actual_currencies_in_result) or not result[current_month_str])
def test_calculate_historical_currency_net_worth_single_currency(self):
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("1000"), date=datetime.date(2023, 10, 5), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.EXPENSE, amount=Decimal("200"), date=datetime.date(2023, 10, 15), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_usd_2, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("300"), date=datetime.date(2023, 11, 5), reference_date=datetime.date(2023,11,1), is_paid=True)
qs = Transaction.objects.filter(owner=self.user, account__currency=self.currency_usd)
result = calculate_historical_currency_net_worth(qs)
oct_str = date_filter(datetime.date(2023, 10, 1), "b Y")
nov_str = date_filter(datetime.date(2023, 11, 1), "b Y")
dec_str = date_filter(datetime.date(2023, 12, 1), "b Y")
self.assertIn(oct_str, result)
self.assertEqual(result[oct_str]["US Dollar"], Decimal("800.00"))
self.assertIn(nov_str, result)
self.assertEqual(result[nov_str]["US Dollar"], Decimal("1100.00"))
self.assertIn(dec_str, result)
self.assertEqual(result[dec_str]["US Dollar"], Decimal("1100.00"))
def test_calculate_historical_currency_net_worth_multi_currency(self):
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("1000"), date=datetime.date(2023, 10, 5), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_eur_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("500"), date=datetime.date(2023, 10, 10), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.EXPENSE, amount=Decimal("100"), date=datetime.date(2023, 11, 5), reference_date=datetime.date(2023,11,1), is_paid=True)
Transaction.objects.create(account=self.account_eur_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("50"), date=datetime.date(2023, 11, 15), reference_date=datetime.date(2023,11,1), is_paid=True)
qs = Transaction.objects.filter(owner=self.user)
result = calculate_historical_currency_net_worth(qs)
oct_str = date_filter(datetime.date(2023, 10, 1), "b Y")
nov_str = date_filter(datetime.date(2023, 11, 1), "b Y")
self.assertEqual(result[oct_str]["US Dollar"], Decimal("1000.00"))
self.assertEqual(result[oct_str]["Euro"], Decimal("500.00"))
self.assertEqual(result[nov_str]["US Dollar"], Decimal("900.00"))
self.assertEqual(result[nov_str]["Euro"], Decimal("550.00"))
def test_calculate_historical_currency_net_worth_public_account_visibility(self):
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("100"), date=datetime.date(2023,10,1), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_public_usd, type=Transaction.Type.INCOME, amount=Decimal("200"), date=datetime.date(2023,10,1), reference_date=datetime.date(2023,10,1), is_paid=True)
qs = Transaction.objects.filter(Q(owner=self.user) | Q(account__visibility=Account.Visibility.PUBLIC))
result = calculate_historical_currency_net_worth(qs)
oct_str = date_filter(datetime.date(2023, 10, 1), "b Y")
self.assertEqual(result[oct_str]["US Dollar"], Decimal("300.00"))
def test_calculate_historical_account_balance_no_transactions(self):
qs = Transaction.objects.none()
result = calculate_historical_account_balance(qs)
current_month_str = date_filter(timezone.localdate(timezone.now()), "b Y")
next_month_str = date_filter(timezone.localdate(timezone.now()) + relativedelta(months=1), "b Y")
self.assertIn(current_month_str, result)
self.assertIn(next_month_str, result)
if result and result[current_month_str]:
for account_name in [self.account_usd_1.name, self.account_eur_1.name, self.account_public_usd.name]:
self.assertEqual(result[current_month_str].get(account_name, Decimal(0)), Decimal("0.00"))
def test_calculate_historical_account_balance_single_account(self):
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("1000"), date=datetime.date(2023, 10, 5), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.EXPENSE, amount=Decimal("200"), date=datetime.date(2023, 10, 15), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("50"), date=datetime.date(2023, 11, 5), reference_date=datetime.date(2023,11,1), is_paid=True)
qs = Transaction.objects.filter(account=self.account_usd_1)
result = calculate_historical_account_balance(qs)
oct_str = date_filter(datetime.date(2023, 10, 1), "b Y")
nov_str = date_filter(datetime.date(2023, 11, 1), "b Y")
self.assertEqual(result[oct_str][self.account_usd_1.name], Decimal("800.00"))
self.assertEqual(result[nov_str][self.account_usd_1.name], Decimal("850.00"))
def test_calculate_historical_account_balance_multiple_accounts(self):
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("100"), date=datetime.date(2023,10,1), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_eur_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("200"), date=datetime.date(2023,10,1), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.EXPENSE, amount=Decimal("30"), date=datetime.date(2023,11,1), reference_date=datetime.date(2023,11,1), is_paid=True)
qs = Transaction.objects.filter(owner=self.user)
result = calculate_historical_account_balance(qs)
oct_str = date_filter(datetime.date(2023, 10, 1), "b Y")
nov_str = date_filter(datetime.date(2023, 11, 1), "b Y")
self.assertEqual(result[oct_str][self.account_usd_1.name], Decimal("100.00"))
self.assertEqual(result[oct_str][self.account_eur_1.name], Decimal("200.00"))
self.assertEqual(result[nov_str][self.account_usd_1.name], Decimal("70.00"))
self.assertEqual(result[nov_str][self.account_eur_1.name], Decimal("200.00"))
def test_date_range_handling_in_utils(self):
qs_empty = Transaction.objects.none()
today = timezone.localdate(timezone.now())
start_of_this_month_str = date_filter(today.replace(day=1), "b Y")
start_of_next_month_str = date_filter((today.replace(day=1) + relativedelta(months=1)), "b Y")
currency_result = calculate_historical_currency_net_worth(qs_empty)
self.assertIn(start_of_this_month_str, currency_result)
self.assertIn(start_of_next_month_str, currency_result)
account_result = calculate_historical_account_balance(qs_empty)
self.assertIn(start_of_this_month_str, account_result)
self.assertIn(start_of_next_month_str, account_result)
def test_archived_account_exclusion_in_currency_net_worth(self):
archived_usd_acc = Account.objects.create(
name="Archived USD", currency=self.currency_usd, owner=self.user, is_archived=True
)
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("100"), date=datetime.date(2023,10,1), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=archived_usd_acc, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("500"), date=datetime.date(2023,10,1), reference_date=datetime.date(2023,10,1), is_paid=True)
qs = Transaction.objects.filter(owner=self.user, account__is_archived=False)
result = calculate_historical_currency_net_worth(qs)
oct_str = date_filter(datetime.date(2023, 10, 1), "b Y")
if oct_str in result:
self.assertEqual(result[oct_str].get("US Dollar", Decimal(0)), Decimal("100.00"))
elif result:
self.fail(f"{oct_str} not found in result, but other data exists.")
def test_archived_account_exclusion_in_account_balance(self):
archived_usd_acc = Account.objects.create(
name="Archived USD Acct Bal", currency=self.currency_usd, owner=self.user, is_archived=True
)
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("100"), date=datetime.date(2023,10,1), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=archived_usd_acc, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("500"), date=datetime.date(2023,10,1), reference_date=datetime.date(2023,10,1), is_paid=True)
qs = Transaction.objects.filter(owner=self.user)
result = calculate_historical_account_balance(qs)
oct_str = date_filter(datetime.date(2023, 10, 1), "b Y")
if oct_str in result:
self.assertIn(self.account_usd_1.name, result[oct_str])
self.assertEqual(result[oct_str][self.account_usd_1.name], Decimal("100.00"))
self.assertNotIn(archived_usd_acc.name, result[oct_str])
elif result:
self.fail(f"{oct_str} not found in result for account balance, but other data exists.")
class NetWorthViewTests(BaseNetWorthTest):
def test_net_worth_current_view(self):
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("1200.50"), date=datetime.date(2023, 10, 5), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_eur_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("800.75"), date=datetime.date(2023, 10, 10), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_usd_2, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("300.00"), date=datetime.date(2023, 9, 1), reference_date=datetime.date(2023,9,1), is_paid=False) # This is unpaid
response = self.client.get(reverse("net_worth_current"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "net_worth/net_worth.html")
# Current net worth display should only include paid transactions
self.assertContains(response, "US Dollar")
self.assertContains(response, "1,200.50")
self.assertContains(response, "Euro")
self.assertContains(response, "800.75")
chart_data_currency_json = response.context.get("chart_data_currency_json")
self.assertIsNotNone(chart_data_currency_json)
chart_data_currency = json.loads(chart_data_currency_json)
self.assertIn("labels", chart_data_currency)
self.assertIn("datasets", chart_data_currency)
# Historical chart data in net_worth_current view uses a queryset that is NOT filtered by is_paid.
sep_str = date_filter(datetime.date(2023, 9, 1), "b Y")
if sep_str in chart_data_currency["labels"]:
usd_dataset = next((ds for ds in chart_data_currency["datasets"] if ds["label"] == "US Dollar"), None)
self.assertIsNotNone(usd_dataset)
sep_idx = chart_data_currency["labels"].index(sep_str)
# The $300 from Sep (account_usd_2) should be part of the historical calculation for the chart
self.assertEqual(usd_dataset["data"][sep_idx], 300.00)
def test_net_worth_projected_view(self):
Transaction.objects.create(account=self.account_usd_1, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("1000"), date=datetime.date(2023, 10, 5), reference_date=datetime.date(2023,10,1), is_paid=True)
Transaction.objects.create(account=self.account_usd_2, owner=self.user, type=Transaction.Type.INCOME, amount=Decimal("500"), date=datetime.date(2023, 11, 1), reference_date=datetime.date(2023,11,1), is_paid=False) # Unpaid
response = self.client.get(reverse("net_worth_projected"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "net_worth/net_worth.html")
# `currency_net_worth` in projected view also uses a queryset NOT filtered by is_paid when calling `calculate_currency_totals`.
self.assertContains(response, "US Dollar")
self.assertContains(response, "1,500.00") # 1000 (paid) + 500 (unpaid)
chart_data_currency_json = response.context.get("chart_data_currency_json")
self.assertIsNotNone(chart_data_currency_json)
chart_data_currency = json.loads(chart_data_currency_json)
self.assertIn("labels", chart_data_currency)
self.assertIn("datasets", chart_data_currency)
nov_str = date_filter(datetime.date(2023, 11, 1), "b Y")
oct_str = date_filter(datetime.date(2023, 10, 1), "b Y")
if nov_str in chart_data_currency["labels"]:
usd_dataset = next((ds for ds in chart_data_currency["datasets"] if ds["label"] == "US Dollar"), None)
if usd_dataset:
nov_idx = chart_data_currency["labels"].index(nov_str)
# Value in Nov should be cumulative: 1000 (from Oct) + 500 (from Nov unpaid)
self.assertEqual(usd_dataset["data"][nov_idx], 1500.00)
# Check October value if it also exists
if oct_str in chart_data_currency["labels"]:
oct_idx = chart_data_currency["labels"].index(oct_str)
self.assertEqual(usd_dataset["data"][oct_idx], 1000.00)