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()