Greg DiCristofaro aa22f3d9af testing
2020-07-22 10:44:53 -04:00

194 lines
6.5 KiB
Python

import unittest
from typing import Iterator, List, Union, Dict
from propsutil import get_entry_dict
from enum import Enum
class ChangeType(Enum):
"""Describes the nature of a change in the properties file."""
ADDITION = 'ADDITION'
DELETION = 'DELETION'
CHANGE = 'CHANGE'
class ItemChange:
rel_path: str
key: str
prev_val: Union[str, None]
cur_val: Union[str, None]
type: ChangeType
def __init__(self, rel_path: str, key: str, prev_val: str, cur_val: str):
"""Describes the change that occurred for a particular key of a properties file.
Args:
rel_path (str): The relative path of the properties file.
key (str): The key in the properties file.
prev_val (str): The previous value for the key.
cur_val (str): The current value for the key.
"""
self.rel_path = rel_path
self.key = key
self.prev_val = prev_val
self.cur_val = cur_val
if ItemChange.has_str_content(cur_val) and not ItemChange.has_str_content(prev_val):
self.type = ChangeType.ADDITION
elif not ItemChange.has_str_content(cur_val) and ItemChange.has_str_content(prev_val):
self.type = ChangeType.DELETION
else:
self.type = ChangeType.CHANGE
@staticmethod
def has_str_content(content: str):
"""Determines whether or not the content is empty or None.
Args:
content (str): The text.
Returns:
bool: Whether or not it has content.
"""
return content is not None and len(content.strip()) > 0
@staticmethod
def get_headers() -> List[str]:
"""Returns the csv headers to insert when serializing a list of ItemChange objects to csv.
Returns:
List[str]: The column headers
"""
return ['Relative Path', 'Key', 'Change Type', 'Previous Value', 'Current Value']
def get_row(self) -> List[str]:
"""Returns the list of values to be entered as a row in csv serialization.
Returns:
List[str]: The list of values to be entered as a row in csv serialization.
"""
return [
self.rel_path,
self.key,
self.type,
self.prev_val,
self.cur_val]
def get_item_change(rel_path: str, key: str, prev_val: str, cur_val: str) -> Union[ItemChange, None]:
"""Returns an ItemChange object if the previous value is not equal to the current value.
Args:
rel_path (str): The relative path for the properties file.
key (str): The key within the properties file for this potential change.
prev_val (str): The previous value.
cur_val (str): The current value.
Returns:
ItemChange: The ItemChange object or None if values are the same.
"""
if prev_val == cur_val:
return None
else:
return ItemChange(rel_path, key, prev_val, cur_val)
def get_changed(rel_path: str, a_str: str, b_str: str) -> Iterator[ItemChange]:
"""Given the relative path of the properties file that has been provided,
determines the property items that have changed between the two property
file strings.
Args:
rel_path (str): The relative path for the properties file.
a_str (str): The string representing the original state of the file.
b_str (str): The string representing the current state of the file.
Returns:
List[ItemChange]: The changes determined.
"""
print('Retrieving changes for {0}...'.format(rel_path))
a_dict = get_entry_dict(a_str)
b_dict = get_entry_dict(b_str)
all_keys = set().union(a_dict.keys(), b_dict.keys())
mapped = map(lambda key: get_item_change(
rel_path, key, a_dict.get(key), b_dict.get(key)), all_keys)
return filter(lambda entry: entry is not None, mapped)
class ItemChangeTest(unittest.TestCase):
@staticmethod
def dict_to_prop_str(dict: Dict[str,str]) -> str:
toret = ''
for key,val in dict.items:
toret += "{key}={value}\n".format(key=key,val=val)
return toret
def get_changed_test(self):
deleted_key = 'deleted.property.key'
deleted_val = 'will be deleted'
change_key = 'change.property.key'
change_val_a = 'original value'
change_val_b = 'new value'
change_key2 = 'change2.property.key'
change_val2_a = 'original value 2'
change_val2_b = ''
addition_key = 'addition.property.key'
addition_new_val = 'the added value'
same_key = 'samevalue.property.key'
same_value = 'the same value'
same_key2 = 'samevalue2.property.key'
same_value2 = ''
a_dict = {
deleted_key: deleted_val,
change_key: change_val_a,
change_key2: change_val2_a,
same_key: same_value,
same_key2: same_value2
}
b_dict = {
change_key: change_val_b,
change_key2: change_val2_b,
addition_key: addition_new_val,
same_key: same_value,
same_key2: same_value2
}
a_str = ItemChangeTest.dict_to_prop_str(a_dict)
b_str = ItemChangeTest.dict_to_prop_str(b_dict)
rel_path = 'my/rel/path.properties'
key_to_change = {}
for item_change in get_changed(rel_path, a_str, b_str):
self.assertEqual(item_change.rel_path, rel_path)
key_to_change[item_change.key] = item_change
deleted_item = key_to_change[deleted_key]
self.assertEqual(deleted_item.type, ChangeType.DELETION)
self.assertEqual(deleted_item.prev_val, deleted_val)
self.assertEqual(deleted_item.cur_val, None)
addition_item = key_to_change[addition_key]
self.assertEqual(addition_item.type, ChangeType.ADDITION)
self.assertEqual(addition_item.prev_val, None)
self.assertEqual(deleted_item.cur_val, addition_new_val)
change_item = key_to_change[change_key]
self.assertEqual(change_item.type, ChangeType.CHANGE)
self.assertEqual(change_item.prev_val, change_val_a)
self.assertEqual(change_item.cur_val, change_val_b)
change_item2 = key_to_change[change_key2]
self.assertEqual(change_item2.type, ChangeType.CHANGE)
self.assertEqual(change_item2.prev_val, change_val2_a)
self.assertEqual(change_item2.cur_val, change_val2_b)
self.assertTrue(same_key not in key_to_change)
self.assertTrue(same_key2 not in key_to_change)