From 54df9af013ed6ce97b2f1f2a44ad200a566daf92 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Thu, 11 Dec 2025 21:52:51 +0545 Subject: [PATCH] feat(test): added test for models --- src/migrations/0004_alter_invoice_due_date.py | 17 +++ src/models/finance.py | 2 +- tests/test_models.py | 134 +++++++++++++++++ tests/test_notifications.py | 138 ++++++++++++++---- 4 files changed, 259 insertions(+), 32 deletions(-) create mode 100644 src/migrations/0004_alter_invoice_due_date.py create mode 100644 tests/test_models.py diff --git a/src/migrations/0004_alter_invoice_due_date.py b/src/migrations/0004_alter_invoice_due_date.py new file mode 100644 index 0000000..74e40da --- /dev/null +++ b/src/migrations/0004_alter_invoice_due_date.py @@ -0,0 +1,17 @@ +# Generated by Django 6.0 on 2025-12-11 15:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("src", "0003_notification"), + ] + + operations = [ + migrations.AlterField( + model_name="invoice", + name="due_date", + field=models.DateField(), + ), + ] diff --git a/src/models/finance.py b/src/models/finance.py index 50cdb4d..9f973f1 100644 --- a/src/models/finance.py +++ b/src/models/finance.py @@ -23,7 +23,7 @@ class Invoice(TimeStampedModel): invoice_number = models.CharField(max_length=20, unique=True, editable=False) amount = models.DecimalField(max_digits=10, decimal_places=2) date_issued = models.DateField(auto_now_add=True) - due_date = models.DateField(auto_now=True) + due_date = models.DateField() status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="DRAFT") def save(self, *args, **kwargs): diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..8ca7627 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,134 @@ +from datetime import timedelta +from decimal import Decimal + +from django.core.exceptions import ValidationError +from django.test import TestCase +from django.utils import timezone + +from src.models.clients import Client +from src.models.finance import Invoice +from src.models.productivity import TimeEntry +from src.models.projects import Milestone, Project, Task +from src.models.services import Service + + +class ClientModelTest(TestCase): + def test_short_code_generation(self): + client1 = Client.objects.create(name="Sankalp Corp", email="s@example.com") + self.assertEqual(client1.short_code, "SAN001") + + client2 = Client.objects.create(name="Sankalp Inc", email="s2@example.com") + self.assertEqual(client2.short_code, "SAN002") + + client3 = Client.objects.create(name="Other Corp", email="o@example.com") + self.assertEqual(client3.short_code, "OTH001") + + +class ProjectModelTest(TestCase): + def setUp(self): + self.client = Client.objects.create(name="Test Client", email="c@example.com") + + def test_deadline_validation(self): + start_date = timezone.localdate() + deadline = start_date - timedelta(days=1) # Deadline before start + + project = Project( + name="Bad Project", + client=self.client, + start_date=start_date, + deadline=deadline, + ) + with self.assertRaises(ValidationError): + project.clean() + + def test_milestone_str(self): + project = Project.objects.create(name="P1", client=self.client) + milestone = Milestone.objects.create(project=project, title="M1") + self.assertIn("✗", str(milestone)) + + milestone.is_completed = True + milestone.save() + self.assertIn("✓", str(milestone)) + + def test_task_due_date_validation(self): + yesterday = timezone.localdate() - timedelta(days=1) + task = Task( + project=Project.objects.create(name="P1", client=self.client), + title="Old Task", + due_date=yesterday, + ) + with self.assertRaises(ValidationError): + task.clean() + + +class ServiceModelTest(TestCase): + def setUp(self): + self.client = Client.objects.create(name="Test Client", email="c@example.com") + + def test_expiry_date_validation(self): + start_date = timezone.localdate() + expiry_date = start_date - timedelta(days=1) + + service = Service( + client=self.client, + name="Bad Service", + service_type="DOMAIN", + start_date=start_date, + expiry_date=expiry_date, + ) + with self.assertRaises(ValidationError): + service.clean() + + +class FinanceModelTest(TestCase): + def setUp(self): + self.client = Client.objects.create(name="Test Client", email="c@example.com") + self.project = Project.objects.create(name="P1", client=self.client) + + def test_invoice_number_generation(self): + # Client short code is TES001 + inv1 = Invoice.objects.create( + client=self.client, + project=self.project, + amount=Decimal("100.00"), + due_date=timezone.localdate(), + ) + self.assertEqual(inv1.invoice_number, "TES001-0001") + + inv2 = Invoice.objects.create( + client=self.client, + project=self.project, + amount=Decimal("200.00"), + due_date=timezone.localdate(), + ) + self.assertEqual(inv2.invoice_number, "TES001-0002") + + +class ProductivityModelTest(TestCase): + def setUp(self): + self.client = Client.objects.create(name="Test Client", email="c@example.com") + self.project = Project.objects.create(name="P1", client=self.client) + + def test_duration_calculation(self): + start = timezone.now() + end = start + timedelta(hours=2, minutes=30) + + entry = TimeEntry.objects.create( + project=self.project, description="Coding", start_time=start, end_time=end + ) + + # Refresh to get DB value if needed, but save() sets it on instance + self.assertEqual(entry.duration, timedelta(hours=2, minutes=30)) + + def test_clean_validation(self): + start = timezone.now() + end = start - timedelta(hours=1) + + entry = TimeEntry( + project=self.project, + description="Time Travel", + start_time=start, + end_time=end, + ) + with self.assertRaises(ValidationError): + entry.clean() diff --git a/tests/test_notifications.py b/tests/test_notifications.py index efbfe88..f606505 100644 --- a/tests/test_notifications.py +++ b/tests/test_notifications.py @@ -1,50 +1,126 @@ +from datetime import timedelta +from decimal import Decimal + from django.contrib.auth import get_user_model from django.core import mail from django.test import TestCase from django.utils import timezone from src.models.clients import Client +from src.models.finance import Invoice from src.models.notifications import Notification, check_and_send_notifications -from src.models.projects import Project +from src.models.productivity import TimeEntry +from src.models.projects import Milestone, Project, Task +from src.models.services import Service User = get_user_model() class NotificationTests(TestCase): - def test_send_deadline_notifications(self): - # Setup - User.objects.create_superuser("admin", "admin@example.com", "password") - client = Client.objects.create(name="Test Client", email="client@example.com") + def setUp(self): + self.admin = User.objects.create_superuser( + "admin", "admin@example.com", "password" + ) + self.client = Client.objects.create( + name="Test Client", email="client@example.com" + ) + self.tomorrow = timezone.localdate() + timedelta(days=1) - # Project due tomorrow - tomorrow = timezone.localdate() + timezone.timedelta(days=1) - Project.objects.create(name="Test Project", client=client, deadline=tomorrow) + def test_project_deadline_notification(self): + with self.settings(ADMINS=[("Admin", "admin@example.com")]): + Project.objects.create( + name="Test Project", client=self.client, deadline=self.tomorrow + ) + check_and_send_notifications() - # Run notification check + # 1 to client, 1 to admin + self.assertEqual(len(mail.outbox), 2) + self.assertTrue( + Notification.objects.filter( + subject__contains="Project Deadline" + ).exists() + ) + + def test_milestone_due_notification(self): + with self.settings(ADMINS=[("Admin", "admin@example.com")]): + project = Project.objects.create(name="Test Project", client=self.client) + Milestone.objects.create( + project=project, title="Test Milestone", due_date=self.tomorrow + ) + check_and_send_notifications() + + # 1 to client, 1 to admin + self.assertEqual(len(mail.outbox), 2) + self.assertTrue( + Notification.objects.filter(subject__contains="Milestone Due").exists() + ) + + def test_task_due_notification(self): + with self.settings(ADMINS=[("Admin", "admin@example.com")]): + project = Project.objects.create(name="Test Project", client=self.client) + Task.objects.create( + project=project, title="Test Task", due_date=self.tomorrow + ) + check_and_send_notifications() + + # 1 to admin only + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ["admin@example.com"]) + self.assertTrue( + Notification.objects.filter(subject__contains="Task Due").exists() + ) + + def test_service_expiry_notification(self): + Service.objects.create( + client=self.client, + name="Test Service", + service_type="DOMAIN", + expiry_date=self.tomorrow, + ) check_and_send_notifications() - # Verify emails sent - # Expecting 2 emails: 1 to client, 1 to admin - self.assertEqual(len(mail.outbox), 2) - - # Verify Notification objects created - self.assertEqual(Notification.objects.count(), 2) - - client_notif = Notification.objects.get(recipient="client@example.com") - self.assertIn("Project Deadline Reminder", client_notif.subject) - - admin_notif = Notification.objects.get(recipient="admin@example.com") - self.assertIn("Admin Alert", admin_notif.subject) - - def test_no_notifications_if_not_due(self): - # Setup - client = Client.objects.create(name="Test Client", email="client@example.com") - - # Project due day after tomorrow - day_after = timezone.localdate() + timezone.timedelta(days=2) - Project.objects.create(name="Future Project", client=client, deadline=day_after) + # 1 to client only + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ["client@example.com"]) + self.assertTrue( + Notification.objects.filter(subject__contains="Service Expiry").exists() + ) + def test_invoice_due_notification(self): + project = Project.objects.create(name="Test Project", client=self.client) + Invoice.objects.create( + client=self.client, + project=project, + amount=Decimal("100.00"), + due_date=self.tomorrow, + status="SENT", + ) check_and_send_notifications() - self.assertEqual(len(mail.outbox), 0) - self.assertEqual(Notification.objects.count(), 0) + # 1 to client + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ["client@example.com"]) + self.assertTrue( + Notification.objects.filter(subject__contains="Invoice Due").exists() + ) + + def test_time_entry_notification(self): + with self.settings(ADMINS=[("Admin", "admin@example.com")]): + project = Project.objects.create(name="Test Project", client=self.client) + now = timezone.now() + TimeEntry.objects.create( + project=project, + description="Work done", + start_time=now - timedelta(hours=2), + end_time=now - timedelta(hours=1), + ) + check_and_send_notifications() + + # 1 to admin + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ["admin@example.com"]) + self.assertTrue( + Notification.objects.filter( + subject__contains="Time Entry Logged" + ).exists() + )