Skip to content

Commit

Permalink
Merge pull request #104 from khalford/write_tests_for_untested_code_v2
Browse files Browse the repository at this point in the history
Write tests for untested code.
  • Loading branch information
meoflynn committed Oct 20, 2023
2 parents 98e0fb1 + 76baab6 commit 929845d
Show file tree
Hide file tree
Showing 14 changed files with 218 additions and 174 deletions.
7 changes: 2 additions & 5 deletions Pynetbox_Data_Uploader/.pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ max-line-length=120

# Disable various warnings:
# C0114: Missing module string - we don't need module strings for the small repo
# C0115: Missing class doc string - a lot of the actions are self explanatory
# W0511: TODOs they're well....to do later
# R0801: Similar lines - Imports and method signatures will flag this, such as forwarding action args
# C0116: Missing method docstring - Adds too much noise
# E0401: Ignore import errors as imports work

disable=C0114,C0115,E0401,W0511,R0801,C0116,E0401

disable=C0114,W0511
44 changes: 0 additions & 44 deletions Pynetbox_Data_Uploader/lib/netbox_api/format_dict.py

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
from typing import Optional
from netbox_api.netbox_connect import NetboxConnect


class NetboxExistence:
class NetboxCheck:
"""
This class contains methods that check if an object exists in Netbox.
"""

def __init__(self, url: str, token: str, api: Optional = None):
if not api:
self.netbox = NetboxConnect(url, token).api_object()
else:
self.netbox = api
def __init__(self, netbox):
self.netbox = netbox

def check_device_exists(self, device_name: str) -> bool:
"""
Expand Down
14 changes: 5 additions & 9 deletions Pynetbox_Data_Uploader/lib/netbox_api/netbox_create.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
from typing import Optional
from netbox_api.netbox_connect import NetboxConnect
from typing import Union, Dict, List


class NetboxDCIM:
class NetboxCreate:
"""
This class contains methods that will interact create objects in Netbox.
"""

def __init__(self, url: str, token: str, api: Optional = None):
if not api:
self.netbox = NetboxConnect(url, token).api_object()
else:
self.netbox = api
def __init__(self, netbox):
self.netbox = netbox

def create_device(self, data: dict | list) -> bool:
def create_device(self, data: Union[Dict, List]) -> bool:
"""
This method uses the pynetbox Api to create a device in Netbox.
:param data: A list of or a single dictionary.
Expand Down
39 changes: 24 additions & 15 deletions Pynetbox_Data_Uploader/lib/netbox_api/netbox_data.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
from operator import attrgetter
from typing import Optional, Union
from netbox_api.netbox_connect import NetboxConnect
from enums.dcim_device_id import DeviceInfoID
from enums.dcim_device_no_id import DeviceInfoNoID
from typing import Union, Dict
from lib.enums.dcim_device_id import DeviceInfoID
from lib.enums.dcim_device_no_id import DeviceInfoNoID

# pylint:disable = too-few-public-methods


class NetboxGetID(NetboxConnect):
class NetboxGetID:
"""
This class retrieves field value ID's from Netbox.
"""

def __init__(self, url: str, token: str, api: Optional = None):
def __init__(self, netbox):
"""
This method initialises the class with the following parameters.
Also, it allows dependency injection testing.
:param url: Netbox website URL.
:param token: Netbox authentication token.
This method allows the Netbox Api Object and Enums to be accessible within the class.
"""
if not api:
self.netbox = NetboxConnect(url, token).api_object()
else:
self.netbox = api
self.netbox = netbox
self.enums_id = DeviceInfoID
self.enums_no_id = DeviceInfoNoID

Expand All @@ -45,7 +38,23 @@ def get_id(
if isinstance(site_value, int):
site_name = self.netbox.dcim.sites.get(site_value).name
site_slug = site_name.replace(" ", "-").lower()
value = value.get(name=netbox_value, site=site_slug)
value = value.get(name=netbox_value, site=site_slug)
else:
value = value.get(name=netbox_value).id
return value

def get_id_from_key(self, key: str, dictionary: Dict) -> Union[str, int]:
"""
This method calls the get_id method to retrieve the Netbox id of a value.
:param key: The attribute to look for.
:param dictionary: The device dictionary being referenced.
:return: If an ID was needed and found it returns the ID. If an ID was not needed it returns the original value.
"""
if key not in list(self.enums_no_id.__members__):
value = self.get_id(
attr_string=key,
netbox_value=dictionary[key],
site_value=dictionary["site"],
)
return value
return dictionary[key]
Empty file.
37 changes: 0 additions & 37 deletions Pynetbox_Data_Uploader/lib/utils/csv_to_dict.py

This file was deleted.

66 changes: 66 additions & 0 deletions Pynetbox_Data_Uploader/lib/utils/format_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from typing import Dict, List
from pandas import read_csv
from lib.netbox_api.netbox_data import NetboxGetID


class FormatDict:
"""
This class takes dictionaries with string values and changes those to ID values from Netbox.
"""

def __init__(self, netbox):
"""
This method initialises the class with the following parameters.
:param netbox: The Netbox object to pass into NetboxGetID
"""
self.netbox = netbox

def iterate_dicts(self, dicts: list) -> List:
"""
This method iterates through each dictionary and calls a format method on each.
:return: Returns the formatted dictionaries.
"""
new_dicts = []
for dictionary in dicts:
new_dicts.append(self.format_dict(dictionary))
return new_dicts

def format_dict(self, dictionary) -> Dict:
"""
This method iterates through each value in the dictionary.
If the value needs to be converted into a Pynetbox ID it calls the .get() method.
:param dictionary: The dictionary to be formatted
:return: Returns the formatted dictionary
"""
for key in dictionary:
netbox_id = NetboxGetID(self.netbox).get_id_from_key(key=key, dictionary=dictionary)
dictionary[key] = netbox_id
return dictionary


@staticmethod
def csv_to_python(file_path: str) -> Dict:
"""
This method reads data from csv files and writes them to a dictionary.
:param file_path: The file path of the utils file to be read from.
:return: Returns the data from the csv as a dictionary.
"""
dataframe = read_csv(file_path)
return dataframe.to_dict(orient="list")

@staticmethod
def separate_data(data: dict) -> List:
"""
This method reduces Pandas utils to Dict conversion to individual dictionaries.
:param data: The data from the utils file.
:return: Returns a list of dictionaries which each represent a row of data from utils.
"""
data_keys = list(data.keys())
len_rows = len(data[data_keys[0]])
dicts = []
for index in range(len_rows):
new_dict = {}
for key in data_keys:
new_dict.update({key: data[key][index]})
dicts.append(new_dict)
return dicts
82 changes: 82 additions & 0 deletions Pynetbox_Data_Uploader/tests/test_csv_to_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from unittest.mock import NonCallableMock, patch, MagicMock
import pytest
from lib.utils.format_dict import FormatDict


@pytest.fixture(name="instance")
def instance_fixture():
"""
This fixture method calls the class being tested.
:return: The class object.
"""
netbox = NonCallableMock()
return FormatDict(netbox)


def test_csv_to_python(instance):
"""
This test ensures that the csv_read method is called once with the file_path arg.
"""
file_path = NonCallableMock()
with patch("lib.utils.format_dict.read_csv") as mock_read_csv:
res = instance.csv_to_python(file_path)
mock_read_csv.assert_called_once_with(file_path)
mock_read_csv.return_value.to_dict.assert_called_once_with(orient="list")
assert res == mock_read_csv.return_value.to_dict.return_value


def test_separate_data(instance):
"""
This test ensures that the dictionaries from pandas formatted into row by row dictionaries.
These are much more understandable and can be used individually or in bulk.
"""
test_data = {"key1": ["Adata1", "Bdata1"], "key2": ["Adata2", "Bdata2"]}
format_data = instance.separate_data(test_data)
assert format_data == [
{"key1": "Adata1", "key2": "Adata2"},
{"key1": "Bdata1", "key2": "Bdata2"},
]


def test_iterate_dicts_no_items(instance):
"""
This test ensures that an empty list is returned when there are no dictionaries.
"""
mock_dictionary = MagicMock()
with patch("lib.utils.format_dict.FormatDict.format_dict") as mock_format:
res = instance.iterate_dicts([mock_dictionary])
mock_format.assert_called_once_with(mock_dictionary)
assert res == [mock_format.return_value]


def test_iterate_dicts_one_item(instance):
"""
This test ensures the format method is called on the only dictionary.
"""
mock_dictionary = MagicMock()
with patch("lib.utils.format_dict.FormatDict.format_dict") as mock_format:
res = instance.iterate_dicts([mock_dictionary])
mock_format.assert_called_once_with(mock_dictionary)
assert res == [mock_format.return_value]


def test_iterate_dicts_many_items(instance):
"""
This test ensures the format method is called each dictionary.
"""
mock_dictionary_1 = MagicMock()
mock_dictionary_3 = MagicMock()
mock_dictionary_2 = MagicMock()
with patch("lib.utils.format_dict.FormatDict.format_dict") as mock_format:
res = instance.iterate_dicts(
[mock_dictionary_1, mock_dictionary_2, mock_dictionary_3]
)
mock_format.assert_any_call(mock_dictionary_1)
mock_format.assert_any_call(mock_dictionary_2)
mock_format.assert_any_call(mock_dictionary_3)
expected = [
mock_format.return_value,
mock_format.return_value,
mock_format.return_value,
]
assert res == expected
31 changes: 0 additions & 31 deletions Pynetbox_Data_Uploader/tests/test_csv_utils.py

This file was deleted.

Loading

0 comments on commit 929845d

Please sign in to comment.