Skip to content

Commit

Permalink
Merge pull request #7 from sebastienrousseauhsbc/main
Browse files Browse the repository at this point in the history
Add unit tests and documentation for helper modules
  • Loading branch information
sebastienrousseauhsbc authored Jul 27, 2024
2 parents 196efac + 7e43317 commit e9cf9b8
Show file tree
Hide file tree
Showing 20 changed files with 785 additions and 25 deletions.
33 changes: 30 additions & 3 deletions encryption_helper/common/strings.py
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 4 additions & 1 deletion encryption_helper/context/context.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
38 changes: 37 additions & 1 deletion encryption_helper/utils/checks/checks.py
Original file line number Diff line number Diff line change
@@ -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
71 changes: 62 additions & 9 deletions encryption_helper/utils/io/read_file.py
Original file line number Diff line number Diff line change
@@ -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()
77 changes: 66 additions & 11 deletions encryption_helper/utils/io/write_file.py
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -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.
Empty file added tests/common/__init__.py
Empty file.
25 changes: 25 additions & 0 deletions tests/common/test_strings.py
Original file line number Diff line number Diff line change
@@ -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()
Empty file added tests/context/__init__.py
Empty file.
67 changes: 67 additions & 0 deletions tests/context/test_context.py
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit e9cf9b8

Please sign in to comment.