FallenBirdyForm/test/unit/test_costs_models.py
2025-06-07 19:24:41 +02:00

262 lines
9.1 KiB
Python

"""
Unit tests for Costs models.
"""
import pytest
from django.test import TestCase
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
from django.utils import timezone
from django.db import models
from decimal import Decimal
from costs.models import Costs
from bird.models import Bird, BirdStatus, Circumstance
from aviary.models import Aviary
class CostsModelTests(TestCase):
"""Test cases for Costs model."""
def setUp(self):
"""Set up test data."""
self.user = User.objects.create_user(
username='testuser',
email='test@example.com',
password='testpass123'
)
self.aviary = Aviary.objects.create(
name="Test Aviary",
location="Test Location",
created_by=self.user
)
self.bird_status = BirdStatus.objects.create(
name="Gesund",
description="Healthy bird"
)
self.circumstance = Circumstance.objects.create(
name="Gefunden",
description="Found bird"
)
self.bird = Bird.objects.create(
name="Test Bird",
species="Test Species",
aviary=self.aviary,
status=self.bird_status,
circumstance=self.circumstance,
created_by=self.user
)
self.costs = Costs.objects.create(
bird=self.bird,
description="Veterinary treatment",
amount=Decimal('150.75'),
cost_date=timezone.now().date(),
category="medical",
invoice_number="INV-001",
vendor="Test Veterinary Clinic",
notes="Routine checkup and treatment",
user=self.user,
created_by=self.user
)
def test_costs_creation(self):
"""Test that a cost entry can be created."""
self.assertTrue(isinstance(self.costs, Costs))
self.assertEqual(self.costs.bird, self.bird)
self.assertEqual(self.costs.description, "Veterinary treatment")
self.assertEqual(self.costs.amount, Decimal('150.75'))
self.assertEqual(self.costs.category, "medical")
self.assertEqual(self.costs.invoice_number, "INV-001")
self.assertEqual(self.costs.vendor, "Test Veterinary Clinic")
self.assertEqual(self.costs.notes, "Routine checkup and treatment")
def test_costs_str_representation(self):
"""Test the string representation of costs."""
expected = f"{self.costs.description} - €{self.costs.amount}"
self.assertEqual(str(self.costs), expected)
def test_costs_amount_validation(self):
"""Test that cost amount is validated."""
# Test negative amount
with self.assertRaises(ValidationError):
costs = Costs(
bird=self.bird,
description="Invalid cost",
amount=Decimal('-10.00'),
cost_date=timezone.now().date(),
user=self.user,
created_by=self.user
)
costs.full_clean()
# Test zero amount (should be valid)
costs = Costs(
bird=self.bird,
description="Zero cost",
amount=Decimal('0.00'),
cost_date=timezone.now().date(),
user=self.user,
created_by=self.user
)
costs.full_clean() # Should not raise validation error
def test_costs_category_choices(self):
"""Test that cost category has valid choices."""
valid_categories = ['medical', 'food', 'equipment', 'transport', 'other']
self.assertIn(self.costs.category, valid_categories)
# Test invalid category
with self.assertRaises(ValidationError):
costs = Costs(
bird=self.bird,
description="Invalid category",
amount=Decimal('10.00'),
category="invalid_category",
cost_date=timezone.now().date(),
user=self.user,
created_by=self.user
)
costs.full_clean()
def test_costs_required_fields(self):
"""Test that required fields are validated."""
with self.assertRaises(ValidationError):
costs = Costs()
costs.full_clean()
def test_costs_relationship(self):
"""Test costs relationships."""
self.assertEqual(self.costs.bird, self.bird)
self.assertEqual(self.costs.created_by, self.user)
def test_costs_date_validation(self):
"""Test that cost date is validated."""
# Test future date (should be valid unless restricted)
future_date = timezone.now().date() + timezone.timedelta(days=30)
costs = Costs(
bird=self.bird,
description="Future cost",
amount=Decimal('50.00'),
cost_date=future_date,
user=self.user,
created_by=self.user
)
costs.full_clean() # Should not raise validation error
def test_costs_decimal_precision(self):
"""Test decimal precision for amounts."""
# Test 2 decimal place amount (model allows max 2 decimal places)
precise_amount = Decimal('123.45')
costs = Costs(
bird=self.bird,
description="Precise amount",
amount=precise_amount,
cost_date=timezone.now().date(),
user=self.user,
created_by=self.user
)
costs.full_clean()
costs.save()
# Reload from database and check precision
costs.refresh_from_db()
# Model supports 2 decimal places, should match exactly
self.assertEqual(costs.amount, precise_amount)
# Test that amounts with more than 2 decimal places are rejected
with self.assertRaises(ValidationError):
invalid_costs = Costs(
bird=self.bird,
description="Too precise amount",
amount=Decimal('123.456'), # More than 2 decimal places
cost_date=timezone.now().date(),
user=self.user,
created_by=self.user
)
invalid_costs.full_clean()
def test_costs_filtering_by_category(self):
"""Test filtering costs by category."""
# Create costs in different categories
Costs.objects.create(
bird=self.bird,
description="Food cost",
amount=Decimal('25.00'),
category="food",
cost_date=timezone.now().date(),
user=self.user,
created_by=self.user
)
Costs.objects.create(
bird=self.bird,
description="Equipment cost",
amount=Decimal('75.00'),
category="equipment",
cost_date=timezone.now().date(),
user=self.user,
created_by=self.user
)
# Filter by category
medical_costs = Costs.objects.filter(category="medical")
food_costs = Costs.objects.filter(category="food")
equipment_costs = Costs.objects.filter(category="equipment")
self.assertEqual(medical_costs.count(), 1)
self.assertEqual(food_costs.count(), 1)
self.assertEqual(equipment_costs.count(), 1)
self.assertIn(self.costs, medical_costs)
def test_costs_total_for_bird(self):
"""Test calculating total costs for a bird."""
# Create additional costs for the same bird
Costs.objects.create(
bird=self.bird,
description="Additional cost 1",
amount=Decimal('50.00'),
cost_date=timezone.now().date(),
user=self.user,
created_by=self.user
)
Costs.objects.create(
bird=self.bird,
description="Additional cost 2",
amount=Decimal('25.25'),
cost_date=timezone.now().date(),
user=self.user,
created_by=self.user
)
# Calculate total costs for the bird
total_costs = Costs.objects.filter(bird=self.bird).aggregate(
total=models.Sum('amount')
)['total']
expected_total = Decimal('150.75') + Decimal('50.00') + Decimal('25.25')
self.assertEqual(total_costs, expected_total)
def test_costs_invoice_number_uniqueness(self):
"""Test invoice number uniqueness if enforced."""
# Try to create another cost with the same invoice number
try:
duplicate_costs = Costs(
bird=self.bird,
description="Duplicate invoice",
amount=Decimal('10.00'),
invoice_number="INV-001", # Same as self.costs
cost_date=timezone.now().date(),
user=self.user,
created_by=self.user
)
duplicate_costs.full_clean()
# If unique constraint exists, this should fail
except ValidationError:
# Expected if invoice_number has unique constraint
pass