mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-04-21 16:21:32 +02:00
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:
@@ -1,3 +1,204 @@
|
||||
from django.test import TestCase
|
||||
import csv
|
||||
import io
|
||||
import zipfile
|
||||
from decimal import Decimal
|
||||
from datetime import date, datetime
|
||||
|
||||
# Create your tests here.
|
||||
from django.test import TestCase, Client
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.accounts.models import Account, AccountGroup
|
||||
from apps.currencies.models import Currency
|
||||
from apps.transactions.models import Transaction, TransactionCategory, TransactionTag, TransactionEntity
|
||||
from apps.export_app.resources.transactions import TransactionResource, TransactionTagResource
|
||||
from apps.export_app.resources.accounts import AccountResource
|
||||
from apps.export_app.forms import ExportForm, RestoreForm # Added RestoreForm
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
class BaseExportAppTest(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.superuser = User.objects.create_superuser(
|
||||
email="exportadmin@example.com", password="password"
|
||||
)
|
||||
cls.currency_usd = Currency.objects.create(code="USD", name="US Dollar", decimal_places=2)
|
||||
cls.currency_eur = Currency.objects.create(code="EUR", name="Euro", decimal_places=2)
|
||||
|
||||
cls.user_group = AccountGroup.objects.create(name="User Group", owner=cls.superuser)
|
||||
cls.account_usd = Account.objects.create(
|
||||
name="Checking USD", currency=cls.currency_usd, owner=cls.superuser, group=cls.user_group
|
||||
)
|
||||
cls.account_eur = Account.objects.create(
|
||||
name="Savings EUR", currency=cls.currency_eur, owner=cls.superuser, group=cls.user_group
|
||||
)
|
||||
|
||||
cls.category_food = TransactionCategory.objects.create(name="Food", owner=cls.superuser)
|
||||
cls.tag_urgent = TransactionTag.objects.create(name="Urgent", owner=cls.superuser)
|
||||
cls.entity_store = TransactionEntity.objects.create(name="SuperStore", owner=cls.superuser)
|
||||
|
||||
cls.transaction1 = Transaction.objects.create(
|
||||
account=cls.account_usd,
|
||||
owner=cls.superuser,
|
||||
type=Transaction.Type.EXPENSE,
|
||||
date=date(2023, 1, 10),
|
||||
reference_date=date(2023, 1, 1),
|
||||
amount=Decimal("50.00"),
|
||||
description="Groceries",
|
||||
category=cls.category_food,
|
||||
is_paid=True
|
||||
)
|
||||
cls.transaction1.tags.add(cls.tag_urgent)
|
||||
cls.transaction1.entities.add(cls.entity_store)
|
||||
|
||||
cls.transaction2 = Transaction.objects.create(
|
||||
account=cls.account_eur,
|
||||
owner=cls.superuser,
|
||||
type=Transaction.Type.INCOME,
|
||||
date=date(2023, 1, 15),
|
||||
reference_date=date(2023, 1, 1),
|
||||
amount=Decimal("1200.00"),
|
||||
description="Salary",
|
||||
is_paid=True
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.client.login(email="exportadmin@example.com", password="password")
|
||||
|
||||
|
||||
class ResourceExportTests(BaseExportAppTest):
|
||||
def test_transaction_resource_export(self):
|
||||
resource = TransactionResource()
|
||||
queryset = Transaction.objects.filter(owner=self.superuser).order_by('pk') # Ensure consistent order
|
||||
dataset = resource.export(queryset=queryset)
|
||||
|
||||
self.assertEqual(len(dataset), 2)
|
||||
self.assertIn("id", dataset.headers)
|
||||
self.assertIn("account", dataset.headers)
|
||||
self.assertIn("description", dataset.headers)
|
||||
self.assertIn("category", dataset.headers)
|
||||
self.assertIn("tags", dataset.headers)
|
||||
self.assertIn("entities", dataset.headers)
|
||||
|
||||
exported_row1_dict = dict(zip(dataset.headers, dataset[0]))
|
||||
|
||||
self.assertEqual(exported_row1_dict["id"], self.transaction1.id)
|
||||
self.assertEqual(exported_row1_dict["account"], self.account_usd.name)
|
||||
self.assertEqual(exported_row1_dict["description"], "Groceries")
|
||||
self.assertEqual(exported_row1_dict["category"], self.category_food.name)
|
||||
# M2M fields order might vary, so check for presence
|
||||
self.assertIn(self.tag_urgent.name, exported_row1_dict["tags"].split(','))
|
||||
self.assertIn(self.entity_store.name, exported_row1_dict["entities"].split(','))
|
||||
self.assertEqual(Decimal(exported_row1_dict["amount"]), self.transaction1.amount)
|
||||
|
||||
|
||||
def test_account_resource_export(self):
|
||||
resource = AccountResource()
|
||||
queryset = Account.objects.filter(owner=self.superuser).order_by('name') # Ensure consistent order
|
||||
dataset = resource.export(queryset=queryset)
|
||||
|
||||
self.assertEqual(len(dataset), 2)
|
||||
self.assertIn("id", dataset.headers)
|
||||
self.assertIn("name", dataset.headers)
|
||||
self.assertIn("group", dataset.headers)
|
||||
self.assertIn("currency", dataset.headers)
|
||||
|
||||
# Assuming order by name, Checking USD comes first
|
||||
exported_row_usd_dict = dict(zip(dataset.headers, dataset[0]))
|
||||
self.assertEqual(exported_row_usd_dict["name"], self.account_usd.name)
|
||||
self.assertEqual(exported_row_usd_dict["group"], self.user_group.name)
|
||||
self.assertEqual(exported_row_usd_dict["currency"], self.currency_usd.name)
|
||||
|
||||
|
||||
class ExportViewTests(BaseExportAppTest):
|
||||
def test_export_form_get(self):
|
||||
response = self.client.get(reverse("export_form"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIsInstance(response.context["form"], ExportForm)
|
||||
|
||||
def test_export_single_csv(self):
|
||||
data = {"transactions": "on"}
|
||||
response = self.client.post(reverse("export_form"), data)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response["Content-Type"], "text/csv")
|
||||
self.assertTrue(response["Content-Disposition"].endswith("_WYGIWYH_export_transactions.csv\""))
|
||||
|
||||
content = response.content.decode('utf-8')
|
||||
reader = csv.reader(io.StringIO(content))
|
||||
headers = next(reader)
|
||||
self.assertIn("id", headers)
|
||||
self.assertIn("description", headers)
|
||||
|
||||
self.assertIn(self.transaction1.description, content)
|
||||
self.assertIn(self.transaction2.description, content)
|
||||
|
||||
|
||||
def test_export_multiple_to_zip(self):
|
||||
data = {
|
||||
"transactions": "on",
|
||||
"accounts": "on",
|
||||
}
|
||||
response = self.client.post(reverse("export_form"), data)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response["Content-Type"], "application/zip")
|
||||
self.assertTrue(response["Content-Disposition"].endswith("_WYGIWYH_export.zip\""))
|
||||
|
||||
zip_buffer = io.BytesIO(response.content)
|
||||
with zipfile.ZipFile(zip_buffer, "r") as zf:
|
||||
filenames = zf.namelist()
|
||||
self.assertIn("transactions.csv", filenames)
|
||||
self.assertIn("accounts.csv", filenames)
|
||||
|
||||
with zf.open("transactions.csv") as csv_file:
|
||||
content = csv_file.read().decode('utf-8')
|
||||
self.assertIn("id,type,date", content)
|
||||
self.assertIn(self.transaction1.description, content)
|
||||
|
||||
|
||||
def test_export_no_selection(self):
|
||||
data = {}
|
||||
response = self.client.post(reverse("export_form"), data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("You have to select at least one export", response.content.decode())
|
||||
|
||||
def test_export_access_non_superuser(self):
|
||||
normal_user = User.objects.create_user(email="normal@example.com", password="password")
|
||||
self.client.logout()
|
||||
self.client.login(email="normal@example.com", password="password")
|
||||
|
||||
response = self.client.get(reverse("export_index"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
response = self.client.get(reverse("export_form"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
|
||||
class RestoreViewTests(BaseExportAppTest):
|
||||
def test_restore_form_get(self):
|
||||
response = self.client.get(reverse("restore_form"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, "export_app/fragments/restore.html")
|
||||
self.assertIsInstance(response.context["form"], RestoreForm)
|
||||
|
||||
# Actual restore POST tests are complex due to file processing and DB interactions.
|
||||
# A placeholder for how one might start, heavily reliant on mocking or a working DB.
|
||||
# @patch('apps.export_app.views.process_imports')
|
||||
# def test_restore_form_post_zip_mocked_processing(self, mock_process_imports):
|
||||
# zip_content = io.BytesIO()
|
||||
# with zipfile.ZipFile(zip_content, "w") as zf:
|
||||
# zf.writestr("users.csv", "id,email\n1,test@example.com") # Minimal valid CSV content
|
||||
|
||||
# zip_file_upload = SimpleUploadedFile("test_restore.zip", zip_content.getvalue(), content_type="application/zip")
|
||||
# data = {"zip_file": zip_file_upload}
|
||||
|
||||
# response = self.client.post(reverse("restore_form"), data)
|
||||
# self.assertEqual(response.status_code, 204) # Expecting HTMX success
|
||||
# mock_process_imports.assert_called_once()
|
||||
# # Further checks on how mock_process_imports was called could be added here.
|
||||
pass
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user