From 7e43317c4833e57728d76cc6f02fa8f7967a150d Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sat, 27 Jul 2024 15:11:40 +0100 Subject: [PATCH] test(encryption-helper): :white_check_mark: Add unit tests and documentation for helper modules --- encryption_helper/common/strings.py | 33 ++++++- encryption_helper/context/context.py | 5 +- encryption_helper/utils/checks/checks.py | 38 +++++++- encryption_helper/utils/io/read_file.py | 71 ++++++++++++-- encryption_helper/utils/io/write_file.py | 77 ++++++++++++--- pyproject.toml | 1 + tests/__init__.py | 4 + tests/common/__init__.py | 0 tests/common/test_strings.py | 25 +++++ tests/context/__init__.py | 0 tests/context/test_context.py | 67 +++++++++++++ tests/test___init__.py | 25 +++++ tests/test___main__.py | 103 ++++++++++++++++++++ tests/test_main.py | 92 ++++++++++++++++++ tests/utils/__init__.py | 0 tests/utils/checks/__init__.py | 0 tests/utils/checks/test_checks.py | 39 ++++++++ tests/utils/io/__init__.py | 0 tests/utils/io/test_read_file.py | 113 ++++++++++++++++++++++ tests/utils/io/test_write_file.py | 117 +++++++++++++++++++++++ 20 files changed, 785 insertions(+), 25 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/common/__init__.py create mode 100644 tests/common/test_strings.py create mode 100644 tests/context/__init__.py create mode 100644 tests/context/test_context.py create mode 100644 tests/test___init__.py create mode 100644 tests/test___main__.py create mode 100644 tests/test_main.py create mode 100644 tests/utils/__init__.py create mode 100644 tests/utils/checks/__init__.py create mode 100644 tests/utils/checks/test_checks.py create mode 100644 tests/utils/io/__init__.py create mode 100644 tests/utils/io/test_read_file.py create mode 100644 tests/utils/io/test_write_file.py diff --git a/encryption_helper/common/strings.py b/encryption_helper/common/strings.py index e6068d1..2ad3e81 100644 --- a/encryption_helper/common/strings.py +++ b/encryption_helper/common/strings.py @@ -1,3 +1,30 @@ -private_key_suffix = 'private-key.pem' -public_key_suffix = 'public_key.pem' -encrypted_file_suffix = '.bin' +""" +Strings module for the encryption_helper package. + +This module provides a collection of string constants used throughout the +encryption_helper package. These constants include file suffixes for various +key and encrypted file types. + +Attributes: + private_key_suffix (str): The file suffix for private key files. + public_key_suffix (str): The file suffix for public key files. + encrypted_file_suffix (str): The file suffix for encrypted files. +""" + +private_key_suffix = "private-key.pem" +""" +str: The file suffix for private key files. +""" + +public_key_suffix = "public_key.pem" +""" +str: The file suffix for public key files. +""" + +encrypted_file_suffix = ".bin" +""" +str: The file suffix for encrypted files. +""" + +# The module defines string constants for use in the encryption_helper package. +# These constants help standardize file naming conventions across the package. diff --git a/encryption_helper/context/context.py b/encryption_helper/context/context.py index 023afc5..efc6315 100644 --- a/encryption_helper/context/context.py +++ b/encryption_helper/context/context.py @@ -1,8 +1,11 @@ """ -context.py +Context module for the encryption_helper package. This module provides a Context class for managing application-wide settings and logging. It implements the Singleton pattern to ensure only one instance of the context exists. + +Classes: + Context: A singleton class for managing application context and logging. """ import logging diff --git a/encryption_helper/utils/checks/checks.py b/encryption_helper/utils/checks/checks.py index 1b769ed..2a8b8d5 100644 --- a/encryption_helper/utils/checks/checks.py +++ b/encryption_helper/utils/checks/checks.py @@ -1,5 +1,41 @@ +""" +Checks module for the encryption_helper package. + +This module provides utility functions to validate input strings. It includes +functions to check if strings are `None` or empty. + +Functions: + str_none_or_empty: Checks if one or more input strings are `None` or empty. +""" + + def str_none_or_empty(*argv): + """ + Check if one or more input strings are `None` or empty. + + This function takes a variable number of string arguments and checks each + one to determine if it is either `None` or an empty string (including strings + that are only whitespace). + + Args: + *argv: A variable number of string arguments to be checked. + + Returns: + bool: `True` if any of the input strings is `None` or empty; otherwise `False`. + + Examples: + >>> str_none_or_empty('test', 'hello', 'world') + False + >>> str_none_or_empty('test', None, 'world') + True + >>> str_none_or_empty('test', '', 'world') + True + >>> str_none_or_empty('test', ' ', 'world') + True + >>> str_none_or_empty() + False + """ for arg in argv: - if arg is None or len(arg.strip()) == 0: + if arg is None or not isinstance(arg, str) or len(arg.strip()) == 0: return True return False diff --git a/encryption_helper/utils/io/read_file.py b/encryption_helper/utils/io/read_file.py index fc7ee65..fb52010 100644 --- a/encryption_helper/utils/io/read_file.py +++ b/encryption_helper/utils/io/read_file.py @@ -1,24 +1,77 @@ +""" +Read File module for the encryption_helper package. + +This module provides functions to read text files in binary mode. It includes +functions to read files given a directory and file name or an absolute file path. + +Functions: + read_text_in_binary_mode: Reads a file in binary mode given a directory and file name. + read_text_in_binary_mode_abs: Reads a file in binary mode given an absolute file path. +""" + import os -from rsa.utils.checks import checks -from rsa.context import context +from encryption_helper.utils.checks import checks +from encryption_helper.context import context def read_text_in_binary_mode(directory, file_name): - logger = context.Context.getinstance().get_logger() + """ + Read a file in binary mode given a directory and file name. + + This function constructs the absolute file path from the given directory and file name, + checks if the inputs are valid, and reads the file in binary mode. + + Args: + directory (str): The directory containing the file. + file_name (str): The name of the file to be read. + + Returns: + bytes: The contents of the file in binary mode. + + Raises: + Exception: If one or more arguments are empty. + OSError: If there is an error reading the file. + + Examples: + >>> data = read_text_in_binary_mode('path/to/dir', 'file.txt') + >>> print(data) + + """ + logger = context.Context.get_instance().get_logger() if checks.str_none_or_empty(directory, file_name): - logger.exception('One or more arguments are empty') - raise Exception('One or more arguments are empty') + logger.exception("One or more arguments are empty") + raise Exception("One or more arguments are empty") absolute_file_path = os.path.join(directory, file_name) return read_text_in_binary_mode_abs(absolute_file_path) def read_text_in_binary_mode_abs(absolute_file_path): - logger = context.Context.getinstance().get_logger() + """ + Read a file in binary mode given an absolute file path. + + This function checks if the input is valid and reads the file in binary mode. + + Args: + absolute_file_path (str): The absolute path to the file to be read. + + Returns: + bytes: The contents of the file in binary mode. + + Raises: + Exception: If the argument is empty. + OSError: If there is an error reading the file. + + Examples: + >>> data = read_text_in_binary_mode_abs('/path/to/dir/file.txt') + >>> print(data) + + """ + logger = context.Context.get_instance().get_logger() if checks.str_none_or_empty(absolute_file_path): - logger.exception('One or more arguments are empty') - raise Exception('One or more arguments are empty') + logger.exception("One or more arguments are empty") + raise Exception("One or more arguments are empty") - logger.info('Reading from file : {0}'.format(absolute_file_path)) + logger.info("Reading from file : {0}".format(absolute_file_path)) with open(absolute_file_path, "rb") as f: return f.read() diff --git a/encryption_helper/utils/io/write_file.py b/encryption_helper/utils/io/write_file.py index 8cdc80c..77432bd 100644 --- a/encryption_helper/utils/io/write_file.py +++ b/encryption_helper/utils/io/write_file.py @@ -1,27 +1,82 @@ -from rsa.utils.checks import checks -from rsa.context import context +""" +Write File module for the encryption_helper package. + +This module provides functions to write text files in binary mode. It includes +functions to write files given a directory and file name or an absolute file path. + +Functions: + write_text_in_binary_mode: Writes text to a file in binary mode given a directory and file name. + write_text_in_binary_mode_abs: Writes text to a file in binary mode given an absolute file path. +""" + import os +from encryption_helper.utils.checks import checks +from encryption_helper.context import context def write_text_in_binary_mode(directory, file_name, text): - logger = context.Context.getinstance().get_logger() + """ + Write text to a file in binary mode given a directory and file name. + + This function constructs the absolute file path from the given directory and file name, + checks if the inputs are valid, and writes the text to the file in binary mode. + + Args: + directory (str): The directory containing the file. + file_name (str): The name of the file to be written. + text (bytes): The text to be written to the file. + + Returns: + str: The absolute file path of the written file. + + Raises: + Exception: If one or more arguments are empty. + OSError: If there is an error writing to the file. + + Examples: + >>> path = write_text_in_binary_mode('path/to/dir', 'file.txt', b'test data') + >>> print(path) + + """ + logger = context.Context.get_instance().get_logger() if checks.str_none_or_empty(directory, file_name, text): - logger.exception('One or more arguments are empty') - raise Exception('One or more arguments are empty') + logger.exception("One or more arguments are empty") + raise Exception("One or more arguments are empty") absolute_file_path = os.path.join(directory, file_name) return write_text_in_binary_mode_abs(absolute_file_path, text) def write_text_in_binary_mode_abs(absolute_file_path, text): - logger = context.Context.getinstance().get_logger() + """ + Write text to a file in binary mode given an absolute file path. + + This function checks if the inputs are valid and writes the text to the file in binary mode. + + Args: + absolute_file_path (str): The absolute path to the file to be written. + text (bytes): The text to be written to the file. + + Returns: + str: The absolute file path of the written file. + + Raises: + Exception: If one or more arguments are empty. + OSError: If there is an error writing to the file. + + Examples: + >>> path = write_text_in_binary_mode_abs('/path/to/dir/file.txt', b'test data') + >>> print(path) + + """ + logger = context.Context.get_instance().get_logger() if checks.str_none_or_empty(absolute_file_path, text): - logger.exception('One or more arguments are empty') - raise Exception('One or more arguments are empty') + logger.exception("One or more arguments are empty") + raise Exception("One or more arguments are empty") - logger.info('Writing to file : {0}'.format(absolute_file_path)) - with open(absolute_file_path, 'wb') as f: + logger.info("Writing to file : {0}".format(absolute_file_path)) + with open(absolute_file_path, "wb") as f: f.write(text) - logger.info('Writing to file complete') + logger.info("Writing to file complete") return absolute_file_path diff --git a/pyproject.toml b/pyproject.toml index 18c77eb..83b5917 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ pycryptodome = "^3.20.0" [tool.poetry.dev-dependencies] pytest = "^8.3.2" +pytest-mock = "^3.14.0" [tool.poetry.scripts] encryption-helper = "encryption_helper.__main__:main" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..bb163b8 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,4 @@ +# tests/__init__.py + +# This file can be left empty, but it must exist to make Python treat the +# directory as a package, which is necessary for relative imports. diff --git a/tests/common/__init__.py b/tests/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/common/test_strings.py b/tests/common/test_strings.py new file mode 100644 index 0000000..0b5f695 --- /dev/null +++ b/tests/common/test_strings.py @@ -0,0 +1,25 @@ +import unittest +from encryption_helper.common.strings import ( + private_key_suffix, + public_key_suffix, + encrypted_file_suffix, +) + + +class TestStrings(unittest.TestCase): + + def test_private_key_suffix(self): + # Test the private key suffix + self.assertEqual(private_key_suffix, "private-key.pem") + + def test_public_key_suffix(self): + # Test the public key suffix + self.assertEqual(public_key_suffix, "public_key.pem") + + def test_encrypted_file_suffix(self): + # Test the encrypted file suffix + self.assertEqual(encrypted_file_suffix, ".bin") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/context/__init__.py b/tests/context/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/context/test_context.py b/tests/context/test_context.py new file mode 100644 index 0000000..7262b1e --- /dev/null +++ b/tests/context/test_context.py @@ -0,0 +1,67 @@ +import unittest +import logging +from encryption_helper.context.context import Context + + +class TestContext(unittest.TestCase): + + def setUp(self): + # Reset the singleton instance before each test + Context._instance = None + + def test_singleton_instance(self): + # Test that only one instance of Context is created + context1 = Context.get_instance() + context2 = Context.get_instance() + self.assertIs(context1, context2) + + def test_set_name(self): + # Test setting the context name + context = Context.get_instance() + context.set_name("test_app") + self.assertEqual(context.name, "test_app") + + def test_set_log_level_valid(self): + # Test setting valid log levels + context = Context.get_instance() + context.set_log_level("DEBUG") + self.assertEqual(context.log_level, logging.DEBUG) + context.set_log_level("INFO") + self.assertEqual(context.log_level, logging.INFO) + context.set_log_level("WARNING") + self.assertEqual(context.log_level, logging.WARNING) + context.set_log_level("ERROR") + self.assertEqual(context.log_level, logging.ERROR) + context.set_log_level("CRITICAL") + self.assertEqual(context.log_level, logging.CRITICAL) + + def test_set_log_level_invalid(self): + # Test setting an invalid log level + context = Context.get_instance() + with self.assertRaises(ValueError): + context.set_log_level("INVALID") + + def test_init_logging(self): + # Test initializing logging + context = Context.get_instance() + context.set_name("test_app") + context.set_log_level("DEBUG") + context.init_logging() + logger = context.get_logger() + self.assertEqual(logger.name, "test_app") + self.assertEqual(logger.level, logging.DEBUG) + self.assertTrue(logger.hasHandlers()) + + def test_get_logger(self): + # Test getting the logger + context = Context.get_instance() + context.set_name("test_app") + context.set_log_level("DEBUG") + logger = context.get_logger() + self.assertIsInstance(logger, logging.Logger) + self.assertEqual(logger.name, "test_app") + self.assertEqual(logger.level, logging.DEBUG) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test___init__.py b/tests/test___init__.py new file mode 100644 index 0000000..912b438 --- /dev/null +++ b/tests/test___init__.py @@ -0,0 +1,25 @@ +import unittest + +# Import the module to be tested +import encryption_helper + + +class TestInit(unittest.TestCase): + + def test_version(self): + # Ensure the version is set correctly + self.assertEqual(encryption_helper.__version__, "0.0.1") + + def test_generate_rsa_key_import(self): + # Ensure generate_rsa_key is imported correctly + from encryption_helper import generate_rsa_key + + self.assertIsNotNone(generate_rsa_key) + + def test_all_exports(self): + # Ensure __all__ includes the correct exports + self.assertIn("generate_rsa_key", encryption_helper.__all__) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test___main__.py b/tests/test___main__.py new file mode 100644 index 0000000..a39e773 --- /dev/null +++ b/tests/test___main__.py @@ -0,0 +1,103 @@ +import unittest +from unittest.mock import patch, MagicMock +import sys +from io import StringIO + +# Import the main function from the __main__.py module +from encryption_helper.__main__ import main + + +class TestMain(unittest.TestCase): + + @patch("encryption_helper.__main__.generate_rsa_key") + def test_main_success(self, mock_generate_rsa_key): + # Mock the generate_rsa_key function to return dummy keys + mock_generate_rsa_key.return_value = (b"fake_public_key", b"fake_private_key") + + # Capture the output printed to the console + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + main() + output = mock_stdout.getvalue() + + # Check if the expected output is in the console output + self.assertIn("Running encryption_helper...", output) + self.assertIn("RSA key pair generated successfully.", output) + self.assertIn("Public key length: 15 bytes", output) + self.assertIn("Private key length: 16 bytes", output) + self.assertIn("The keys have been saved in the 'keys/pem' directory.", output) + + @patch( + "encryption_helper.__main__.generate_rsa_key", + side_effect=OSError("File write error"), + ) + def test_main_os_error(self, mock_generate_rsa_key): + # Capture the output printed to the console + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + with self.assertRaises(SystemExit) as cm: + main() + output = mock_stdout.getvalue() + self.assertEqual(cm.exception.code, 1) + + # Check if the expected output is in the console output + self.assertIn("Running encryption_helper...", output) + self.assertIn( + "An error occurred while writing the key files: File write error", output + ) + + @patch( + "encryption_helper.__main__.generate_rsa_key", + side_effect=ValueError("Invalid parameters"), + ) + def test_main_value_error(self, mock_generate_rsa_key): + # Capture the output printed to the console + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + with self.assertRaises(SystemExit) as cm: + main() + output = mock_stdout.getvalue() + self.assertEqual(cm.exception.code, 1) + + # Check if the expected output is in the console output + self.assertIn("Running encryption_helper...", output) + self.assertIn( + "An error occurred with the key generation parameters: Invalid parameters", + output, + ) + + @patch( + "encryption_helper.__main__.generate_rsa_key", + side_effect=ImportError("Missing module"), + ) + def test_main_import_error(self, mock_generate_rsa_key): + # Capture the output printed to the console + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + with self.assertRaises(SystemExit) as cm: + main() + output = mock_stdout.getvalue() + self.assertEqual(cm.exception.code, 1) + + # Check if the expected output is in the console output + self.assertIn("Running encryption_helper...", output) + self.assertIn( + "An error occurred while importing required modules: Missing module", output + ) + + @patch( + "encryption_helper.__main__.generate_rsa_key", + side_effect=Exception("Unexpected error"), + ) + def test_main_unexpected_error(self, mock_generate_rsa_key): + # Capture the output printed to the console + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + with self.assertRaises(SystemExit) as cm: + main() + output = mock_stdout.getvalue() + self.assertEqual(cm.exception.code, 1) + + # Check if the expected output is in the console output + self.assertIn("Running encryption_helper...", output) + self.assertIn("An unexpected error occurred: Unexpected error", output) + self.assertIn("Please report this issue to the maintainers.", output) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..a44b500 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,92 @@ +import unittest +from unittest.mock import patch, mock_open, MagicMock +from encryption_helper.main import generate_rsa_key +from pathlib import Path + + +class TestGenerateRSAKey(unittest.TestCase): + + @patch("encryption_helper.main.RSA.generate") + @patch("encryption_helper.main.Path.mkdir") + @patch("encryption_helper.main.open", new_callable=mock_open) + @patch("encryption_helper.main.Context.get_instance") + def test_generate_rsa_key( + self, mock_context, mock_open, mock_mkdir, mock_rsa_generate + ): + # Mock RSA key pair generation + mock_key_pair = MagicMock() + mock_key_pair.export_key.return_value = b"fake_private_key" + mock_key_pair.publickey().export_key.return_value = b"fake_public_key" + mock_rsa_generate.return_value = mock_key_pair + + # Mock logger + mock_logger = MagicMock() + mock_context.return_value.get_logger.return_value = mock_logger + + # Call the function + public_key, private_key = generate_rsa_key() + + # Assertions to check if the keys are generated correctly + self.assertEqual(public_key, b"fake_public_key") + self.assertEqual(private_key, b"fake_private_key") + + # Assertions to check if the directory was created + mock_mkdir.assert_called_once_with(parents=True, exist_ok=True) + + # Assertions to check if files were written correctly + mock_open.assert_any_call(Path("keys/pem/private-key.pem"), "wb") + mock_open.assert_any_call(Path("keys/pem/public-key.pem"), "wb") + mock_open().write.assert_any_call(b"fake_private_key") + mock_open().write.assert_any_call(b"fake_public_key") + + # Assertions to check logging + mock_logger.debug.assert_any_call(f"Public key:\n{public_key.decode()}\n") + mock_logger.debug.assert_any_call(f"Private key:\n{private_key.decode()}\n") + + # Assertions to check printing to console + with patch("builtins.print") as mock_print: + generate_rsa_key() + mock_print.assert_any_call("Public key:") + mock_print.assert_any_call(public_key.decode()) + mock_print.assert_any_call("\nPrivate key:") + mock_print.assert_any_call(private_key.decode()) + + @patch( + "encryption_helper.main.RSA.generate", side_effect=OSError("File write error") + ) + @patch("encryption_helper.main.Context.get_instance") + def test_generate_rsa_key_os_error(self, mock_context, mock_rsa_generate): + # Mock logger + mock_logger = MagicMock() + mock_context.return_value.get_logger.return_value = mock_logger + + # Ensure OSError is raised + with self.assertRaises(OSError): + generate_rsa_key() + + # Assertions to check if error was logged + mock_logger.error.assert_called_once_with( + "Error writing key files: File write error" + ) + + @patch( + "encryption_helper.main.RSA.generate", side_effect=Exception("Unexpected error") + ) + @patch("encryption_helper.main.Context.get_instance") + def test_generate_rsa_key_unexpected_error(self, mock_context, mock_rsa_generate): + # Mock logger + mock_logger = MagicMock() + mock_context.return_value.get_logger.return_value = mock_logger + + # Ensure generic Exception is raised + with self.assertRaises(Exception): + generate_rsa_key() + + # Assertions to check if error was logged + mock_logger.error.assert_called_once_with( + "Unexpected error during key generation: Unexpected error" + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/utils/checks/__init__.py b/tests/utils/checks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/utils/checks/test_checks.py b/tests/utils/checks/test_checks.py new file mode 100644 index 0000000..1d40771 --- /dev/null +++ b/tests/utils/checks/test_checks.py @@ -0,0 +1,39 @@ +import unittest +from encryption_helper.utils.checks import checks + + +class TestChecks(unittest.TestCase): + + def test_str_none_or_empty_all_valid(self): + # Test case where all arguments are valid non-empty strings + result = checks.str_none_or_empty("test", "hello", "world") + self.assertFalse(result) + + def test_str_none_or_empty_with_none(self): + # Test case where one of the arguments is None + result = checks.str_none_or_empty("test", None, "world") + self.assertTrue(result) + + def test_str_none_or_empty_with_empty_string(self): + # Test case where one of the arguments is an empty string + result = checks.str_none_or_empty("test", "", "world") + self.assertTrue(result) + + def test_str_none_or_empty_with_whitespace_string(self): + # Test case where one of the arguments is a whitespace string + result = checks.str_none_or_empty("test", " ", "world") + self.assertTrue(result) + + def test_str_none_or_empty_all_empty(self): + # Test case where all arguments are empty strings + result = checks.str_none_or_empty("", " ", None) + self.assertTrue(result) + + def test_str_none_or_empty_no_args(self): + # Test case where no arguments are provided + result = checks.str_none_or_empty() + self.assertFalse(result) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/utils/io/__init__.py b/tests/utils/io/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/utils/io/test_read_file.py b/tests/utils/io/test_read_file.py new file mode 100644 index 0000000..3ae3467 --- /dev/null +++ b/tests/utils/io/test_read_file.py @@ -0,0 +1,113 @@ +import unittest +from unittest.mock import patch, mock_open, MagicMock +from encryption_helper.utils.io import read_file + + +class TestReadFile(unittest.TestCase): + + @patch( + "encryption_helper.utils.io.read_file.os.path.join", + return_value="/fake/directory/fake_file.txt", + ) + @patch( + "encryption_helper.utils.io.read_file.open", + new_callable=mock_open, + read_data=b"test data", + ) + @patch("encryption_helper.utils.io.read_file.context.Context.get_instance") + @patch("encryption_helper.utils.io.read_file.checks.str_none_or_empty") + def test_read_text_in_binary_mode( + self, mock_str_none_or_empty, mock_get_instance, mock_open, mock_path_join + ): + # Mock the logger + mock_logger = MagicMock() + mock_get_instance.return_value.get_logger.return_value = mock_logger + + # Mock checks + mock_str_none_or_empty.return_value = False + + # Call the function + result = read_file.read_text_in_binary_mode("fake/directory", "fake_file.txt") + + # Assertions + self.assertEqual(result, b"test data") + mock_path_join.assert_called_once_with("fake/directory", "fake_file.txt") + mock_open.assert_called_once_with("/fake/directory/fake_file.txt", "rb") + mock_logger.info.assert_called_once_with( + "Reading from file : /fake/directory/fake_file.txt" + ) + + @patch( + "encryption_helper.utils.io.read_file.os.path.join", + return_value="/fake/directory/fake_file.txt", + ) + @patch( + "encryption_helper.utils.io.read_file.open", + new_callable=mock_open, + read_data=b"test data", + ) + @patch("encryption_helper.utils.io.read_file.context.Context.get_instance") + @patch("encryption_helper.utils.io.read_file.checks.str_none_or_empty") + def test_read_text_in_binary_mode_abs( + self, mock_str_none_or_empty, mock_get_instance, mock_open, mock_path_join + ): + # Mock the logger + mock_logger = MagicMock() + mock_get_instance.return_value.get_logger.return_value = mock_logger + + # Mock checks + mock_str_none_or_empty.return_value = False + + # Call the function + result = read_file.read_text_in_binary_mode_abs("/fake/directory/fake_file.txt") + + # Assertions + self.assertEqual(result, b"test data") + mock_open.assert_called_once_with("/fake/directory/fake_file.txt", "rb") + mock_logger.info.assert_called_once_with( + "Reading from file : /fake/directory/fake_file.txt" + ) + + @patch("encryption_helper.utils.io.read_file.context.Context.get_instance") + @patch( + "encryption_helper.utils.io.read_file.checks.str_none_or_empty", + return_value=True, + ) + def test_read_text_in_binary_mode_empty_args( + self, mock_str_none_or_empty, mock_get_instance + ): + # Mock the logger + mock_logger = MagicMock() + mock_get_instance.return_value.get_logger.return_value = mock_logger + + # Call the function and expect an exception + with self.assertRaises(Exception) as context: + read_file.read_text_in_binary_mode("fake/directory", "fake_file.txt") + + # Assertions + self.assertEqual(str(context.exception), "One or more arguments are empty") + mock_logger.exception.assert_called_once_with("One or more arguments are empty") + + @patch("encryption_helper.utils.io.read_file.context.Context.get_instance") + @patch( + "encryption_helper.utils.io.read_file.checks.str_none_or_empty", + return_value=True, + ) + def test_read_text_in_binary_mode_abs_empty_args( + self, mock_str_none_or_empty, mock_get_instance + ): + # Mock the logger + mock_logger = MagicMock() + mock_get_instance.return_value.get_logger.return_value = mock_logger + + # Call the function and expect an exception + with self.assertRaises(Exception) as context: + read_file.read_text_in_binary_mode_abs("/fake/directory/fake_file.txt") + + # Assertions + self.assertEqual(str(context.exception), "One or more arguments are empty") + mock_logger.exception.assert_called_once_with("One or more arguments are empty") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/utils/io/test_write_file.py b/tests/utils/io/test_write_file.py new file mode 100644 index 0000000..dc848f0 --- /dev/null +++ b/tests/utils/io/test_write_file.py @@ -0,0 +1,117 @@ +import unittest +from unittest.mock import patch, mock_open, MagicMock +from encryption_helper.utils.io import write_file + + +class TestWriteFile(unittest.TestCase): + + @patch( + "encryption_helper.utils.io.write_file.os.path.join", + return_value="/fake/directory/fake_file.txt", + ) + @patch("encryption_helper.utils.io.write_file.open", new_callable=mock_open) + @patch("encryption_helper.utils.io.write_file.context.Context.get_instance") + @patch("encryption_helper.utils.io.write_file.checks.str_none_or_empty") + def test_write_text_in_binary_mode( + self, mock_str_none_or_empty, mock_get_instance, mock_open, mock_path_join + ): + # Mock the logger + mock_logger = MagicMock() + mock_get_instance.return_value.get_logger.return_value = mock_logger + + # Mock checks + mock_str_none_or_empty.return_value = False + + # Call the function + result = write_file.write_text_in_binary_mode( + "fake/directory", "fake_file.txt", b"test data" + ) + + # Assertions + self.assertEqual(result, "/fake/directory/fake_file.txt") + mock_path_join.assert_called_once_with("fake/directory", "fake_file.txt") + mock_open.assert_called_once_with("/fake/directory/fake_file.txt", "wb") + mock_open().write.assert_called_once_with(b"test data") + mock_logger.info.assert_any_call( + "Writing to file : /fake/directory/fake_file.txt" + ) + mock_logger.info.assert_any_call("Writing to file complete") + + @patch( + "encryption_helper.utils.io.write_file.os.path.join", + return_value="/fake/directory/fake_file.txt", + ) + @patch("encryption_helper.utils.io.write_file.open", new_callable=mock_open) + @patch("encryption_helper.utils.io.write_file.context.Context.get_instance") + @patch("encryption_helper.utils.io.write_file.checks.str_none_or_empty") + def test_write_text_in_binary_mode_abs( + self, mock_str_none_or_empty, mock_get_instance, mock_open, mock_path_join + ): + # Mock the logger + mock_logger = MagicMock() + mock_get_instance.return_value.get_logger.return_value = mock_logger + + # Mock checks + mock_str_none_or_empty.return_value = False + + # Call the function + result = write_file.write_text_in_binary_mode_abs( + "/fake/directory/fake_file.txt", b"test data" + ) + + # Assertions + self.assertEqual(result, "/fake/directory/fake_file.txt") + mock_open.assert_called_once_with("/fake/directory/fake_file.txt", "wb") + mock_open().write.assert_called_once_with(b"test data") + mock_logger.info.assert_any_call( + "Writing to file : /fake/directory/fake_file.txt" + ) + mock_logger.info.assert_any_call("Writing to file complete") + + @patch("encryption_helper.utils.io.write_file.context.Context.get_instance") + @patch( + "encryption_helper.utils.io.write_file.checks.str_none_or_empty", + return_value=True, + ) + def test_write_text_in_binary_mode_empty_args( + self, mock_str_none_or_empty, mock_get_instance + ): + # Mock the logger + mock_logger = MagicMock() + mock_get_instance.return_value.get_logger.return_value = mock_logger + + # Call the function and expect an exception + with self.assertRaises(Exception) as context: + write_file.write_text_in_binary_mode( + "fake/directory", "fake_file.txt", b"test data" + ) + + # Assertions + self.assertEqual(str(context.exception), "One or more arguments are empty") + mock_logger.exception.assert_called_once_with("One or more arguments are empty") + + @patch("encryption_helper.utils.io.write_file.context.Context.get_instance") + @patch( + "encryption_helper.utils.io.write_file.checks.str_none_or_empty", + return_value=True, + ) + def test_write_text_in_binary_mode_abs_empty_args( + self, mock_str_none_or_empty, mock_get_instance + ): + # Mock the logger + mock_logger = MagicMock() + mock_get_instance.return_value.get_logger.return_value = mock_logger + + # Call the function and expect an exception + with self.assertRaises(Exception) as context: + write_file.write_text_in_binary_mode_abs( + "/fake/directory/fake_file.txt", b"test data" + ) + + # Assertions + self.assertEqual(str(context.exception), "One or more arguments are empty") + mock_logger.exception.assert_called_once_with("One or more arguments are empty") + + +if __name__ == "__main__": + unittest.main()