diff --git a/CHANGELOG.md b/CHANGELOG.md index 267ec4f5..a7ca42a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,12 @@ Section Order: +### Changed + +- ESI error handling improved to catch and `HTTPError`, `ESIErrorLimitException` and + `ESIBucketLimitException` which can be thrown by `django-esi` when ESI is + unavailable or rate limited. + ## [5.2.0] - 2026-10-04 > [!IMPORTANT] diff --git a/Makefile b/Makefile index bd3304d6..a9f6760e 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ ParsedConfigFiles := $(ConfigFile) $(wildcard $(ConfigFileOverride)) # Extract all config values from $(ConfigFile) and export them as Makefile variables ifneq ($(wildcard $(ConfigFile)),) -TMPFILE := $(shell mkdir -p $(dir $(ConfigFile)) && mktemp $(dir $(ConfigFile))/make_vars.XXXXXX) +TMPFILE := $(shell mktemp "$${TMPDIR:-/tmp}/make_vars.XXXXXX") $(shell /usr/bin/env python3 .make/parse_config.py --mode=make $(ParsedConfigFiles) > $(TMPFILE)) include $(TMPFILE) $(shell rm -f $(TMPFILE)) diff --git a/sovtimer/providers/esi.py b/sovtimer/providers/esi.py index 11f34014..60f3c017 100644 --- a/sovtimer/providers/esi.py +++ b/sovtimer/providers/esi.py @@ -11,7 +11,13 @@ # Alliance Auth from allianceauth.services.hooks import get_extension_logger -from esi.exceptions import HTTPClientError, HTTPNotModified +from esi.exceptions import ( + ESIBucketLimitException, + ESIErrorLimitException, + HTTPClientError, + HTTPNotModified, + HTTPServerError, +) from esi.openapi_clients import ESIClientProvider, EsiOperation # AA Sovereignty Timer @@ -127,9 +133,13 @@ def result( # pylint: disable=too-many-arguments, too-many-positional-arguments ) esi_result = None - except (HTTPClientError, RequestError) as exc: + except (HTTPClientError, HTTPServerError, RequestError) as exc: logger.error(msg=f"Error while fetching data from ESI: {str(exc)}") + esi_result = None + except (ESIErrorLimitException, ESIBucketLimitException) as exc: + logger.warning(msg=f"ESI rate limit hit, request not sent: {str(exc)}") + esi_result = None # If caller requested the raw response, return a tuple (result, response) diff --git a/sovtimer/tasks.py b/sovtimer/tasks.py index 2b908849..4663265d 100644 --- a/sovtimer/tasks.py +++ b/sovtimer/tasks.py @@ -97,7 +97,7 @@ def update_sov_campaigns(use_etags: bool = True, force_refresh: bool = False) -> use_etags=use_etags, force_refresh=force_refresh ) - # Exit early if no campaigns are returned + # Exit early if no campaigns result is returned if campaigns_from_esi is None: return @@ -195,7 +195,7 @@ def update_sov_structures( # pylint: disable=too-many-locals use_etags=use_etags, force_refresh=force_refresh ) - # Exit early if no structures are returned + # Exit early if no structures result is returned if structures_from_esi is None: return diff --git a/sovtimer/tests/test_models.py b/sovtimer/tests/test_models.py index a498b8bc..387d6e94 100644 --- a/sovtimer/tests/test_models.py +++ b/sovtimer/tests/test_models.py @@ -13,6 +13,8 @@ from sovtimer.models import Alliance, Campaign, SovereigntyStructure, logger from sovtimer.tests import BaseTestCase +MODULE = "sovtimer.models" + class TypeCheckingImportBehavior(BaseTestCase): """ @@ -41,7 +43,7 @@ def test_imports_type_checking_symbols_when_flag_is_true(self): sys.modules["esi.stubs"].SovereigntyCampaignsGetItem = object() with patch.object(typing, "TYPE_CHECKING", True): - models = importlib.reload(importlib.import_module("sovtimer.models")) + models = importlib.reload(importlib.import_module(MODULE)) self.assertIn("Response", models.__dict__) self.assertIn("SovereigntyCampaignsGetItem", models.__dict__) @@ -67,7 +69,7 @@ def test_imports_type_checking_symbols_when_flag_is_true(self): else: sys.modules["esi"] = orig_esi - importlib.reload(importlib.import_module("sovtimer.models")) + importlib.reload(importlib.import_module(MODULE)) def test_does_not_import_type_checking_symbols_when_flag_is_false(self): """ @@ -90,7 +92,7 @@ def test_does_not_import_type_checking_symbols_when_flag_is_false(self): sys.modules["esi.stubs"].SovereigntyCampaignsGetItem = object() with patch.object(typing, "TYPE_CHECKING", False): - models = importlib.reload(importlib.import_module("sovtimer.models")) + models = importlib.reload(importlib.import_module(MODULE)) self.assertNotIn("Response", models.__dict__) self.assertNotIn("SovereigntyCampaignsGetItem", models.__dict__) @@ -111,7 +113,7 @@ def test_does_not_import_type_checking_symbols_when_flag_is_false(self): else: sys.modules["esi"] = orig_esi - importlib.reload(importlib.import_module("sovtimer.models")) + importlib.reload(importlib.import_module(MODULE)) class TestAlliance(BaseTestCase): @@ -119,9 +121,9 @@ class TestAlliance(BaseTestCase): Test cases for the Alliance model """ - @patch("sovtimer.models.ESIHandler.get_alliances_alliance_id") - @patch("sovtimer.models.Alliance.objects.bulk_create") - @patch("sovtimer.models.Alliance.objects.filter") + @patch(MODULE + ".ESIHandler.get_alliances_alliance_id") + @patch(MODULE + ".Alliance.objects.bulk_create") + @patch(MODULE + ".Alliance.objects.filter") def test_returns_all_alliances_when_all_ids_exist( self, mock_filter, mock_bulk_create, mock_get_alliance ): @@ -158,9 +160,9 @@ def test_returns_all_alliances_when_all_ids_exist( mock_bulk_create.assert_not_called() mock_get_alliance.assert_not_called() - @patch("sovtimer.models.ESIHandler.get_alliances_alliance_id") - @patch("sovtimer.models.Alliance.objects.bulk_create") - @patch("sovtimer.models.Alliance.objects.filter") + @patch(MODULE + ".ESIHandler.get_alliances_alliance_id") + @patch(MODULE + ".Alliance.objects.bulk_create") + @patch(MODULE + ".Alliance.objects.filter") def test_creates_new_alliances_when_some_ids_do_not_exist( self, mock_filter, mock_bulk_create, mock_get_alliance ): @@ -209,9 +211,9 @@ def test_creates_new_alliances_when_some_ids_do_not_exist( mock_get_alliance.assert_any_call(alliance_id=3, force_refresh=False) mock_bulk_create.assert_called_once() - @patch("sovtimer.models.ESIHandler.get_alliances_alliance_id") - @patch("sovtimer.models.Alliance.objects.bulk_create") - @patch("sovtimer.models.Alliance.objects.filter") + @patch(MODULE + ".ESIHandler.get_alliances_alliance_id") + @patch(MODULE + ".Alliance.objects.bulk_create") + @patch(MODULE + ".Alliance.objects.filter") def test_skips_creation_when_esi_returns_no_data( self, mock_filter, mock_bulk_create, mock_get_alliance ): @@ -249,7 +251,7 @@ class TestSovereigntyStructure(BaseTestCase): Test cases for the SovereigntyStructure model """ - @patch("sovtimer.models.ESIHandler.get_sovereignty_systems") + @patch(MODULE + ".ESIHandler.get_sovereignty_systems") def test_returns_none_when_esihandler_returns_none(self, mock_get): """ Test that none is returned when ESI returns None @@ -269,7 +271,7 @@ def test_returns_none_when_esihandler_returns_none(self, mock_get): self.assertIsNone(result) mock_get.assert_called_once_with(use_etags=False, force_refresh=False) - @patch("sovtimer.models.ESIHandler.get_sovereignty_systems") + @patch(MODULE + ".ESIHandler.get_sovereignty_systems") def test_returns_parsed_structure_list_when_esihandler_returns_data(self, mock_get): """ Test that data is returned when ESI returns parsed data @@ -328,7 +330,7 @@ def test_returns_parsed_structure_list_when_esihandler_returns_data(self, mock_g self.assertEqual(structure["development"]["activity_defense_multiplier"], 0.5) mock_get.assert_called_once_with(use_etags=True, force_refresh=True) - @patch("sovtimer.models.ESIHandler.get_sovereignty_systems") + @patch(MODULE + ".ESIHandler.get_sovereignty_systems") def test_skips_systems_without_alliance_and_continues_processing_others( self, mock_get ): @@ -395,7 +397,7 @@ def root(self): self.assertEqual(result[0]["solar_system_id"], 4001) mock_get.assert_called_once_with(use_etags=True, force_refresh=False) - @patch("sovtimer.models.ESIHandler.get_sovereignty_systems") + @patch(MODULE + ".ESIHandler.get_sovereignty_systems") def test_skips_system_when_development_raises_attribute_error(self, mock_get): """ Test skips system when system development is raises AttributeError and continues processing others @@ -481,7 +483,7 @@ def strategic_level(self): self.assertEqual(len(result), 0) mock_get.assert_called_once_with(use_etags=True, force_refresh=False) - @patch("sovtimer.models.ESIHandler.get_sovereignty_systems") + @patch(MODULE + ".ESIHandler.get_sovereignty_systems") def test_handles_system_with_no_alliance_field_gracefully(self, mock_get): """ Test handles system with no alliance field gracefully @@ -515,7 +517,7 @@ class TestCampaign(BaseTestCase): Test cases for the Campaign model """ - @patch("sovtimer.models.ESIHandler.get_sovereignty_campaigns") + @patch(MODULE + ".ESIHandler.get_sovereignty_campaigns") def test_returns_campaigns_when_esi_returns_data(self, mock_get_campaigns): """ Test that campaigns are returned when ESI returns data @@ -545,7 +547,7 @@ def test_returns_campaigns_when_esi_returns_data(self, mock_get_campaigns): use_etags=True, force_refresh=False, return_response=False ) - @patch("sovtimer.models.ESIHandler.get_sovereignty_campaigns") + @patch(MODULE + ".ESIHandler.get_sovereignty_campaigns") def test_returns_empty_list_when_esi_returns_no_data(self, mock_get_campaigns): """ Test that an empty list is returned when ESI returns no data for campaigns @@ -565,7 +567,7 @@ def test_returns_empty_list_when_esi_returns_no_data(self, mock_get_campaigns): use_etags=True, force_refresh=False, return_response=False ) - @patch("sovtimer.models.ESIHandler.get_sovereignty_campaigns") + @patch(MODULE + ".ESIHandler.get_sovereignty_campaigns") def test_logs_info_when_esi_returns_no_data(self, mock_get_campaigns): """ Test that an info log is generated when ESI returns no data for campaigns @@ -583,7 +585,7 @@ def test_logs_info_when_esi_returns_no_data(self, mock_get_campaigns): self.assertIn("No campaign changes found, nothing to update.", log.output[0]) - @patch("sovtimer.models.ESIHandler.get_sovereignty_campaigns") + @patch(MODULE + ".ESIHandler.get_sovereignty_campaigns") def test_logs_debug_when_esi_returns_data(self, mock_get_campaigns): """ Test that a debug log is generated when ESI returns data for campaigns diff --git a/sovtimer/tests/test_providers.py b/sovtimer/tests/test_providers.py index 57dbdfb0..ea89bacb 100644 --- a/sovtimer/tests/test_providers.py +++ b/sovtimer/tests/test_providers.py @@ -22,6 +22,8 @@ from sovtimer.providers.esi import ESIHandler from sovtimer.tests import BaseTestCase +MODULE = "sovtimer.providers" + class TestTypingTypeCheckingIfCondition(BaseTestCase): """ @@ -50,7 +52,7 @@ def test_when_typing_TYPE_CHECKING_true_then_stub_types_are_imported_into_module sys.modules["esi.stubs"] = fake_stubs with patch.object(typing, "TYPE_CHECKING", True): - providers = importlib.import_module("sovtimer.providers.esi") + providers = importlib.import_module(MODULE + ".esi") importlib.reload(providers) self.assertTrue(hasattr(providers, "AllianceDetail")) @@ -60,7 +62,7 @@ def test_when_typing_TYPE_CHECKING_true_then_stub_types_are_imported_into_module sys.modules.update(original_sys_modules) try: - importlib.reload(importlib.import_module("sovtimer.providers.esi")) + importlib.reload(importlib.import_module(MODULE + ".esi")) except Exception: pass @@ -197,6 +199,85 @@ def test_returns_none_on_client_or_request_error(self): result = ESIHandler.result(operation=operation) self.assertIsNone(result) + def test_returns_none_on_esi_error_limit_exception(self): + """ + Test returns `None` when an ESI error limit exception is raised. + + :return: + :rtype: + """ + + fake_error_limit_exception = type("FakeErrorLimitException", (Exception,), {}) + operation = MagicMock() + operation.operation = MagicMock(operationId="GetSomething") + operation.result.side_effect = fake_error_limit_exception("error limit hit") + + with ( + patch( + MODULE + ".esi.ESIErrorLimitException", + fake_error_limit_exception, + ), + patch(MODULE + ".esi.logger.warning") as mock_warning, + ): + result = ESIHandler.result(operation=operation) + + self.assertIsNone(result) + mock_warning.assert_called_once() + self.assertIn( + "ESI rate limit hit, request not sent", + mock_warning.call_args.kwargs["msg"], + ) + + def test_returns_none_on_esi_bucket_limit_exception(self): + """ + Test returns `None` when an ESI bucket limit exception is raised. + + :return: + :rtype: + """ + + fake_bucket_limit_exception = type("FakeBucketLimitException", (Exception,), {}) + operation = MagicMock() + operation.operation = MagicMock(operationId="GetSomething") + operation.result.side_effect = fake_bucket_limit_exception("bucket limit hit") + + with ( + patch( + MODULE + ".esi.ESIBucketLimitException", + fake_bucket_limit_exception, + ), + patch(MODULE + ".esi.logger.warning") as mock_warning, + ): + result = ESIHandler.result(operation=operation) + + self.assertIsNone(result) + mock_warning.assert_called_once() + self.assertIn( + "ESI rate limit hit, request not sent", + mock_warning.call_args.kwargs["msg"], + ) + + def test_returns_none_tuple_on_esi_bucket_limit_when_return_response_true(self): + """ + Test returns `(None, None)` when a bucket limit exception is raised and `return_response` is true. + + :return: + :rtype: + """ + + fake_bucket_limit_exception = type("FakeBucketLimitException", (Exception,), {}) + operation = MagicMock() + operation.operation = MagicMock(operationId="GetSomething") + operation.result.side_effect = fake_bucket_limit_exception("bucket limit hit") + + with patch( + MODULE + ".esi.ESIBucketLimitException", + fake_bucket_limit_exception, + ): + result = ESIHandler.result(operation=operation, return_response=True) + + self.assertEqual(result, (None, None)) + def test_passes_extra_kwargs_to_operation_result(self): """ Test passes extra kwargs to operation result. @@ -242,8 +323,8 @@ def test_returns_campaigns_when_esi_returns_data(self): """ with ( - patch("sovtimer.providers.esi.esi", new=MagicMock()), - patch("sovtimer.providers.esi.ESIHandler.result") as mock_result, + patch(MODULE + ".esi.esi", new=MagicMock()), + patch(MODULE + ".esi.ESIHandler.result") as mock_result, ): mock_result.return_value = [{"campaign_id": 1}, {"campaign_id": 2}] @@ -266,9 +347,9 @@ def test_raises_exception_when_result_raises(self): """ with ( - patch("sovtimer.providers.esi.esi", new=MagicMock()), + patch(MODULE + ".esi.esi", new=MagicMock()), patch( - "sovtimer.providers.esi.ESIHandler.result", + MODULE + ".esi.ESIHandler.result", side_effect=Exception("Error"), ) as mock_result, ): @@ -291,9 +372,9 @@ def test_logs_debug_message_when_fetching_campaigns(self): """ with ( - patch("sovtimer.providers.esi.esi", new=MagicMock()), - patch("sovtimer.providers.esi.logger.debug") as mock_logger, - patch("sovtimer.providers.esi.ESIHandler.result") as mock_result, + patch(MODULE + ".esi.esi", new=MagicMock()), + patch(MODULE + ".esi.logger.debug") as mock_logger, + patch(MODULE + ".esi.ESIHandler.result") as mock_result, ): mock_result.return_value = [{"campaign_id": 1}] @@ -307,8 +388,8 @@ class TestESIHandlerGetAlliancesAllianceId(BaseTestCase): Test the ESIHandler.get_alliances_alliance_id method. """ - @patch("sovtimer.providers.esi.esi", new=MagicMock()) - @patch("sovtimer.providers.esi.ESIHandler.result") + @patch(MODULE + ".esi.esi", new=MagicMock()) + @patch(MODULE + ".esi.ESIHandler.result") def test_returns_alliance_data_when_operation_succeeds(self, mock_result): """ Test that the method returns alliance data when the ESI operation succeeds. @@ -330,8 +411,8 @@ def test_returns_alliance_data_when_operation_succeeds(self, mock_result): self.assertIn("operation", called_kwargs) self.assertFalse(called_kwargs.get("force_refresh")) - @patch("sovtimer.providers.esi.esi", new=MagicMock()) - @patch("sovtimer.providers.esi.ESIHandler.result") + @patch(MODULE + ".esi.esi", new=MagicMock()) + @patch(MODULE + ".esi.ESIHandler.result") def test_passes_force_refresh_to_result_operation(self, mock_result): """ Test that the force_refresh parameter is passed correctly to the ESIHandler.result method. @@ -370,8 +451,8 @@ def test_returns_systems_when_esi_returns_data(self): """ with ( - patch("sovtimer.providers.esi.esi", new=MagicMock()), - patch("sovtimer.providers.esi.ESIHandler.result") as mock_result, + patch(MODULE + ".esi.esi", new=MagicMock()), + patch(MODULE + ".esi.ESIHandler.result") as mock_result, ): mock_result.return_value = [{"system_id": 1}, {"system_id": 2}] @@ -394,9 +475,9 @@ def test_raises_exception_when_result_raises(self): """ with ( - patch("sovtimer.providers.esi.esi", new=MagicMock()), + patch(MODULE + ".esi.esi", new=MagicMock()), patch( - "sovtimer.providers.esi.ESIHandler.result", + MODULE + ".esi.ESIHandler.result", side_effect=Exception("Error"), ) as mock_result, ): @@ -417,9 +498,9 @@ def test_logs_debug_message_when_fetching_systems(self): """ with ( - patch("sovtimer.providers.esi.esi", new=MagicMock()), - patch("sovtimer.providers.esi.logger.debug") as mock_logger, - patch("sovtimer.providers.esi.ESIHandler.result") as mock_result, + patch(MODULE + ".esi.esi", new=MagicMock()), + patch(MODULE + ".esi.logger.debug") as mock_logger, + patch(MODULE + ".esi.ESIHandler.result") as mock_result, ): mock_result.return_value = [{"system_id": 1}] diff --git a/sovtimer/tests/test_tasks.py b/sovtimer/tests/test_tasks.py index 80374f22..b1ac65a0 100644 --- a/sovtimer/tests/test_tasks.py +++ b/sovtimer/tests/test_tasks.py @@ -14,19 +14,21 @@ ) from sovtimer.tests import BaseTestCase +MODULE = "sovtimer.tasks" + class TestUpdateSovStructures(BaseTestCase): """ Test the update_sov_structures task """ - @patch("sovtimer.tasks.cache.get") - @patch("sovtimer.tasks.cache.set") - @patch("sovtimer.tasks.SovereigntyStructure.objects.bulk_create") - @patch("sovtimer.tasks.SovereigntyStructure.objects.exclude") - @patch("sovtimer.tasks.SolarSystem.objects.filter") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") - @patch("sovtimer.tasks.SovereigntyStructure.get_sov_structures_from_esi") + @patch(MODULE + ".cache.get") + @patch(MODULE + ".cache.set") + @patch(MODULE + ".SovereigntyStructure.objects.bulk_create") + @patch(MODULE + ".SovereigntyStructure.objects.exclude") + @patch(MODULE + ".SolarSystem.objects.filter") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".SovereigntyStructure.get_sov_structures_from_esi") def test_returns_early_and_makes_no_db_calls_when_esi_returns_none( self, mock_get_sov_structures, @@ -75,8 +77,8 @@ def test_returns_early_and_makes_no_db_calls_when_esi_returns_none( mock_bulk_create.assert_not_called() mock_exclude.assert_not_called() - @patch("sovtimer.tasks.SovereigntyStructure.get_sov_structures_from_esi") - @patch("sovtimer.tasks.cache.get") + @patch(MODULE + ".SovereigntyStructure.get_sov_structures_from_esi") + @patch(MODULE + ".cache.get") def test_skips_update_when_cache_not_expired( self, mock_cache_get, mock_get_sov_structures ): @@ -87,13 +89,13 @@ def test_skips_update_when_cache_not_expired( mock_cache_get.assert_called_once_with(Constants.TASK_STRUCTURE_CACHE_KEY) mock_get_sov_structures.assert_not_called() - @patch("sovtimer.tasks.cache.get") - @patch("sovtimer.tasks.cache.set") - @patch("sovtimer.tasks.SovereigntyStructure.objects.bulk_create") - @patch("sovtimer.tasks.SovereigntyStructure.objects.exclude") - @patch("sovtimer.tasks.SolarSystem.objects.filter") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") - @patch("sovtimer.tasks.SovereigntyStructure.get_sov_structures_from_esi") + @patch(MODULE + ".cache.get") + @patch(MODULE + ".cache.set") + @patch(MODULE + ".SovereigntyStructure.objects.bulk_create") + @patch(MODULE + ".SovereigntyStructure.objects.exclude") + @patch(MODULE + ".SolarSystem.objects.filter") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".SovereigntyStructure.get_sov_structures_from_esi") def test_processes_structure_with_default_vulnerability( self, mock_get_sov_structures, @@ -164,13 +166,13 @@ def test_processes_structure_with_default_vulnerability( self.assertEqual(created[0].structure_id, 1001) self.assertEqual(created[0].vulnerability_occupancy_level, 1) - @patch("sovtimer.tasks.cache.get") - @patch("sovtimer.tasks.cache.set") - @patch("sovtimer.tasks.SovereigntyStructure.objects.bulk_create") - @patch("sovtimer.tasks.SovereigntyStructure.objects.exclude") - @patch("sovtimer.tasks.SolarSystem.objects.filter") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") - @patch("sovtimer.tasks.SovereigntyStructure.get_sov_structures_from_esi") + @patch(MODULE + ".cache.get") + @patch(MODULE + ".cache.set") + @patch(MODULE + ".SovereigntyStructure.objects.bulk_create") + @patch(MODULE + ".SovereigntyStructure.objects.exclude") + @patch(MODULE + ".SolarSystem.objects.filter") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".SovereigntyStructure.get_sov_structures_from_esi") def test_skips_structure_if_hub_id_missing( self, mock_get_sov_structures, @@ -235,13 +237,13 @@ def test_skips_structure_if_hub_id_missing( self.assertEqual(len(created), 1) self.assertEqual(created[0].structure_id, 1002) - @patch("sovtimer.tasks.cache.get") - @patch("sovtimer.tasks.cache.set") - @patch("sovtimer.tasks.SovereigntyStructure.objects.bulk_create") - @patch("sovtimer.tasks.SovereigntyStructure.objects.exclude") - @patch("sovtimer.tasks.SolarSystem.objects.filter") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") - @patch("sovtimer.tasks.SovereigntyStructure.get_sov_structures_from_esi") + @patch(MODULE + ".cache.get") + @patch(MODULE + ".cache.set") + @patch(MODULE + ".SovereigntyStructure.objects.bulk_create") + @patch(MODULE + ".SovereigntyStructure.objects.exclude") + @patch(MODULE + ".SolarSystem.objects.filter") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".SovereigntyStructure.get_sov_structures_from_esi") def test_skips_duplicate_structure_ids( self, mock_get_sov_structures, @@ -310,13 +312,13 @@ def test_skips_duplicate_structure_ids( self.assertEqual(len(created), 1) self.assertEqual(created[0].structure_id, 1003) - @patch("sovtimer.tasks.cache.get") - @patch("sovtimer.tasks.cache.set") - @patch("sovtimer.tasks.SovereigntyStructure.objects.bulk_create") - @patch("sovtimer.tasks.SovereigntyStructure.objects.exclude") - @patch("sovtimer.tasks.SolarSystem.objects.filter") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") - @patch("sovtimer.tasks.SovereigntyStructure.get_sov_structures_from_esi") + @patch(MODULE + ".cache.get") + @patch(MODULE + ".cache.set") + @patch(MODULE + ".SovereigntyStructure.objects.bulk_create") + @patch(MODULE + ".SovereigntyStructure.objects.exclude") + @patch(MODULE + ".SolarSystem.objects.filter") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".SovereigntyStructure.get_sov_structures_from_esi") def test_development_getitem_raises_attribute_error_defaults_to_one( self, mock_get_sov_structures, @@ -378,13 +380,13 @@ def __getitem__(self, key): self.assertEqual(len(created), 1) self.assertEqual(created[0].vulnerability_occupancy_level, 1) - @patch("sovtimer.tasks.cache.get") - @patch("sovtimer.tasks.cache.set") - @patch("sovtimer.tasks.SovereigntyStructure.objects.bulk_create") - @patch("sovtimer.tasks.SovereigntyStructure.objects.exclude") - @patch("sovtimer.tasks.SolarSystem.objects.filter") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") - @patch("sovtimer.tasks.SovereigntyStructure.get_sov_structures_from_esi") + @patch(MODULE + ".cache.get") + @patch(MODULE + ".cache.set") + @patch(MODULE + ".SovereigntyStructure.objects.bulk_create") + @patch(MODULE + ".SovereigntyStructure.objects.exclude") + @patch(MODULE + ".SolarSystem.objects.filter") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".SovereigntyStructure.get_sov_structures_from_esi") def test_development_multiplier_value_is_used_when_present( self, mock_get_sov_structures, @@ -442,13 +444,13 @@ def test_development_multiplier_value_is_used_when_present( self.assertEqual(len(created), 1) self.assertEqual(created[0].vulnerability_occupancy_level, 0.33) - @patch("sovtimer.tasks.cache.get") - @patch("sovtimer.tasks.cache.set") - @patch("sovtimer.tasks.SovereigntyStructure.objects.bulk_create") - @patch("sovtimer.tasks.SovereigntyStructure.objects.exclude") - @patch("sovtimer.tasks.SolarSystem.objects.filter") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") - @patch("sovtimer.tasks.SovereigntyStructure.get_sov_structures_from_esi") + @patch(MODULE + ".cache.get") + @patch(MODULE + ".cache.set") + @patch(MODULE + ".SovereigntyStructure.objects.bulk_create") + @patch(MODULE + ".SovereigntyStructure.objects.exclude") + @patch(MODULE + ".SolarSystem.objects.filter") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".SovereigntyStructure.get_sov_structures_from_esi") def test_sovereignty_hub_not_dict_results_in_no_vulnerability_window( self, mock_get_sov_structures, @@ -520,10 +522,10 @@ class TestUpdateSovCampaigns(BaseTestCase): Test the update_sov_campaigns task """ - @patch("sovtimer.tasks.Campaign.get_sov_campaigns_from_esi") - @patch("sovtimer.tasks.Campaign.objects.bulk_create") - @patch("sovtimer.tasks.Campaign.objects.all") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".Campaign.get_sov_campaigns_from_esi") + @patch(MODULE + ".Campaign.objects.bulk_create") + @patch(MODULE + ".Campaign.objects.all") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") def test_progress_previous_uses_previous_progress_when_scores_equal( self, mock_bulk_get_or_create, @@ -576,10 +578,10 @@ def test_progress_previous_uses_previous_progress_when_scores_equal( self.assertEqual(len(created), 1) self.assertEqual(created[0].progress_previous, 0.55) - @patch("sovtimer.tasks.Campaign.get_sov_campaigns_from_esi") - @patch("sovtimer.tasks.Campaign.objects.bulk_create") - @patch("sovtimer.tasks.Campaign.objects.all") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".Campaign.get_sov_campaigns_from_esi") + @patch(MODULE + ".Campaign.objects.bulk_create") + @patch(MODULE + ".Campaign.objects.all") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") def test_progress_previous_uses_previous_defender_score_when_scores_differ( self, mock_bulk_get_or_create, @@ -633,10 +635,10 @@ def test_progress_previous_uses_previous_defender_score_when_scores_differ( # progress_previous should equal the existing defender_score (0.75) self.assertEqual(created[0].progress_previous, 0.75) - @patch("sovtimer.tasks.Campaign.get_sov_campaigns_from_esi") - @patch("sovtimer.tasks.Campaign.objects.bulk_create") - @patch("sovtimer.tasks.Campaign.objects.all") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".Campaign.get_sov_campaigns_from_esi") + @patch(MODULE + ".Campaign.objects.bulk_create") + @patch(MODULE + ".Campaign.objects.all") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") def test_progress_previous_defaults_to_campaign_defender_score_when_no_previous( self, mock_bulk_get_or_create, @@ -686,10 +688,10 @@ def test_progress_previous_defaults_to_campaign_defender_score_when_no_previous( self.assertEqual(len(created), 1) self.assertEqual(created[0].progress_previous, 0.65) - @patch("sovtimer.tasks.Campaign.get_sov_campaigns_from_esi") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") - @patch("sovtimer.tasks.Campaign.objects.bulk_create") - @patch("sovtimer.tasks.Campaign.objects.all") + @patch(MODULE + ".Campaign.get_sov_campaigns_from_esi") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".Campaign.objects.bulk_create") + @patch(MODULE + ".Campaign.objects.all") def test_returns_early_and_makes_no_db_calls_when_esi_returns_none( self, mock_campaign_all, @@ -725,10 +727,10 @@ def test_returns_early_and_makes_no_db_calls_when_esi_returns_none( mock_campaign_all.assert_not_called() mock_bulk_create.assert_not_called() - @patch("sovtimer.tasks.Campaign.get_sov_campaigns_from_esi") - @patch("sovtimer.tasks.Alliance.bulk_get_or_create_from_esi") - @patch("sovtimer.tasks.Campaign.objects.bulk_create") - @patch("sovtimer.tasks.Campaign.objects.all") + @patch(MODULE + ".Campaign.get_sov_campaigns_from_esi") + @patch(MODULE + ".Alliance.bulk_get_or_create_from_esi") + @patch(MODULE + ".Campaign.objects.bulk_create") + @patch(MODULE + ".Campaign.objects.all") def test_handles_empty_list_from_esi_and_performs_empty_bulk_create( self, mock_campaign_all, @@ -781,9 +783,9 @@ class TestRunSovCampaignUpdates(BaseTestCase): Test the run_sov_campaign_updates task """ - @patch("sovtimer.tasks.chain") - @patch("sovtimer.tasks.update_sov_structures.si") - @patch("sovtimer.tasks.update_sov_campaigns.si") + @patch(MODULE + ".chain") + @patch(MODULE + ".update_sov_structures.si") + @patch(MODULE + ".update_sov_campaigns.si") def test_runs_both_tasks_in_sequence( self, mock_update_campaigns, mock_update_structures, mock_chain ): @@ -812,9 +814,9 @@ def test_runs_both_tasks_in_sequence( mock_update_campaigns.return_value.set.assert_called_once_with(priority=6) mock_chain.return_value.apply_async.assert_called_once() - @patch("sovtimer.tasks.chain") - @patch("sovtimer.tasks.update_sov_structures.si") - @patch("sovtimer.tasks.update_sov_campaigns.si") + @patch(MODULE + ".chain") + @patch(MODULE + ".update_sov_structures.si") + @patch(MODULE + ".update_sov_campaigns.si") def test_handles_no_priority_set( self, mock_update_campaigns, mock_update_structures, mock_chain ): @@ -837,10 +839,10 @@ def test_handles_no_priority_set( mock_update_campaigns.return_value.set.assert_called_once_with(priority=6) mock_chain.return_value.apply_async.assert_called_once() - @patch("sovtimer.tasks.logger.info") - @patch("sovtimer.tasks.chain") - @patch("sovtimer.tasks.update_sov_structures.si") - @patch("sovtimer.tasks.update_sov_campaigns.si") + @patch(MODULE + ".logger.info") + @patch(MODULE + ".chain") + @patch(MODULE + ".update_sov_structures.si") + @patch(MODULE + ".update_sov_campaigns.si") def test_logs_start_of_update_process( self, mock_update_campaigns, diff --git a/sovtimer/tests/test_views.py b/sovtimer/tests/test_views.py index 5fee1666..033ef10a 100644 --- a/sovtimer/tests/test_views.py +++ b/sovtimer/tests/test_views.py @@ -17,6 +17,8 @@ from sovtimer.tests import BaseTestCase from sovtimer.views import _fmt_float_to_percentage +MODULE = "sovtimer.views" + class TestHelperFmtFloatToPercentage(BaseTestCase): """ @@ -44,7 +46,7 @@ class TestDashboard(BaseTestCase): Test the dashboard view """ - @patch("sovtimer.views.logger") + @patch(MODULE + ".logger") def test_dashboard_renders_correct_template(self, mock_logger): """ Test that the dashboard view renders the correct template @@ -66,7 +68,7 @@ def test_dashboard_renders_correct_template(self, mock_logger): self.assertTemplateUsed(response, "sovtimer/dashboard.html") mock_logger.info.assert_called_once_with(msg="Module called by testuser") - @patch("sovtimer.views.logger") + @patch(MODULE + ".logger") def test_dashboard_logs_anonymous_user(self, mock_logger): """ Test that the dashboard view logs AnonymousUser @@ -88,7 +90,7 @@ class TestDashboardData(BaseTestCase): Test the dashboard_data view """ - @patch("sovtimer.views.Campaign.objects.select_related") + @patch(MODULE + ".Campaign.objects.select_related") def test_returns_empty_response_when_no_campaign_system_ids( self, mock_campaign_select_related ): @@ -112,9 +114,9 @@ def test_returns_empty_response_when_no_campaign_system_ids( self.assertEqual(response.status_code, HTTPStatus.OK) self.assertEqual(response.json(), []) - @patch("sovtimer.views.Campaign.Type") - @patch("sovtimer.views.Campaign.objects.select_related") - @patch("sovtimer.views.SovereigntyStructure.objects.filter") + @patch(MODULE + ".Campaign.Type") + @patch(MODULE + ".Campaign.objects.select_related") + @patch(MODULE + ".SovereigntyStructure.objects.filter") def test_dashboard_data_includes_active_campaigns( self, mock_structure_filter, mock_campaign_select_related, mock_campaign_type ): @@ -179,9 +181,9 @@ def test_dashboard_data_includes_active_campaigns( self.assertEqual(response.json()[0]["defender"]["sort"], "Alliance A") self.assertEqual(response.json()[0]["campaign_status"], "active") - @patch("sovtimer.views.Campaign.Type") - @patch("sovtimer.views.Campaign.objects.select_related") - @patch("sovtimer.views.SovereigntyStructure.objects.filter") + @patch(MODULE + ".Campaign.Type") + @patch(MODULE + ".Campaign.objects.select_related") + @patch(MODULE + ".SovereigntyStructure.objects.filter") def test_dashboard_data_handles_missing_vulnerability_levels( self, mock_structure_filter, mock_campaign_select_related, mock_campaign_type ): @@ -240,10 +242,10 @@ def test_dashboard_data_handles_missing_vulnerability_levels( self.assertEqual(len(response.json()), 1) self.assertEqual(response.json()[0]["adm"], "1.0") - @patch("sovtimer.views.Campaign.Type") - @patch("sovtimer.views.timezone.now") - @patch("sovtimer.views.Campaign.objects.select_related") - @patch("sovtimer.views.SovereigntyStructure.objects.filter") + @patch(MODULE + ".Campaign.Type") + @patch(MODULE + ".timezone.now") + @patch(MODULE + ".Campaign.objects.select_related") + @patch(MODULE + ".SovereigntyStructure.objects.filter") def test_campaign_status_is_upcoming_when_within_timeframe( self, mock_structure_filter, @@ -314,9 +316,9 @@ def test_campaign_status_is_upcoming_when_within_timeframe( self.assertEqual(len(response.json()), 1) self.assertEqual(response.json()[0]["campaign_status"], "upcoming") - @patch("sovtimer.views.Campaign.Type") - @patch("sovtimer.views.Campaign.objects.select_related") - @patch("sovtimer.views.SovereigntyStructure.objects.filter") + @patch(MODULE + ".Campaign.Type") + @patch(MODULE + ".Campaign.objects.select_related") + @patch(MODULE + ".SovereigntyStructure.objects.filter") def test_sets_attackers_progress_when_previous_progress_is_higher( self, mock_structure_filter, mock_campaign_select_related, mock_campaign_type ):