From 4397eee91ace8ac1a6f434309997ad1ce7bfb182 Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Wed, 11 Jun 2025 15:55:36 -0400
Subject: [PATCH 01/12] Moved allocation tests into subdirectory and wrote
tests for Allocation clean() method
Signed-off-by: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
---
coldfront/core/allocation/test_models.py | 24 ----
coldfront/core/allocation/tests/__init__.py | 0
.../core/allocation/tests/test_models.py | 117 ++++++++++++++++++
.../core/allocation/{ => tests}/test_views.py | 0
4 files changed, 117 insertions(+), 24 deletions(-)
delete mode 100644 coldfront/core/allocation/test_models.py
create mode 100644 coldfront/core/allocation/tests/__init__.py
create mode 100644 coldfront/core/allocation/tests/test_models.py
rename coldfront/core/allocation/{ => tests}/test_views.py (100%)
diff --git a/coldfront/core/allocation/test_models.py b/coldfront/core/allocation/test_models.py
deleted file mode 100644
index 3adef2d49..000000000
--- a/coldfront/core/allocation/test_models.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# SPDX-FileCopyrightText: (C) ColdFront Authors
-#
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-"""Unit tests for the allocation models"""
-
-from django.test import TestCase
-
-from coldfront.core.test_helpers.factories import AllocationFactory, ResourceFactory
-
-
-class AllocationModelTests(TestCase):
- """tests for Allocation model"""
-
- @classmethod
- def setUpTestData(cls):
- """Set up project to test model properties and methods"""
- cls.allocation = AllocationFactory()
- cls.allocation.resources.add(ResourceFactory(name="holylfs07/tier1"))
-
- def test_allocation_str(self):
- """test that allocation str method returns correct string"""
- allocation_str = "%s (%s)" % (self.allocation.get_parent_resource.name, self.allocation.project.pi)
- self.assertEqual(str(self.allocation), allocation_str)
diff --git a/coldfront/core/allocation/tests/__init__.py b/coldfront/core/allocation/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
new file mode 100644
index 000000000..d03ee90aa
--- /dev/null
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -0,0 +1,117 @@
+# SPDX-FileCopyrightText: (C) ColdFront Authors
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+"""Unit tests for the allocation models"""
+
+from django.test import TestCase
+
+from coldfront.core.test_helpers.factories import AllocationFactory, AllocationStatusChoiceFactory, ProjectFactory, ResourceFactory
+from coldfront.core.allocation.models import Allocation, AllocationStatusChoice
+from coldfront.core.project.models import Project
+import datetime
+from django.core.exceptions import ValidationError
+
+
+
+class AllocationModelTests(TestCase):
+ """tests for Allocation model"""
+
+ @classmethod
+ def setUpTestData(cls):
+ """Set up project to test model properties and methods"""
+ cls.allocation = AllocationFactory()
+ cls.allocation.resources.add(ResourceFactory(name="holylfs07/tier1"))
+
+ def test_allocation_str(self):
+ """test that allocation str method returns correct string"""
+ allocation_str = "%s (%s)" % (self.allocation.get_parent_resource.name, self.allocation.project.pi)
+ self.assertEqual(str(self.allocation), allocation_str)
+
+
+class AllocationModelCleanMethodTests(TestCase):
+ """tests for Allocation model clean method"""
+
+ @classmethod
+ def setUpTestData(cls):
+ """Set up allocation to test clean method"""
+ cls.active_status: AllocationStatusChoice = AllocationStatusChoiceFactory(name="Active")
+ cls.expired_status: AllocationStatusChoice = AllocationStatusChoiceFactory(name="Expired")
+ cls.project: Project = ProjectFactory()
+
+ def test_status_is_expired_and_no_end_date_has_validation_error(self):
+ """Test that an allocation with status 'expired' and no end date raises a validation error."""
+ actual_allocation: Allocation = AllocationFactory.build(status=self.expired_status, end_date=None, project=self.project)
+ with self.assertRaises(ValidationError):
+ actual_allocation.full_clean()
+
+ def test_status_is_expired_and_end_date_not_past_has_validation_error(self):
+ """Test that an allocation with status 'expired' and end date in the future raises a validation error."""
+ end_date_in_the_future: datetime.date = (datetime.datetime.now() + datetime.timedelta(days=1)).date()
+ actual_allocation: Allocation = AllocationFactory.build(status=self.expired_status, end_date=end_date_in_the_future, project=self.project)
+ with self.assertRaises(ValidationError):
+ actual_allocation.full_clean()
+
+ def test_status_is_expired_and_start_date_after_end_date_has_validation_error(self):
+ """Test that an allocation with status 'expired' and start date after end date raises a validation error."""
+ end_date: datetime.date = (datetime.datetime.now() + datetime.timedelta(days=1)).date()
+ start_date_after_end_date: datetime.date = end_date + datetime.timedelta(days=1)
+
+ actual_allocation: Allocation = AllocationFactory.build(status=self.expired_status, start_date=start_date_after_end_date, end_date=end_date, project=self.project)
+ with self.assertRaises(ValidationError):
+ actual_allocation.full_clean()
+
+ def test_status_is_expired_and_start_date_before_end_date_no_error(self):
+ """Test that an allocation with status 'expired' and start date before end date does not raise a validation error."""
+ start_date: datetime.date = datetime.datetime(year=2023, month=11, day=2).date()
+ end_date: datetime.date = start_date + datetime.timedelta(days=40)
+
+ actual_allocation: Allocation = AllocationFactory.build(status=self.expired_status, start_date=start_date, end_date=end_date, project=self.project)
+ actual_allocation.full_clean()
+
+ def test_status_is_expired_and_start_date_equals_end_date_no_error(self):
+ """Test that an allocation with status 'expired' and start date equal to end date does not raise a validation error."""
+ start_and_end_date: datetime.date = datetime.datetime(year=1997, month=4, day=20).date()
+
+ actual_allocation: Allocation = AllocationFactory.build(status=self.expired_status, start_date=start_and_end_date, end_date=start_and_end_date, project=self.project)
+ actual_allocation.full_clean()
+
+ def test_status_is_active_and_no_start_date_has_validation_error(self):
+ """Test that an allocation with status 'active' and no start date raises a validation error."""
+ actual_allocation: Allocation = AllocationFactory.build(status=self.active_status, start_date=None, project=self.project)
+ with self.assertRaises(ValidationError):
+ actual_allocation.full_clean()
+
+ def test_status_is_active_and_no_end_date_has_validation_error(self):
+ """Test that an allocation with status 'active' and no end date raises a validation error."""
+ actual_allocation: Allocation = AllocationFactory.build(status=self.active_status, end_date=None, project=self.project)
+ with self.assertRaises(ValidationError):
+ actual_allocation.full_clean()
+
+ def test_status_is_active_and_start_date_after_end_date_has_validation_error(self):
+ """Test that an allocation with status 'active' and start date after end date raises a validation error."""
+ end_date: datetime.date = (datetime.datetime.now() + datetime.timedelta(days=1)).date()
+ start_date_after_end_date: datetime.date = (end_date + datetime.timedelta(days=1))
+
+ actual_allocation: Allocation = AllocationFactory.build(status=self.active_status, start_date=start_date_after_end_date, end_date=end_date, project=self.project)
+ with self.assertRaises(ValidationError):
+ actual_allocation.full_clean()
+
+ def test_status_is_active_and_start_date_before_end_date_no_error(self):
+ """Test that an allocation with status 'active' and start date before end date does not raise a validation error."""
+ start_date: datetime.date = datetime.datetime(year=2001, month=5, day=3).date()
+ end_date: datetime.date = start_date + datetime.timedelta(days=160)
+
+ actual_allocation: Allocation = AllocationFactory.build(status=self.active_status, start_date=start_date, end_date=end_date, project=self.project)
+ actual_allocation.full_clean()
+
+ def test_status_is_active_and_start_date_equals_end_date_no_error(self):
+ """Test that an allocation with status 'active' and start date equal to end date does not raise a validation error."""
+ start_and_end_date: datetime.date = datetime.datetime(year=2005, month=6, day=3).date()
+
+ actual_allocation: Allocation = AllocationFactory.build(status=self.active_status, start_date=start_and_end_date, end_date=start_and_end_date, project=self.project)
+ actual_allocation.full_clean()
+
+
+
+
\ No newline at end of file
diff --git a/coldfront/core/allocation/test_views.py b/coldfront/core/allocation/tests/test_views.py
similarity index 100%
rename from coldfront/core/allocation/test_views.py
rename to coldfront/core/allocation/tests/test_views.py
From a5f5cbe3c226eace59640f0ef5bb4f343f1acebc Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Thu, 12 Jun 2025 15:33:26 -0400
Subject: [PATCH 02/12] Wrote tests for Allocation's save() method
---
coldfront/core/allocation/models.py | 1 +
.../core/allocation/tests/test_models.py | 151 +++++++++++++++++-
2 files changed, 150 insertions(+), 2 deletions(-)
diff --git a/coldfront/core/allocation/models.py b/coldfront/core/allocation/models.py
index 3e6ef9bf9..526b1f935 100644
--- a/coldfront/core/allocation/models.py
+++ b/coldfront/core/allocation/models.py
@@ -129,6 +129,7 @@ def save(self, *args, **kwargs):
"""Saves the project."""
if self.pk:
+ ALLOCATION_FUNCS_ON_EXPIRE = import_from_settings("ALLOCATION_FUNCS_ON_EXPIRE", [])
old_obj = Allocation.objects.get(pk=self.pk)
if old_obj.status.name != self.status.name and self.status.name == "Expired":
for func_string in ALLOCATION_FUNCS_ON_EXPIRE:
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
index d03ee90aa..ff2164cb9 100644
--- a/coldfront/core/allocation/tests/test_models.py
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -4,13 +4,16 @@
"""Unit tests for the allocation models"""
-from django.test import TestCase
+from django.test import TestCase, override_settings
from coldfront.core.test_helpers.factories import AllocationFactory, AllocationStatusChoiceFactory, ProjectFactory, ResourceFactory
from coldfront.core.allocation.models import Allocation, AllocationStatusChoice
from coldfront.core.project.models import Project
import datetime
from django.core.exceptions import ValidationError
+from unittest.mock import patch
+from django.utils.safestring import SafeString
+
@@ -114,4 +117,148 @@ def test_status_is_active_and_start_date_equals_end_date_no_error(self):
-
\ No newline at end of file
+class AllocationFuncOnExpireException(Exception):
+ """Custom exception for testing allocation expiration function in the AllocationModelSaveMethodTests class."""
+ pass
+
+def allocation_func_on_expire_exception(*args, **kwargs):
+ """Test function to be called on allocation expiration in the AllocationModelSaveMethodTests class."""
+ raise AllocationFuncOnExpireException("This is a test exception for allocation expiration.")
+
+def get_dotted_path(func):
+ """Return the dotted path string for a Python function in the AllocationModelSaveMethodTests class."""
+ return f"{func.__module__}.{func.__qualname__}"
+
+NUMBER_OF_INVOCATIONS = 12
+
+def count_invocations(*args, **kwargs):
+ count_invocations.invocation_count = getattr(count_invocations, 'invocation_count', 0) + 1
+ pass
+
+def count_invocations_negative(*args, **kwargs):
+ count_invocations_negative.invocation_count = getattr(count_invocations_negative, 'invocation_count', 0) - 1
+ pass
+
+def list_of_same_expire_funcs(func: callable, size=NUMBER_OF_INVOCATIONS) -> list[str]:
+ return [get_dotted_path(func) for _ in range(size)]
+
+def list_of_different_expire_funcs() -> list[str]:
+ """Return a list of different functions to be called on allocation expiration.
+ The list will have a length of NUMBER_OF_INVOCATIONS, with the last function being allocation_func_on_expire_exception.
+ If NUMBER_OF_INVOCATIONS is odd, the list will contain (NUMBER_OF_INVOCATIONS // 2) instances of count_invocations and (NUMBER_OF_INVOCATIONS // 2) instances of count_invocations_negative.
+ If NUMBER_OF_INVOCATIONS is even, the list will contain (NUMBER_OF_INVOCATIONS // 2) instances of count_invocations and ((NUMBER_OF_INVOCATIONS // 2)-1) instances of count_invocations_negative.
+ """
+ expire_funcs: list[str] = []
+ for i in range(NUMBER_OF_INVOCATIONS):
+ if i == (NUMBER_OF_INVOCATIONS - 1):
+ expire_funcs.append(get_dotted_path(allocation_func_on_expire_exception))
+ elif i % 2 == 0:
+ expire_funcs.append(get_dotted_path(count_invocations))
+ else:
+ expire_funcs.append(get_dotted_path(count_invocations_negative))
+ return expire_funcs
+
+
+class AllocationModelSaveMethodTests(TestCase):
+
+ def setUp(self):
+ count_invocations.invocation_count = 0
+ count_invocations_negative.invocation_count = 0
+
+ @classmethod
+ def setUpTestData(cls):
+ """Set up allocation to test clean method"""
+ cls.active_status: AllocationStatusChoice = AllocationStatusChoiceFactory(name="Active")
+ cls.expired_status: AllocationStatusChoice = AllocationStatusChoiceFactory(name="Expired")
+ cls.other_status: AllocationStatusChoice = AllocationStatusChoiceFactory(name="Other")
+ cls.project: Project = ProjectFactory()
+
+ @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=[get_dotted_path(allocation_func_on_expire_exception),])
+ def test_on_expiration_calls_single_func_in_funcs_on_expire(self):
+ """Test that the allocation save method calls the functions specified in ALLOCATION_FUNCS_ON_EXPIRE when it expires."""
+ allocation = AllocationFactory(status=self.active_status)
+ with self.assertRaises(AllocationFuncOnExpireException):
+ allocation.status = self.expired_status
+ allocation.save()
+
+ @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(count_invocations))
+ def test_on_expiration_calls_multiple_funcs_in_funcs_on_expire(self):
+ """Test that the allocation save method calls a function multiple times when ALLOCATION_FUNCS_ON_EXPIRE has multiple instances of it."""
+ allocation = AllocationFactory(status=self.active_status)
+ allocation.status = self.expired_status
+ allocation.save()
+ self.assertEqual(count_invocations.invocation_count, NUMBER_OF_INVOCATIONS)
+
+ @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_different_expire_funcs())
+ def test_on_expiration_calls_multiple_different_funcs_in_funcs_on_expire(self):
+ """Test that the allocation save method calls all the different functions present in the list ALLOCATION_FUNCS_ON_EXPIRE."""
+ allocation = AllocationFactory(status=self.active_status)
+ allocation.status = self.expired_status
+
+ # the last function in the list is allocation_func_on_expire_exception, which raises an exception
+ with self.assertRaises(AllocationFuncOnExpireException):
+ allocation.save()
+
+ # the other functions will have been called a different number of times depending on whether NUMBER_OF_INVOCATIONS is odd or even
+ if NUMBER_OF_INVOCATIONS % 2 == 0:
+ expected_positive_invocations = NUMBER_OF_INVOCATIONS // 2
+ expected_negative_invocations = -((NUMBER_OF_INVOCATIONS // 2) - 1)
+ self.assertEqual(count_invocations.invocation_count, expected_positive_invocations)
+ self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations)
+ else:
+ expected_positive_invocations = NUMBER_OF_INVOCATIONS // 2
+ expected_negative_invocations = -(NUMBER_OF_INVOCATIONS // 2)
+ self.assertEqual(count_invocations.invocation_count, expected_positive_invocations)
+ self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations)
+
+ @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ def test_no_expire_no_funcs_on_expire_called(self):
+ """Test that the allocation save method does not call any functions when the allocation is not expired."""
+ allocation = AllocationFactory(status=self.active_status)
+ allocation.save()
+
+ @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ def test_allocation_changed_but_always_expired_no_funcs_on_expire_called(self):
+ """Test that the allocation save method does not call any functions when the allocation is always expired."""
+ allocation = AllocationFactory(status=self.expired_status)
+ allocation.justification = "This allocation is always expired."
+ allocation.save()
+
+ @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ def test_allocation_changed_but_never_expired_no_funcs_on_expire_called(self):
+ """Test that the allocation save method does not call any functions when the allocation is never expired."""
+ allocation = AllocationFactory(status=self.active_status)
+ allocation.status = self.other_status
+ allocation.save()
+
+ @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ def test_allocation_always_expired_no_funcs_on_expire_called(self):
+ """Test that the allocation save method does not call any functions when the allocation is always expired."""
+ allocation = AllocationFactory(status=self.expired_status)
+ allocation.justification = "This allocation is always expired."
+ allocation.save()
+
+ @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ def test_allocation_reactivated_no_funcs_on_expire_called(self):
+ """Test that the allocation save method does not call any functions when the allocation is reactivated."""
+ allocation = AllocationFactory(status=self.expired_status)
+ allocation.status = self.active_status
+ allocation.save()
+
+ @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=[])
+ def test_new_allocation_is_in_database(self):
+ """Test that a new allocation is saved in the database."""
+ allocation: Allocation = AllocationFactory(status=self.active_status)
+ allocation.save()
+ self.assertTrue(Allocation.objects.filter(id=allocation.id).exists())
+
+ @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=[])
+ def test_multiple_new_allocations_are_in_database(self):
+ """Test that multiple new allocations are saved in the database."""
+ allocations = [AllocationFactory(status=self.active_status) for _ in range(25)]
+ for allocation in allocations:
+ self.assertTrue(Allocation.objects.filter(id=allocation.id).exists())
+
+class AllocationModelExpiresInTests(TestCase):
+ # going to skip ths until I know how datetimes should be handled
+ ...
From 5f4a134fa364056777d833f8012ca56437891fab Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Thu, 12 Jun 2025 15:45:07 -0400
Subject: [PATCH 03/12] lint and format
---
.../core/allocation/tests/test_models.py | 80 +++++++++++++------
1 file changed, 57 insertions(+), 23 deletions(-)
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
index ff2164cb9..aabbe7672 100644
--- a/coldfront/core/allocation/tests/test_models.py
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -4,17 +4,20 @@
"""Unit tests for the allocation models"""
-from django.test import TestCase, override_settings
-
-from coldfront.core.test_helpers.factories import AllocationFactory, AllocationStatusChoiceFactory, ProjectFactory, ResourceFactory
-from coldfront.core.allocation.models import Allocation, AllocationStatusChoice
-from coldfront.core.project.models import Project
import datetime
+
from django.core.exceptions import ValidationError
-from unittest.mock import patch
+from django.test import TestCase, override_settings
from django.utils.safestring import SafeString
-
+from coldfront.core.allocation.models import Allocation, AllocationStatusChoice
+from coldfront.core.project.models import Project
+from coldfront.core.test_helpers.factories import (
+ AllocationFactory,
+ AllocationStatusChoiceFactory,
+ ProjectFactory,
+ ResourceFactory,
+)
class AllocationModelTests(TestCase):
@@ -44,14 +47,18 @@ def setUpTestData(cls):
def test_status_is_expired_and_no_end_date_has_validation_error(self):
"""Test that an allocation with status 'expired' and no end date raises a validation error."""
- actual_allocation: Allocation = AllocationFactory.build(status=self.expired_status, end_date=None, project=self.project)
+ actual_allocation: Allocation = AllocationFactory.build(
+ status=self.expired_status, end_date=None, project=self.project
+ )
with self.assertRaises(ValidationError):
actual_allocation.full_clean()
def test_status_is_expired_and_end_date_not_past_has_validation_error(self):
"""Test that an allocation with status 'expired' and end date in the future raises a validation error."""
end_date_in_the_future: datetime.date = (datetime.datetime.now() + datetime.timedelta(days=1)).date()
- actual_allocation: Allocation = AllocationFactory.build(status=self.expired_status, end_date=end_date_in_the_future, project=self.project)
+ actual_allocation: Allocation = AllocationFactory.build(
+ status=self.expired_status, end_date=end_date_in_the_future, project=self.project
+ )
with self.assertRaises(ValidationError):
actual_allocation.full_clean()
@@ -60,7 +67,9 @@ def test_status_is_expired_and_start_date_after_end_date_has_validation_error(se
end_date: datetime.date = (datetime.datetime.now() + datetime.timedelta(days=1)).date()
start_date_after_end_date: datetime.date = end_date + datetime.timedelta(days=1)
- actual_allocation: Allocation = AllocationFactory.build(status=self.expired_status, start_date=start_date_after_end_date, end_date=end_date, project=self.project)
+ actual_allocation: Allocation = AllocationFactory.build(
+ status=self.expired_status, start_date=start_date_after_end_date, end_date=end_date, project=self.project
+ )
with self.assertRaises(ValidationError):
actual_allocation.full_clean()
@@ -69,34 +78,44 @@ def test_status_is_expired_and_start_date_before_end_date_no_error(self):
start_date: datetime.date = datetime.datetime(year=2023, month=11, day=2).date()
end_date: datetime.date = start_date + datetime.timedelta(days=40)
- actual_allocation: Allocation = AllocationFactory.build(status=self.expired_status, start_date=start_date, end_date=end_date, project=self.project)
+ actual_allocation: Allocation = AllocationFactory.build(
+ status=self.expired_status, start_date=start_date, end_date=end_date, project=self.project
+ )
actual_allocation.full_clean()
def test_status_is_expired_and_start_date_equals_end_date_no_error(self):
"""Test that an allocation with status 'expired' and start date equal to end date does not raise a validation error."""
start_and_end_date: datetime.date = datetime.datetime(year=1997, month=4, day=20).date()
- actual_allocation: Allocation = AllocationFactory.build(status=self.expired_status, start_date=start_and_end_date, end_date=start_and_end_date, project=self.project)
+ actual_allocation: Allocation = AllocationFactory.build(
+ status=self.expired_status, start_date=start_and_end_date, end_date=start_and_end_date, project=self.project
+ )
actual_allocation.full_clean()
def test_status_is_active_and_no_start_date_has_validation_error(self):
"""Test that an allocation with status 'active' and no start date raises a validation error."""
- actual_allocation: Allocation = AllocationFactory.build(status=self.active_status, start_date=None, project=self.project)
+ actual_allocation: Allocation = AllocationFactory.build(
+ status=self.active_status, start_date=None, project=self.project
+ )
with self.assertRaises(ValidationError):
actual_allocation.full_clean()
def test_status_is_active_and_no_end_date_has_validation_error(self):
"""Test that an allocation with status 'active' and no end date raises a validation error."""
- actual_allocation: Allocation = AllocationFactory.build(status=self.active_status, end_date=None, project=self.project)
+ actual_allocation: Allocation = AllocationFactory.build(
+ status=self.active_status, end_date=None, project=self.project
+ )
with self.assertRaises(ValidationError):
actual_allocation.full_clean()
def test_status_is_active_and_start_date_after_end_date_has_validation_error(self):
"""Test that an allocation with status 'active' and start date after end date raises a validation error."""
end_date: datetime.date = (datetime.datetime.now() + datetime.timedelta(days=1)).date()
- start_date_after_end_date: datetime.date = (end_date + datetime.timedelta(days=1))
+ start_date_after_end_date: datetime.date = end_date + datetime.timedelta(days=1)
- actual_allocation: Allocation = AllocationFactory.build(status=self.active_status, start_date=start_date_after_end_date, end_date=end_date, project=self.project)
+ actual_allocation: Allocation = AllocationFactory.build(
+ status=self.active_status, start_date=start_date_after_end_date, end_date=end_date, project=self.project
+ )
with self.assertRaises(ValidationError):
actual_allocation.full_clean()
@@ -105,43 +124,54 @@ def test_status_is_active_and_start_date_before_end_date_no_error(self):
start_date: datetime.date = datetime.datetime(year=2001, month=5, day=3).date()
end_date: datetime.date = start_date + datetime.timedelta(days=160)
- actual_allocation: Allocation = AllocationFactory.build(status=self.active_status, start_date=start_date, end_date=end_date, project=self.project)
+ actual_allocation: Allocation = AllocationFactory.build(
+ status=self.active_status, start_date=start_date, end_date=end_date, project=self.project
+ )
actual_allocation.full_clean()
def test_status_is_active_and_start_date_equals_end_date_no_error(self):
"""Test that an allocation with status 'active' and start date equal to end date does not raise a validation error."""
start_and_end_date: datetime.date = datetime.datetime(year=2005, month=6, day=3).date()
- actual_allocation: Allocation = AllocationFactory.build(status=self.active_status, start_date=start_and_end_date, end_date=start_and_end_date, project=self.project)
+ actual_allocation: Allocation = AllocationFactory.build(
+ status=self.active_status, start_date=start_and_end_date, end_date=start_and_end_date, project=self.project
+ )
actual_allocation.full_clean()
-
class AllocationFuncOnExpireException(Exception):
"""Custom exception for testing allocation expiration function in the AllocationModelSaveMethodTests class."""
+
pass
+
def allocation_func_on_expire_exception(*args, **kwargs):
"""Test function to be called on allocation expiration in the AllocationModelSaveMethodTests class."""
raise AllocationFuncOnExpireException("This is a test exception for allocation expiration.")
+
def get_dotted_path(func):
"""Return the dotted path string for a Python function in the AllocationModelSaveMethodTests class."""
return f"{func.__module__}.{func.__qualname__}"
+
NUMBER_OF_INVOCATIONS = 12
+
def count_invocations(*args, **kwargs):
- count_invocations.invocation_count = getattr(count_invocations, 'invocation_count', 0) + 1
+ count_invocations.invocation_count = getattr(count_invocations, "invocation_count", 0) + 1
pass
+
def count_invocations_negative(*args, **kwargs):
- count_invocations_negative.invocation_count = getattr(count_invocations_negative, 'invocation_count', 0) - 1
+ count_invocations_negative.invocation_count = getattr(count_invocations_negative, "invocation_count", 0) - 1
pass
+
def list_of_same_expire_funcs(func: callable, size=NUMBER_OF_INVOCATIONS) -> list[str]:
return [get_dotted_path(func) for _ in range(size)]
+
def list_of_different_expire_funcs() -> list[str]:
"""Return a list of different functions to be called on allocation expiration.
The list will have a length of NUMBER_OF_INVOCATIONS, with the last function being allocation_func_on_expire_exception.
@@ -160,7 +190,6 @@ def list_of_different_expire_funcs() -> list[str]:
class AllocationModelSaveMethodTests(TestCase):
-
def setUp(self):
count_invocations.invocation_count = 0
count_invocations_negative.invocation_count = 0
@@ -173,7 +202,11 @@ def setUpTestData(cls):
cls.other_status: AllocationStatusChoice = AllocationStatusChoiceFactory(name="Other")
cls.project: Project = ProjectFactory()
- @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=[get_dotted_path(allocation_func_on_expire_exception),])
+ @override_settings(
+ ALLOCATION_FUNCS_ON_EXPIRE=[
+ get_dotted_path(allocation_func_on_expire_exception),
+ ]
+ )
def test_on_expiration_calls_single_func_in_funcs_on_expire(self):
"""Test that the allocation save method calls the functions specified in ALLOCATION_FUNCS_ON_EXPIRE when it expires."""
allocation = AllocationFactory(status=self.active_status)
@@ -259,6 +292,7 @@ def test_multiple_new_allocations_are_in_database(self):
for allocation in allocations:
self.assertTrue(Allocation.objects.filter(id=allocation.id).exists())
+
class AllocationModelExpiresInTests(TestCase):
# going to skip ths until I know how datetimes should be handled
...
From d0e3291a3971069528f05f3bf65a23a3c071da59 Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Mon, 16 Jun 2025 13:51:13 -0400
Subject: [PATCH 04/12] Fixed datetimes in tests to use django's timezone
utility
Signed-off-by: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
---
coldfront/core/allocation/tests/test_models.py | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
index aabbe7672..e7a6d10c5 100644
--- a/coldfront/core/allocation/tests/test_models.py
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -5,6 +5,7 @@
"""Unit tests for the allocation models"""
import datetime
+from django.utils import timezone
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings
@@ -55,7 +56,7 @@ def test_status_is_expired_and_no_end_date_has_validation_error(self):
def test_status_is_expired_and_end_date_not_past_has_validation_error(self):
"""Test that an allocation with status 'expired' and end date in the future raises a validation error."""
- end_date_in_the_future: datetime.date = (datetime.datetime.now() + datetime.timedelta(days=1)).date()
+ end_date_in_the_future: datetime.date = (timezone.now() + datetime.timedelta(days=1)).date()
actual_allocation: Allocation = AllocationFactory.build(
status=self.expired_status, end_date=end_date_in_the_future, project=self.project
)
@@ -64,7 +65,7 @@ def test_status_is_expired_and_end_date_not_past_has_validation_error(self):
def test_status_is_expired_and_start_date_after_end_date_has_validation_error(self):
"""Test that an allocation with status 'expired' and start date after end date raises a validation error."""
- end_date: datetime.date = (datetime.datetime.now() + datetime.timedelta(days=1)).date()
+ end_date: datetime.date = (timezone.now() + datetime.timedelta(days=1)).date()
start_date_after_end_date: datetime.date = end_date + datetime.timedelta(days=1)
actual_allocation: Allocation = AllocationFactory.build(
@@ -75,7 +76,7 @@ def test_status_is_expired_and_start_date_after_end_date_has_validation_error(se
def test_status_is_expired_and_start_date_before_end_date_no_error(self):
"""Test that an allocation with status 'expired' and start date before end date does not raise a validation error."""
- start_date: datetime.date = datetime.datetime(year=2023, month=11, day=2).date()
+ start_date: datetime.date = datetime.datetime(year=2023, month=11, day=2, tzinfo=timezone.get_current_timezone()).date()
end_date: datetime.date = start_date + datetime.timedelta(days=40)
actual_allocation: Allocation = AllocationFactory.build(
@@ -85,7 +86,8 @@ def test_status_is_expired_and_start_date_before_end_date_no_error(self):
def test_status_is_expired_and_start_date_equals_end_date_no_error(self):
"""Test that an allocation with status 'expired' and start date equal to end date does not raise a validation error."""
- start_and_end_date: datetime.date = datetime.datetime(year=1997, month=4, day=20).date()
+ start_and_end_date: datetime.date = datetime.datetime(year=1997, month=4, day=20, tzinfo=timezone.get_current_timezone()).date()
+
actual_allocation: Allocation = AllocationFactory.build(
status=self.expired_status, start_date=start_and_end_date, end_date=start_and_end_date, project=self.project
@@ -110,7 +112,7 @@ def test_status_is_active_and_no_end_date_has_validation_error(self):
def test_status_is_active_and_start_date_after_end_date_has_validation_error(self):
"""Test that an allocation with status 'active' and start date after end date raises a validation error."""
- end_date: datetime.date = (datetime.datetime.now() + datetime.timedelta(days=1)).date()
+ end_date: datetime.date = (timezone.now() + datetime.timedelta(days=1)).date()
start_date_after_end_date: datetime.date = end_date + datetime.timedelta(days=1)
actual_allocation: Allocation = AllocationFactory.build(
@@ -121,7 +123,7 @@ def test_status_is_active_and_start_date_after_end_date_has_validation_error(sel
def test_status_is_active_and_start_date_before_end_date_no_error(self):
"""Test that an allocation with status 'active' and start date before end date does not raise a validation error."""
- start_date: datetime.date = datetime.datetime(year=2001, month=5, day=3).date()
+ start_date: datetime.date = datetime.datetime(year=2001, month=5, day=3, tzinfo=timezone.get_current_timezone()).date()
end_date: datetime.date = start_date + datetime.timedelta(days=160)
actual_allocation: Allocation = AllocationFactory.build(
@@ -131,7 +133,7 @@ def test_status_is_active_and_start_date_before_end_date_no_error(self):
def test_status_is_active_and_start_date_equals_end_date_no_error(self):
"""Test that an allocation with status 'active' and start date equal to end date does not raise a validation error."""
- start_and_end_date: datetime.date = datetime.datetime(year=2005, month=6, day=3).date()
+ start_and_end_date: datetime.date = datetime.datetime(year=2005, month=6, day=3, tzinfo=timezone.get_current_timezone()).date()
actual_allocation: Allocation = AllocationFactory.build(
status=self.active_status, start_date=start_and_end_date, end_date=start_and_end_date, project=self.project
From cce541537c20fbdd421edfc294ad638edda232d4 Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Tue, 17 Jun 2025 13:04:16 -0400
Subject: [PATCH 05/12] Allocation model get_information tests
Signed-off-by: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
---
.../core/allocation/tests/test_models.py | 139 +++++++++++++++++-
1 file changed, 131 insertions(+), 8 deletions(-)
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
index e7a6d10c5..5b4015a62 100644
--- a/coldfront/core/allocation/tests/test_models.py
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -6,16 +6,20 @@
import datetime
from django.utils import timezone
-
+from django.utils.html import escape, format_html
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings
from django.utils.safestring import SafeString
-
-from coldfront.core.allocation.models import Allocation, AllocationStatusChoice
+from unittest.mock import patch
+from coldfront.core.allocation.models import Allocation, AllocationStatusChoice, AllocationAttribute
from coldfront.core.project.models import Project
from coldfront.core.test_helpers.factories import (
AllocationFactory,
AllocationStatusChoiceFactory,
+ AllocationAttributeFactory,
+ AllocationAttributeTypeFactory,
+ AAttributeTypeFactory,
+ AllocationAttributeUsageFactory,
ProjectFactory,
ResourceFactory,
)
@@ -143,7 +147,6 @@ def test_status_is_active_and_start_date_equals_end_date_no_error(self):
class AllocationFuncOnExpireException(Exception):
"""Custom exception for testing allocation expiration function in the AllocationModelSaveMethodTests class."""
-
pass
@@ -162,12 +165,10 @@ def get_dotted_path(func):
def count_invocations(*args, **kwargs):
count_invocations.invocation_count = getattr(count_invocations, "invocation_count", 0) + 1
- pass
def count_invocations_negative(*args, **kwargs):
count_invocations_negative.invocation_count = getattr(count_invocations_negative, "invocation_count", 0) - 1
- pass
def list_of_same_expire_funcs(func: callable, size=NUMBER_OF_INVOCATIONS) -> list[str]:
@@ -296,5 +297,127 @@ def test_multiple_new_allocations_are_in_database(self):
class AllocationModelExpiresInTests(TestCase):
- # going to skip ths until I know how datetimes should be handled
- ...
+
+ mocked_today = datetime.date(2025, 1, 1)
+ three_years_after_mocked_today = datetime.date(2028, 1, 1)
+ four_years_after_mocked_today = datetime.date(2029, 1, 1)
+
+ def test_end_date_is_today_returns_zero(self):
+ """Test that the expires_in method returns 0 when the end date is today."""
+ allocation: Allocation = AllocationFactory(end_date=timezone.now().date())
+ self.assertEqual(allocation.expires_in, 0)
+
+ def test_end_date_tomorrow_returns_one(self):
+ """Test that the expires_in method returns 1 when the end date is tomorrow."""
+ tomorrow: datetime.date = (timezone.now() + datetime.timedelta(days=1)).date()
+ allocation: Allocation = AllocationFactory(end_date=tomorrow)
+ self.assertEqual(allocation.expires_in, 1)
+
+ def test_end_date_yesterday_returns_negative_one(self):
+ """Test that the expires_in method returns -1 when the end date is yesterday."""
+ yesterday: datetime.date = (timezone.now() - datetime.timedelta(days=1)).date()
+ allocation: Allocation = AllocationFactory(end_date=yesterday)
+ self.assertEqual(allocation.expires_in, -1)
+
+ def test_end_date_one_week_ago_returns_negative_seven(self):
+ """Test that the expires_in method returns -7 when the end date is one week ago."""
+ days_in_a_week: int = 7
+ one_week_ago: datetime.date = (timezone.now() - datetime.timedelta(days=days_in_a_week)).date()
+ allocation: Allocation = AllocationFactory(end_date=one_week_ago)
+ self.assertEqual(allocation.expires_in, -days_in_a_week)
+
+ def test_end_date_in_one_week_returns_seven(self):
+ """Test that the expires_in method returns 7 when the end date is in one week."""
+ days_in_a_week: int = 7
+ one_week_from_now: datetime.date = (timezone.now() + datetime.timedelta(days=days_in_a_week)).date()
+ allocation: Allocation = AllocationFactory(end_date=one_week_from_now)
+ self.assertEqual(allocation.expires_in, days_in_a_week)
+
+ def test_end_date_in_three_years_without_leap_day_returns_days_including_no_leap_day(self):
+ """Test that the expires_in method returns the correct number of days in three years when those years did not have a leap year."""
+ days_in_three_years_excluding_leap_year = 365 * 3
+
+ with patch("coldfront.core.allocation.models.datetime") as mock_datetime:
+ mock_datetime.date.today.return_value = self.mocked_today
+
+ allocation: Allocation = AllocationFactory(end_date=self.three_years_after_mocked_today)
+
+ self.assertEqual(allocation.expires_in, days_in_three_years_excluding_leap_year)
+
+ def test_end_date_in_four_years_returns_days_including_leap_day(self):
+ """Test that the expires_in method accounts for the extra day of a leap year."""
+ days_in_four_years_including_leap_year = (365 * 4) + 1
+
+ with patch("coldfront.core.allocation.models.datetime") as mock_datetime:
+ mock_datetime.date.today.return_value = self.mocked_today
+
+ allocation: Allocation = AllocationFactory(end_date=self.four_years_after_mocked_today)
+
+ self.assertEqual(allocation.expires_in, days_in_four_years_including_leap_year)
+
+
+
+class AllocationModelGetInformationTests(TestCase):
+
+ def test_no_allocation_attributes_returns_empty_string(self):
+ """Test that the get_information method returns an empty string if there are no allocation attributes."""
+ allocation: Allocation = AllocationFactory()
+ self.assertEqual(allocation.get_information, "")
+
+ def test_attribute_value_is_not_a_number_returns_invalid_value_string(self):
+ """Test that the get_information method returns an empty string if the only attribute has value not parsable to a number."""
+ allocation: Allocation = AllocationFactory()
+ text_not_parsable_to_number = "this is not parsable to a number"
+ allocation_attribute: AllocationAttribute = AllocationAttributeFactory(allocation=allocation, value=text_not_parsable_to_number)
+ self.assertEqual(allocation.get_information, "")
+
+ def test_attribute_value_is_zero_returns_100_percent_string(self):
+ allocation: Allocation = AllocationFactory()
+ allocation_attribute: AllocationAttribute = AllocationAttributeFactory(allocation=allocation, value=0)
+ allocation_attribute_usage = AllocationAttributeUsageFactory(allocation_attribute=allocation_attribute, value=10)
+
+ allocation_attribute_type_name: str = allocation_attribute.allocation_attribute_type.name
+ allocation_attribute_usage_value: float = float(allocation_attribute_usage.value)
+ allocation_attribute_value: str = allocation_attribute.value
+ expected_percent = 100
+
+ expected_information: SafeString = format_html(
+ "{}: {}/{} ({} %)
",
+ allocation_attribute_type_name,
+ allocation_attribute_usage_value,
+ allocation_attribute_value,
+ expected_percent,
+ )
+
+ self.assertEqual(allocation.get_information, expected_information)
+
+ def test_multiple_attributes_with_same_type_returns_combined_information(self):
+ """Test that the get_information method returns combined information for multiple attributes."""
+ allocation: Allocation = AllocationFactory()
+ allocation_attribute_type = AllocationAttributeTypeFactory()
+
+ allocation_attribute_1: AllocationAttribute = AllocationAttributeFactory(
+ allocation=allocation, allocation_attribute_type=allocation_attribute_type, value=100
+ )
+ allocation_attribute_2: AllocationAttribute = AllocationAttributeFactory(
+ allocation=allocation, allocation_attribute_type=allocation_attribute_type, value=1000
+ )
+ allocation_attribute_usage_1 = AllocationAttributeUsageFactory(allocation_attribute=allocation_attribute_1, value=50)
+ allocation_attribute_usage_2 = AllocationAttributeUsageFactory(allocation_attribute=allocation_attribute_2, value=500)
+
+ percent_1 = round( (float(allocation_attribute_usage_1.value) / float(allocation_attribute_1.value)) * 10_000 ) / 100
+ percent_2 = round( (float(allocation_attribute_usage_2.value) / float(allocation_attribute_2.value)) * 10_000 ) / 100
+
+ expected_information: SafeString = format_html(
+ "{}: {}/{} ({} %)
{}: {}/{} ({} %)
",
+ allocation_attribute_type.name,
+ float(allocation_attribute_usage_1.value),
+ allocation_attribute_1.value,
+ percent_1,
+ allocation_attribute_type.name,
+ float(allocation_attribute_usage_2.value),
+ allocation_attribute_2.value,
+ percent_2,
+ )
+
+ self.assertEqual(allocation.get_information, expected_information)
From 1e7558507986ae8db80d8bcdf97d7f8cea976d82 Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Wed, 18 Jun 2025 14:13:36 -0400
Subject: [PATCH 06/12] Used utc for all datetimes.
Signed-off-by: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
---
coldfront/core/allocation/tests/test_models.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
index 5b4015a62..0833ad6aa 100644
--- a/coldfront/core/allocation/tests/test_models.py
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -80,7 +80,7 @@ def test_status_is_expired_and_start_date_after_end_date_has_validation_error(se
def test_status_is_expired_and_start_date_before_end_date_no_error(self):
"""Test that an allocation with status 'expired' and start date before end date does not raise a validation error."""
- start_date: datetime.date = datetime.datetime(year=2023, month=11, day=2, tzinfo=timezone.get_current_timezone()).date()
+ start_date: datetime.date = datetime.datetime(year=2023, month=11, day=2, tzinfo=timezone.utc).date()
end_date: datetime.date = start_date + datetime.timedelta(days=40)
actual_allocation: Allocation = AllocationFactory.build(
@@ -90,7 +90,7 @@ def test_status_is_expired_and_start_date_before_end_date_no_error(self):
def test_status_is_expired_and_start_date_equals_end_date_no_error(self):
"""Test that an allocation with status 'expired' and start date equal to end date does not raise a validation error."""
- start_and_end_date: datetime.date = datetime.datetime(year=1997, month=4, day=20, tzinfo=timezone.get_current_timezone()).date()
+ start_and_end_date: datetime.date = datetime.datetime(year=1997, month=4, day=20, tzinfo=timezone.utc).date()
actual_allocation: Allocation = AllocationFactory.build(
@@ -127,7 +127,7 @@ def test_status_is_active_and_start_date_after_end_date_has_validation_error(sel
def test_status_is_active_and_start_date_before_end_date_no_error(self):
"""Test that an allocation with status 'active' and start date before end date does not raise a validation error."""
- start_date: datetime.date = datetime.datetime(year=2001, month=5, day=3, tzinfo=timezone.get_current_timezone()).date()
+ start_date: datetime.date = datetime.datetime(year=2001, month=5, day=3, tzinfo=timezone.utc).date()
end_date: datetime.date = start_date + datetime.timedelta(days=160)
actual_allocation: Allocation = AllocationFactory.build(
@@ -137,7 +137,7 @@ def test_status_is_active_and_start_date_before_end_date_no_error(self):
def test_status_is_active_and_start_date_equals_end_date_no_error(self):
"""Test that an allocation with status 'active' and start date equal to end date does not raise a validation error."""
- start_and_end_date: datetime.date = datetime.datetime(year=2005, month=6, day=3, tzinfo=timezone.get_current_timezone()).date()
+ start_and_end_date: datetime.date = datetime.datetime(year=2005, month=6, day=3, tzinfo=timezone.utc).date()
actual_allocation: Allocation = AllocationFactory.build(
status=self.active_status, start_date=start_and_end_date, end_date=start_and_end_date, project=self.project
From 5ac537c6138926bbfed56c3374dddf86a0366269 Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Wed, 18 Jun 2025 14:54:32 -0400
Subject: [PATCH 07/12] Ruff format and linter applied.
Signed-off-by: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
---
.../core/allocation/tests/test_models.py | 44 ++++++++++++-------
1 file changed, 27 insertions(+), 17 deletions(-)
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
index 0833ad6aa..886532955 100644
--- a/coldfront/core/allocation/tests/test_models.py
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -5,21 +5,22 @@
"""Unit tests for the allocation models"""
import datetime
-from django.utils import timezone
-from django.utils.html import escape, format_html
+from unittest.mock import patch
+
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings
+from django.utils import timezone
+from django.utils.html import format_html
from django.utils.safestring import SafeString
-from unittest.mock import patch
-from coldfront.core.allocation.models import Allocation, AllocationStatusChoice, AllocationAttribute
+
+from coldfront.core.allocation.models import Allocation, AllocationAttribute, AllocationStatusChoice
from coldfront.core.project.models import Project
from coldfront.core.test_helpers.factories import (
- AllocationFactory,
- AllocationStatusChoiceFactory,
AllocationAttributeFactory,
AllocationAttributeTypeFactory,
- AAttributeTypeFactory,
AllocationAttributeUsageFactory,
+ AllocationFactory,
+ AllocationStatusChoiceFactory,
ProjectFactory,
ResourceFactory,
)
@@ -91,7 +92,6 @@ def test_status_is_expired_and_start_date_before_end_date_no_error(self):
def test_status_is_expired_and_start_date_equals_end_date_no_error(self):
"""Test that an allocation with status 'expired' and start date equal to end date does not raise a validation error."""
start_and_end_date: datetime.date = datetime.datetime(year=1997, month=4, day=20, tzinfo=timezone.utc).date()
-
actual_allocation: Allocation = AllocationFactory.build(
status=self.expired_status, start_date=start_and_end_date, end_date=start_and_end_date, project=self.project
@@ -147,6 +147,7 @@ def test_status_is_active_and_start_date_equals_end_date_no_error(self):
class AllocationFuncOnExpireException(Exception):
"""Custom exception for testing allocation expiration function in the AllocationModelSaveMethodTests class."""
+
pass
@@ -297,7 +298,6 @@ def test_multiple_new_allocations_are_in_database(self):
class AllocationModelExpiresInTests(TestCase):
-
mocked_today = datetime.date(2025, 1, 1)
three_years_after_mocked_today = datetime.date(2028, 1, 1)
four_years_after_mocked_today = datetime.date(2029, 1, 1)
@@ -354,11 +354,9 @@ def test_end_date_in_four_years_returns_days_including_leap_day(self):
allocation: Allocation = AllocationFactory(end_date=self.four_years_after_mocked_today)
self.assertEqual(allocation.expires_in, days_in_four_years_including_leap_year)
-
class AllocationModelGetInformationTests(TestCase):
-
def test_no_allocation_attributes_returns_empty_string(self):
"""Test that the get_information method returns an empty string if there are no allocation attributes."""
allocation: Allocation = AllocationFactory()
@@ -368,13 +366,17 @@ def test_attribute_value_is_not_a_number_returns_invalid_value_string(self):
"""Test that the get_information method returns an empty string if the only attribute has value not parsable to a number."""
allocation: Allocation = AllocationFactory()
text_not_parsable_to_number = "this is not parsable to a number"
- allocation_attribute: AllocationAttribute = AllocationAttributeFactory(allocation=allocation, value=text_not_parsable_to_number)
+ allocation_attribute: AllocationAttribute = AllocationAttributeFactory( # noqa: F841
+ allocation=allocation, value=text_not_parsable_to_number
+ )
self.assertEqual(allocation.get_information, "")
def test_attribute_value_is_zero_returns_100_percent_string(self):
allocation: Allocation = AllocationFactory()
allocation_attribute: AllocationAttribute = AllocationAttributeFactory(allocation=allocation, value=0)
- allocation_attribute_usage = AllocationAttributeUsageFactory(allocation_attribute=allocation_attribute, value=10)
+ allocation_attribute_usage = AllocationAttributeUsageFactory(
+ allocation_attribute=allocation_attribute, value=10
+ )
allocation_attribute_type_name: str = allocation_attribute.allocation_attribute_type.name
allocation_attribute_usage_value: float = float(allocation_attribute_usage.value)
@@ -402,11 +404,19 @@ def test_multiple_attributes_with_same_type_returns_combined_information(self):
allocation_attribute_2: AllocationAttribute = AllocationAttributeFactory(
allocation=allocation, allocation_attribute_type=allocation_attribute_type, value=1000
)
- allocation_attribute_usage_1 = AllocationAttributeUsageFactory(allocation_attribute=allocation_attribute_1, value=50)
- allocation_attribute_usage_2 = AllocationAttributeUsageFactory(allocation_attribute=allocation_attribute_2, value=500)
+ allocation_attribute_usage_1 = AllocationAttributeUsageFactory(
+ allocation_attribute=allocation_attribute_1, value=50
+ )
+ allocation_attribute_usage_2 = AllocationAttributeUsageFactory(
+ allocation_attribute=allocation_attribute_2, value=500
+ )
- percent_1 = round( (float(allocation_attribute_usage_1.value) / float(allocation_attribute_1.value)) * 10_000 ) / 100
- percent_2 = round( (float(allocation_attribute_usage_2.value) / float(allocation_attribute_2.value)) * 10_000 ) / 100
+ percent_1 = (
+ round((float(allocation_attribute_usage_1.value) / float(allocation_attribute_1.value)) * 10_000) / 100
+ )
+ percent_2 = (
+ round((float(allocation_attribute_usage_2.value) / float(allocation_attribute_2.value)) * 10_000) / 100
+ )
expected_information: SafeString = format_html(
"{}: {}/{} ({} %)
{}: {}/{} ({} %)
",
From 670bc01fdf4dcc4a5ad01dda6350946ade2b2b55 Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Thu, 19 Jun 2025 10:32:10 -0400
Subject: [PATCH 08/12] Ignoring warnings caused by type checker with the
functinal metaprogramming
Signed-off-by: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
---
.../core/allocation/tests/test_models.py | 21 ++++++++++---------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
index 886532955..12220180d 100644
--- a/coldfront/core/allocation/tests/test_models.py
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -24,6 +24,7 @@
ProjectFactory,
ResourceFactory,
)
+import typing
class AllocationModelTests(TestCase):
@@ -165,14 +166,14 @@ def get_dotted_path(func):
def count_invocations(*args, **kwargs):
- count_invocations.invocation_count = getattr(count_invocations, "invocation_count", 0) + 1
+ count_invocations.invocation_count = getattr(count_invocations, "invocation_count", 0) + 1 # type: ignore
def count_invocations_negative(*args, **kwargs):
- count_invocations_negative.invocation_count = getattr(count_invocations_negative, "invocation_count", 0) - 1
+ count_invocations_negative.invocation_count = getattr(count_invocations_negative, "invocation_count", 0) - 1 # type: ignore
-def list_of_same_expire_funcs(func: callable, size=NUMBER_OF_INVOCATIONS) -> list[str]:
+def list_of_same_expire_funcs(func: typing.Callable, size=NUMBER_OF_INVOCATIONS) -> list[str]:
return [get_dotted_path(func) for _ in range(size)]
@@ -195,8 +196,8 @@ def list_of_different_expire_funcs() -> list[str]:
class AllocationModelSaveMethodTests(TestCase):
def setUp(self):
- count_invocations.invocation_count = 0
- count_invocations_negative.invocation_count = 0
+ count_invocations.invocation_count = 0 # type: ignore
+ count_invocations_negative.invocation_count = 0 # type: ignore
@classmethod
def setUpTestData(cls):
@@ -224,7 +225,7 @@ def test_on_expiration_calls_multiple_funcs_in_funcs_on_expire(self):
allocation = AllocationFactory(status=self.active_status)
allocation.status = self.expired_status
allocation.save()
- self.assertEqual(count_invocations.invocation_count, NUMBER_OF_INVOCATIONS)
+ self.assertEqual(count_invocations.invocation_count, NUMBER_OF_INVOCATIONS) # type: ignore
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_different_expire_funcs())
def test_on_expiration_calls_multiple_different_funcs_in_funcs_on_expire(self):
@@ -240,13 +241,13 @@ def test_on_expiration_calls_multiple_different_funcs_in_funcs_on_expire(self):
if NUMBER_OF_INVOCATIONS % 2 == 0:
expected_positive_invocations = NUMBER_OF_INVOCATIONS // 2
expected_negative_invocations = -((NUMBER_OF_INVOCATIONS // 2) - 1)
- self.assertEqual(count_invocations.invocation_count, expected_positive_invocations)
- self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations)
+ self.assertEqual(count_invocations.invocation_count, expected_positive_invocations) # type: ignore
+ self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations) # type: ignore
else:
expected_positive_invocations = NUMBER_OF_INVOCATIONS // 2
expected_negative_invocations = -(NUMBER_OF_INVOCATIONS // 2)
- self.assertEqual(count_invocations.invocation_count, expected_positive_invocations)
- self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations)
+ self.assertEqual(count_invocations.invocation_count, expected_positive_invocations) # type: ignore
+ self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations) # type: ignore
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_no_expire_no_funcs_on_expire_called(self):
From b5643b54a023c231d5d2d59834ba680c17f42d94 Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Thu, 19 Jun 2025 11:15:10 -0400
Subject: [PATCH 09/12] Fixed test for view list to accurately reflect what it
is testing
Signed-off-by: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
---
.../core/allocation/tests/test_models.py | 35 ++++++++++---------
1 file changed, 18 insertions(+), 17 deletions(-)
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
index 12220180d..5c6c5166d 100644
--- a/coldfront/core/allocation/tests/test_models.py
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -5,6 +5,7 @@
"""Unit tests for the allocation models"""
import datetime
+import typing
from unittest.mock import patch
from django.core.exceptions import ValidationError
@@ -13,7 +14,11 @@
from django.utils.html import format_html
from django.utils.safestring import SafeString
-from coldfront.core.allocation.models import Allocation, AllocationAttribute, AllocationStatusChoice
+from coldfront.core.allocation.models import (
+ Allocation,
+ AllocationAttribute,
+ AllocationStatusChoice,
+)
from coldfront.core.project.models import Project
from coldfront.core.test_helpers.factories import (
AllocationAttributeFactory,
@@ -24,7 +29,6 @@
ProjectFactory,
ResourceFactory,
)
-import typing
class AllocationModelTests(TestCase):
@@ -166,11 +170,11 @@ def get_dotted_path(func):
def count_invocations(*args, **kwargs):
- count_invocations.invocation_count = getattr(count_invocations, "invocation_count", 0) + 1 # type: ignore
+ count_invocations.invocation_count = getattr(count_invocations, "invocation_count", 0) + 1 # type: ignore
def count_invocations_negative(*args, **kwargs):
- count_invocations_negative.invocation_count = getattr(count_invocations_negative, "invocation_count", 0) - 1 # type: ignore
+ count_invocations_negative.invocation_count = getattr(count_invocations_negative, "invocation_count", 0) - 1 # type: ignore
def list_of_same_expire_funcs(func: typing.Callable, size=NUMBER_OF_INVOCATIONS) -> list[str]:
@@ -196,8 +200,8 @@ def list_of_different_expire_funcs() -> list[str]:
class AllocationModelSaveMethodTests(TestCase):
def setUp(self):
- count_invocations.invocation_count = 0 # type: ignore
- count_invocations_negative.invocation_count = 0 # type: ignore
+ count_invocations.invocation_count = 0 # type: ignore
+ count_invocations_negative.invocation_count = 0 # type: ignore
@classmethod
def setUpTestData(cls):
@@ -225,7 +229,7 @@ def test_on_expiration_calls_multiple_funcs_in_funcs_on_expire(self):
allocation = AllocationFactory(status=self.active_status)
allocation.status = self.expired_status
allocation.save()
- self.assertEqual(count_invocations.invocation_count, NUMBER_OF_INVOCATIONS) # type: ignore
+ self.assertEqual(count_invocations.invocation_count, NUMBER_OF_INVOCATIONS) # type: ignore
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_different_expire_funcs())
def test_on_expiration_calls_multiple_different_funcs_in_funcs_on_expire(self):
@@ -241,13 +245,13 @@ def test_on_expiration_calls_multiple_different_funcs_in_funcs_on_expire(self):
if NUMBER_OF_INVOCATIONS % 2 == 0:
expected_positive_invocations = NUMBER_OF_INVOCATIONS // 2
expected_negative_invocations = -((NUMBER_OF_INVOCATIONS // 2) - 1)
- self.assertEqual(count_invocations.invocation_count, expected_positive_invocations) # type: ignore
- self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations) # type: ignore
+ self.assertEqual(count_invocations.invocation_count, expected_positive_invocations) # type: ignore
+ self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations) # type: ignore
else:
expected_positive_invocations = NUMBER_OF_INVOCATIONS // 2
expected_negative_invocations = -(NUMBER_OF_INVOCATIONS // 2)
- self.assertEqual(count_invocations.invocation_count, expected_positive_invocations) # type: ignore
- self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations) # type: ignore
+ self.assertEqual(count_invocations.invocation_count, expected_positive_invocations) # type: ignore
+ self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations) # type: ignore
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_no_expire_no_funcs_on_expire_called(self):
@@ -363,13 +367,10 @@ def test_no_allocation_attributes_returns_empty_string(self):
allocation: Allocation = AllocationFactory()
self.assertEqual(allocation.get_information, "")
- def test_attribute_value_is_not_a_number_returns_invalid_value_string(self):
- """Test that the get_information method returns an empty string if the only attribute has value not parsable to a number."""
+ @override_settings(ALLOCATION_ATTRIBUTE_VIEW_LIST=[])
+ def test_attribute_type_not_in_view_list_returns_empty_string(self):
+ """Test that the get_information method returns an empty string if the attribute type is not in ALLOCATION_ATTRIBUTE_VIEW_LIST."""
allocation: Allocation = AllocationFactory()
- text_not_parsable_to_number = "this is not parsable to a number"
- allocation_attribute: AllocationAttribute = AllocationAttributeFactory( # noqa: F841
- allocation=allocation, value=text_not_parsable_to_number
- )
self.assertEqual(allocation.get_information, "")
def test_attribute_value_is_zero_returns_100_percent_string(self):
From 63cb98851f5b211e481375191b64da1a21e4b752 Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Fri, 20 Jun 2025 13:02:51 -0400
Subject: [PATCH 10/12] Skipping all tests the use env variables
---
coldfront/core/allocation/models.py | 1 -
coldfront/core/allocation/tests/test_models.py | 13 ++++++++++++-
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/coldfront/core/allocation/models.py b/coldfront/core/allocation/models.py
index 526b1f935..3e6ef9bf9 100644
--- a/coldfront/core/allocation/models.py
+++ b/coldfront/core/allocation/models.py
@@ -129,7 +129,6 @@ def save(self, *args, **kwargs):
"""Saves the project."""
if self.pk:
- ALLOCATION_FUNCS_ON_EXPIRE = import_from_settings("ALLOCATION_FUNCS_ON_EXPIRE", [])
old_obj = Allocation.objects.get(pk=self.pk)
if old_obj.status.name != self.status.name and self.status.name == "Expired":
for func_string in ALLOCATION_FUNCS_ON_EXPIRE:
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
index 5c6c5166d..e96417fb7 100644
--- a/coldfront/core/allocation/tests/test_models.py
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -6,8 +6,8 @@
import datetime
import typing
+from unittest import skip
from unittest.mock import patch
-
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings
from django.utils import timezone
@@ -211,6 +211,7 @@ def setUpTestData(cls):
cls.other_status: AllocationStatusChoice = AllocationStatusChoiceFactory(name="Other")
cls.project: Project = ProjectFactory()
+ @skip("We are rethinking how to test functions that rely on env variables.")
@override_settings(
ALLOCATION_FUNCS_ON_EXPIRE=[
get_dotted_path(allocation_func_on_expire_exception),
@@ -223,6 +224,7 @@ def test_on_expiration_calls_single_func_in_funcs_on_expire(self):
allocation.status = self.expired_status
allocation.save()
+ @skip("We are rethinking how to test functions that rely on env variables.")
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(count_invocations))
def test_on_expiration_calls_multiple_funcs_in_funcs_on_expire(self):
"""Test that the allocation save method calls a function multiple times when ALLOCATION_FUNCS_ON_EXPIRE has multiple instances of it."""
@@ -231,6 +233,7 @@ def test_on_expiration_calls_multiple_funcs_in_funcs_on_expire(self):
allocation.save()
self.assertEqual(count_invocations.invocation_count, NUMBER_OF_INVOCATIONS) # type: ignore
+ @skip("We are rethinking how to test functions that rely on env variables.")
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_different_expire_funcs())
def test_on_expiration_calls_multiple_different_funcs_in_funcs_on_expire(self):
"""Test that the allocation save method calls all the different functions present in the list ALLOCATION_FUNCS_ON_EXPIRE."""
@@ -253,12 +256,14 @@ def test_on_expiration_calls_multiple_different_funcs_in_funcs_on_expire(self):
self.assertEqual(count_invocations.invocation_count, expected_positive_invocations) # type: ignore
self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations) # type: ignore
+ @skip("We are rethinking how to test functions that rely on env variables.")
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_no_expire_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is not expired."""
allocation = AllocationFactory(status=self.active_status)
allocation.save()
+ @skip("We are rethinking how to test functions that rely on env variables.")
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_changed_but_always_expired_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is always expired."""
@@ -266,6 +271,7 @@ def test_allocation_changed_but_always_expired_no_funcs_on_expire_called(self):
allocation.justification = "This allocation is always expired."
allocation.save()
+ @skip("We are rethinking how to test functions that rely on env variables.")
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_changed_but_never_expired_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is never expired."""
@@ -273,6 +279,7 @@ def test_allocation_changed_but_never_expired_no_funcs_on_expire_called(self):
allocation.status = self.other_status
allocation.save()
+ @skip("We are rethinking how to test functions that rely on env variables.")
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_always_expired_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is always expired."""
@@ -280,6 +287,7 @@ def test_allocation_always_expired_no_funcs_on_expire_called(self):
allocation.justification = "This allocation is always expired."
allocation.save()
+ @skip("We are rethinking how to test functions that rely on env variables.")
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_reactivated_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is reactivated."""
@@ -287,6 +295,7 @@ def test_allocation_reactivated_no_funcs_on_expire_called(self):
allocation.status = self.active_status
allocation.save()
+ @skip("We are rethinking how to test functions that rely on env variables.")
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=[])
def test_new_allocation_is_in_database(self):
"""Test that a new allocation is saved in the database."""
@@ -294,6 +303,7 @@ def test_new_allocation_is_in_database(self):
allocation.save()
self.assertTrue(Allocation.objects.filter(id=allocation.id).exists())
+ @skip("We are rethinking how to test functions that rely on env variables.")
@override_settings(ALLOCATION_FUNCS_ON_EXPIRE=[])
def test_multiple_new_allocations_are_in_database(self):
"""Test that multiple new allocations are saved in the database."""
@@ -367,6 +377,7 @@ def test_no_allocation_attributes_returns_empty_string(self):
allocation: Allocation = AllocationFactory()
self.assertEqual(allocation.get_information, "")
+ @skip("We are rethinking how to test functions that rely on env variables.")
@override_settings(ALLOCATION_ATTRIBUTE_VIEW_LIST=[])
def test_attribute_type_not_in_view_list_returns_empty_string(self):
"""Test that the get_information method returns an empty string if the attribute type is not in ALLOCATION_ATTRIBUTE_VIEW_LIST."""
From 384c9b747716ce445560bc4b0363e3bcb6723376 Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Fri, 20 Jun 2025 13:54:17 -0400
Subject: [PATCH 11/12] Replcaed overwrite_settings with path to test methods
that depend on env variables
---
.../core/allocation/tests/test_models.py | 38 ++++++-------------
1 file changed, 12 insertions(+), 26 deletions(-)
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
index e96417fb7..db615f8bd 100644
--- a/coldfront/core/allocation/tests/test_models.py
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -15,6 +15,7 @@
from django.utils.safestring import SafeString
from coldfront.core.allocation.models import (
+ ALLOCATION_FUNCS_ON_EXPIRE,
Allocation,
AllocationAttribute,
AllocationStatusChoice,
@@ -211,12 +212,7 @@ def setUpTestData(cls):
cls.other_status: AllocationStatusChoice = AllocationStatusChoiceFactory(name="Other")
cls.project: Project = ProjectFactory()
- @skip("We are rethinking how to test functions that rely on env variables.")
- @override_settings(
- ALLOCATION_FUNCS_ON_EXPIRE=[
- get_dotted_path(allocation_func_on_expire_exception),
- ]
- )
+ @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_on_expiration_calls_single_func_in_funcs_on_expire(self):
"""Test that the allocation save method calls the functions specified in ALLOCATION_FUNCS_ON_EXPIRE when it expires."""
allocation = AllocationFactory(status=self.active_status)
@@ -224,8 +220,7 @@ def test_on_expiration_calls_single_func_in_funcs_on_expire(self):
allocation.status = self.expired_status
allocation.save()
- @skip("We are rethinking how to test functions that rely on env variables.")
- @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(count_invocations))
+ @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(count_invocations))
def test_on_expiration_calls_multiple_funcs_in_funcs_on_expire(self):
"""Test that the allocation save method calls a function multiple times when ALLOCATION_FUNCS_ON_EXPIRE has multiple instances of it."""
allocation = AllocationFactory(status=self.active_status)
@@ -233,8 +228,7 @@ def test_on_expiration_calls_multiple_funcs_in_funcs_on_expire(self):
allocation.save()
self.assertEqual(count_invocations.invocation_count, NUMBER_OF_INVOCATIONS) # type: ignore
- @skip("We are rethinking how to test functions that rely on env variables.")
- @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_different_expire_funcs())
+ @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_different_expire_funcs())
def test_on_expiration_calls_multiple_different_funcs_in_funcs_on_expire(self):
"""Test that the allocation save method calls all the different functions present in the list ALLOCATION_FUNCS_ON_EXPIRE."""
allocation = AllocationFactory(status=self.active_status)
@@ -256,55 +250,48 @@ def test_on_expiration_calls_multiple_different_funcs_in_funcs_on_expire(self):
self.assertEqual(count_invocations.invocation_count, expected_positive_invocations) # type: ignore
self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations) # type: ignore
- @skip("We are rethinking how to test functions that rely on env variables.")
- @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_no_expire_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is not expired."""
allocation = AllocationFactory(status=self.active_status)
allocation.save()
- @skip("We are rethinking how to test functions that rely on env variables.")
- @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_changed_but_always_expired_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is always expired."""
allocation = AllocationFactory(status=self.expired_status)
allocation.justification = "This allocation is always expired."
allocation.save()
- @skip("We are rethinking how to test functions that rely on env variables.")
- @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_changed_but_never_expired_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is never expired."""
allocation = AllocationFactory(status=self.active_status)
allocation.status = self.other_status
allocation.save()
- @skip("We are rethinking how to test functions that rely on env variables.")
- @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_always_expired_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is always expired."""
allocation = AllocationFactory(status=self.expired_status)
allocation.justification = "This allocation is always expired."
allocation.save()
- @skip("We are rethinking how to test functions that rely on env variables.")
- @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_reactivated_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is reactivated."""
allocation = AllocationFactory(status=self.expired_status)
allocation.status = self.active_status
allocation.save()
- @skip("We are rethinking how to test functions that rely on env variables.")
- @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=[])
+ @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list())
def test_new_allocation_is_in_database(self):
"""Test that a new allocation is saved in the database."""
allocation: Allocation = AllocationFactory(status=self.active_status)
allocation.save()
self.assertTrue(Allocation.objects.filter(id=allocation.id).exists())
- @skip("We are rethinking how to test functions that rely on env variables.")
- @override_settings(ALLOCATION_FUNCS_ON_EXPIRE=[])
+ @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list())
def test_multiple_new_allocations_are_in_database(self):
"""Test that multiple new allocations are saved in the database."""
allocations = [AllocationFactory(status=self.active_status) for _ in range(25)]
@@ -377,8 +364,7 @@ def test_no_allocation_attributes_returns_empty_string(self):
allocation: Allocation = AllocationFactory()
self.assertEqual(allocation.get_information, "")
- @skip("We are rethinking how to test functions that rely on env variables.")
- @override_settings(ALLOCATION_ATTRIBUTE_VIEW_LIST=[])
+ @patch('coldfront.core.allocation.models.ALLOCATION_ATTRIBUTE_VIEW_LIST', list())
def test_attribute_type_not_in_view_list_returns_empty_string(self):
"""Test that the get_information method returns an empty string if the attribute type is not in ALLOCATION_ATTRIBUTE_VIEW_LIST."""
allocation: Allocation = AllocationFactory()
From 096895418bcff165702630240b2ed4adade4f6c3 Mon Sep 17 00:00:00 2001
From: Eric Butcher <107886303+Eric-Butcher@users.noreply.github.com>
Date: Fri, 20 Jun 2025 14:34:01 -0400
Subject: [PATCH 12/12] Cleaned up the tests
---
.../core/allocation/tests/test_models.py | 33 +++++++++++--------
1 file changed, 19 insertions(+), 14 deletions(-)
diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py
index db615f8bd..139a23772 100644
--- a/coldfront/core/allocation/tests/test_models.py
+++ b/coldfront/core/allocation/tests/test_models.py
@@ -6,16 +6,15 @@
import datetime
import typing
-from unittest import skip
from unittest.mock import patch
+
from django.core.exceptions import ValidationError
-from django.test import TestCase, override_settings
+from django.test import TestCase
from django.utils import timezone
from django.utils.html import format_html
from django.utils.safestring import SafeString
from coldfront.core.allocation.models import (
- ALLOCATION_FUNCS_ON_EXPIRE,
Allocation,
AllocationAttribute,
AllocationStatusChoice,
@@ -200,6 +199,8 @@ def list_of_different_expire_funcs() -> list[str]:
class AllocationModelSaveMethodTests(TestCase):
+ path_to_allocation_models_funcs_on_expire: str = "coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE"
+
def setUp(self):
count_invocations.invocation_count = 0 # type: ignore
count_invocations_negative.invocation_count = 0 # type: ignore
@@ -212,7 +213,7 @@ def setUpTestData(cls):
cls.other_status: AllocationStatusChoice = AllocationStatusChoiceFactory(name="Other")
cls.project: Project = ProjectFactory()
- @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ @patch(path_to_allocation_models_funcs_on_expire, list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_on_expiration_calls_single_func_in_funcs_on_expire(self):
"""Test that the allocation save method calls the functions specified in ALLOCATION_FUNCS_ON_EXPIRE when it expires."""
allocation = AllocationFactory(status=self.active_status)
@@ -220,7 +221,7 @@ def test_on_expiration_calls_single_func_in_funcs_on_expire(self):
allocation.status = self.expired_status
allocation.save()
- @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(count_invocations))
+ @patch(path_to_allocation_models_funcs_on_expire, list_of_same_expire_funcs(count_invocations))
def test_on_expiration_calls_multiple_funcs_in_funcs_on_expire(self):
"""Test that the allocation save method calls a function multiple times when ALLOCATION_FUNCS_ON_EXPIRE has multiple instances of it."""
allocation = AllocationFactory(status=self.active_status)
@@ -228,7 +229,7 @@ def test_on_expiration_calls_multiple_funcs_in_funcs_on_expire(self):
allocation.save()
self.assertEqual(count_invocations.invocation_count, NUMBER_OF_INVOCATIONS) # type: ignore
- @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_different_expire_funcs())
+ @patch(path_to_allocation_models_funcs_on_expire, list_of_different_expire_funcs())
def test_on_expiration_calls_multiple_different_funcs_in_funcs_on_expire(self):
"""Test that the allocation save method calls all the different functions present in the list ALLOCATION_FUNCS_ON_EXPIRE."""
allocation = AllocationFactory(status=self.active_status)
@@ -250,48 +251,48 @@ def test_on_expiration_calls_multiple_different_funcs_in_funcs_on_expire(self):
self.assertEqual(count_invocations.invocation_count, expected_positive_invocations) # type: ignore
self.assertEqual(count_invocations_negative.invocation_count, expected_negative_invocations) # type: ignore
- @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ @patch(path_to_allocation_models_funcs_on_expire, list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_no_expire_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is not expired."""
allocation = AllocationFactory(status=self.active_status)
allocation.save()
- @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ @patch(path_to_allocation_models_funcs_on_expire, list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_changed_but_always_expired_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is always expired."""
allocation = AllocationFactory(status=self.expired_status)
allocation.justification = "This allocation is always expired."
allocation.save()
- @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ @patch(path_to_allocation_models_funcs_on_expire, list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_changed_but_never_expired_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is never expired."""
allocation = AllocationFactory(status=self.active_status)
allocation.status = self.other_status
allocation.save()
- @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ @patch(path_to_allocation_models_funcs_on_expire, list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_always_expired_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is always expired."""
allocation = AllocationFactory(status=self.expired_status)
allocation.justification = "This allocation is always expired."
allocation.save()
- @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
+ @patch(path_to_allocation_models_funcs_on_expire, list_of_same_expire_funcs(allocation_func_on_expire_exception, 1))
def test_allocation_reactivated_no_funcs_on_expire_called(self):
"""Test that the allocation save method does not call any functions when the allocation is reactivated."""
allocation = AllocationFactory(status=self.expired_status)
allocation.status = self.active_status
allocation.save()
- @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list())
+ @patch(path_to_allocation_models_funcs_on_expire, list())
def test_new_allocation_is_in_database(self):
"""Test that a new allocation is saved in the database."""
allocation: Allocation = AllocationFactory(status=self.active_status)
allocation.save()
self.assertTrue(Allocation.objects.filter(id=allocation.id).exists())
- @patch('coldfront.core.allocation.models.ALLOCATION_FUNCS_ON_EXPIRE', list())
+ @patch(path_to_allocation_models_funcs_on_expire, list())
def test_multiple_new_allocations_are_in_database(self):
"""Test that multiple new allocations are saved in the database."""
allocations = [AllocationFactory(status=self.active_status) for _ in range(25)]
@@ -359,12 +360,16 @@ def test_end_date_in_four_years_returns_days_including_leap_day(self):
class AllocationModelGetInformationTests(TestCase):
+ path_to_allocation_models_allocation_attribute_view_list: str = (
+ "coldfront.core.allocation.models.ALLOCATION_ATTRIBUTE_VIEW_LIST"
+ )
+
def test_no_allocation_attributes_returns_empty_string(self):
"""Test that the get_information method returns an empty string if there are no allocation attributes."""
allocation: Allocation = AllocationFactory()
self.assertEqual(allocation.get_information, "")
- @patch('coldfront.core.allocation.models.ALLOCATION_ATTRIBUTE_VIEW_LIST', list())
+ @patch(path_to_allocation_models_allocation_attribute_view_list, list())
def test_attribute_type_not_in_view_list_returns_empty_string(self):
"""Test that the get_information method returns an empty string if the attribute type is not in ALLOCATION_ATTRIBUTE_VIEW_LIST."""
allocation: Allocation = AllocationFactory()