From 17e5638f93defea793e60b6f5bb2d3947b9971e1 Mon Sep 17 00:00:00 2001 From: glrs <5999366+glrs@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:24:34 +0100 Subject: [PATCH] Add unittests --- tests/test_common.py | 33 ++++++++++++++++++++++--- tests/test_config_loader.py | 23 ++++++++++-------- tests/test_report_transfer.py | 45 ++++++++++++++++++++++++++++++++++- tests/test_slurm_utils.py | 4 ++-- 4 files changed, 89 insertions(+), 16 deletions(-) diff --git a/tests/test_common.py b/tests/test_common.py index d387fb8..304267d 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -59,13 +59,14 @@ def test_load_realm_class_module_not_found(self, mock_import_module): @patch("importlib.import_module") def test_load_realm_class_attribute_error(self, mock_import_module): - # Module exists but class does not - mock_module = MagicMock() + # Creating a mock module with no attributes allowed + # means any attribute access raises AttributeError. + mock_module = MagicMock(spec=[]) + mock_import_module.return_value = mock_module module_path = "some.module.MissingClass" result = YggdrasilUtilities.load_realm_class(module_path) - self.assertIsNone(result) mock_import_module.assert_called_with("some.module") @@ -93,6 +94,22 @@ def test_load_module_import_error(self, mock_import_module): self.assertIsNone(result) mock_import_module.assert_called_with("nonexistent.module") + @patch("importlib.import_module") + def test_load_realm_class_caching(self, mock_import_module): + # First call: loads and caches the class + mock_module = MagicMock() + mock_import_module.return_value = mock_module + module_path = "some.module.ExistingClass" + first_result = YggdrasilUtilities.load_realm_class(module_path) + self.assertIsNotNone(first_result) + + # Second call: should return the cached class without calling import_module again + second_result = YggdrasilUtilities.load_realm_class(module_path) + self.assertIs(first_result, second_result) + mock_import_module.assert_called_once_with( + "some.module" + ) # Confirm only called once + def test_get_path_file_exists(self): # Create a dummy config file file_name = "config.yaml" @@ -216,6 +233,16 @@ def test_get_path_with_absolute_file_name(self): result = YggdrasilUtilities.get_path(file_name) self.assertIsNone(result) # Should not allow absolute paths + @patch.object(YggdrasilUtilities, "CONFIG_DIR", Path("/some/base/path")) + def test_get_path_resolve_error(self): + # Mock the config_file.resolve() call to raise an Exception + # to trigger the exception block in get_path + with patch( + "lib.core_utils.common.Path.resolve", side_effect=Exception("Resolve error") + ): + result = YggdrasilUtilities.get_path("somefile") + self.assertIsNone(result) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_config_loader.py b/tests/test_config_loader.py index 7ce6cf1..2793b2b 100644 --- a/tests/test_config_loader.py +++ b/tests/test_config_loader.py @@ -95,9 +95,7 @@ def test_config_immutable(self): # Test that the configuration data is immutable self.config_loader._config = types.MappingProxyType(self.mock_config_data) with self.assertRaises(TypeError): - original_dict = self.mock_config_data - with self.assertRaises(TypeError): - original_dict["key1"] = "new_value" + self.config_loader._config["key1"] = "new_value" # type: ignore def test_load_config_type_error(self): # Test handling of TypeError during json.load @@ -132,18 +130,23 @@ def test_config_manager_instance(self): self.assertIsInstance(config_manager, ConfigLoader) def test_configs_loaded(self): - # Test that configs are loaded when the module is imported - with patch("lib.core_utils.config_loader.Ygg.get_path") as mock_get_path, patch( - "builtins.open", mock_open(read_data=self.mock_config_json) + with patch( + "lib.core_utils.config_loader.config_manager.load_config", + return_value=types.MappingProxyType(self.mock_config_data), ): - mock_get_path.return_value = Path("/path/to/config.json") - # Reload the module to trigger the code at the module level import sys - if "config_loader" in sys.modules: - del sys.modules["config_loader"] + if "lib.core_utils.config_loader" in sys.modules: + del sys.modules["lib.core_utils.config_loader"] + from lib.core_utils import config_loader + # Patch the configs directly + config_loader.configs = types.MappingProxyType(self.mock_config_data) + + self.assertEqual( + config_loader.configs, types.MappingProxyType(self.mock_config_data) + ) self.assertEqual( config_loader.configs, types.MappingProxyType(self.mock_config_data) ) diff --git a/tests/test_report_transfer.py b/tests/test_report_transfer.py index ce4f621..c7cdd2d 100644 --- a/tests/test_report_transfer.py +++ b/tests/test_report_transfer.py @@ -131,7 +131,50 @@ def test_transfer_report_general_exception(self, mock_subprocess_run, mock_confi mock_logging.error.assert_any_call( "Unexpected error during report transfer: Unexpected error" ) - mock_logging.error.assert_any_call("RSYNC output: ") + mock_logging.error.assert_any_call( + "RSYNC output: No output available due to early error." + ) + + @patch("lib.module_utils.report_transfer.configs") + @patch("lib.module_utils.report_transfer.subprocess.run") + @patch("lib.module_utils.report_transfer.logging") + def test_transfer_report_general_exception_with_result( + self, mock_logging, mock_subprocess_run, mock_configs + ): + # Set up a valid config + mock_configs.__getitem__.return_value = { + "server": self.server, + "user": self.user, + "destination": self.remote_dir_base, + "ssh_key": self.ssh_key, + } + + # Mock a successful subprocess run + mock_result = MagicMock() + mock_result.returncode = 0 + mock_result.stdout = "Mocked RSYNC output" + mock_subprocess_run.return_value = mock_result + + # Make logging.info raise an exception to simulate an error after success + def info_side_effect(*args, **kwargs): + raise Exception("Logging info error") + + mock_logging.info.side_effect = info_side_effect + + # Call the function + result = transfer_report(self.report_path, self.project_id, self.sample_id) + + # Assert that the result is False because the exception should cause failure + self.assertFalse(result) + + # Check that the unexpected error was logged + # The code logs: "Unexpected error during report transfer: Logging info error" + mock_logging.error.assert_any_call( + "Unexpected error during report transfer: Logging info error" + ) + + # Check that the RSYNC output was logged + mock_logging.error.assert_any_call("RSYNC output: Mocked RSYNC output") @patch("lib.module_utils.report_transfer.configs") @patch("lib.module_utils.report_transfer.subprocess.run") diff --git a/tests/test_slurm_utils.py b/tests/test_slurm_utils.py index bd8ad3b..ac1358b 100644 --- a/tests/test_slurm_utils.py +++ b/tests/test_slurm_utils.py @@ -232,12 +232,12 @@ def test_generate_slurm_script_template_syntax_error(self, mock_file, mock_path) def test_generate_slurm_script_invalid_template_path_type(self): # Test with invalid type for template_fpath with self.assertRaises(TypeError): - generate_slurm_script(self.args_dict, None, self.output_fpath) + generate_slurm_script(self.args_dict, None, self.output_fpath) # type: ignore def test_generate_slurm_script_invalid_output_path_type(self): # Test with invalid type for output_fpath with self.assertRaises(TypeError): - generate_slurm_script(self.args_dict, self.template_fpath, None) + generate_slurm_script(self.args_dict, self.template_fpath, None) # type: ignore @patch("lib.module_utils.slurm_utils.Path") @patch("builtins.open", new_callable=mock_open)