Skip to content

Commit

Permalink
Add comprehensive test suite and dependencies
Browse files Browse the repository at this point in the history
Introduce a range of tests for factories, data collection, version parsing, PAN-OS versions, and PDF generation to ensure robust code verification. Also, update `pyproject.toml` to include necessary dependencies for testing and code quality checks.
  • Loading branch information
cdot65 committed Sep 24, 2024
1 parent f057a30 commit 2228dfc
Show file tree
Hide file tree
Showing 14 changed files with 397 additions and 21 deletions.
42 changes: 23 additions & 19 deletions device_certificate_report/components/data_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,26 @@ def process_csv_file(csv_file: str) -> List[DeviceInfo]:
device = DeviceInfo(
device_name=device_names[i] if i < len(device_names) else None,
model=model or None,
serial_number=serial_numbers[i]
if i < len(serial_numbers)
else None,
serial_number=(
serial_numbers[i] if i < len(serial_numbers) else None
),
ipv4_address=ipv4_addresses[i] if i < len(ipv4_addresses) else None,
device_state=device_states[i] if i < len(device_states) else None,
device_certificate=device_certificates[i]
if i < len(device_certificates)
else None,
device_certificate_expiry_date=device_certificate_expiry_dates[i]
if i < len(device_certificate_expiry_dates)
else None,
device_certificate=(
device_certificates[i] if i < len(device_certificates) else None
),
device_certificate_expiry_date=(
device_certificate_expiry_dates[i]
if i < len(device_certificate_expiry_dates)
else None
),
software_version=software_version or None,
globalprotect_client=globalprotect_clients[i]
if i < len(globalprotect_clients)
and globalprotect_clients[i] != "0.0.0"
else None,
globalprotect_client=(
globalprotect_clients[i]
if i < len(globalprotect_clients)
and globalprotect_clients[i] != "0.0.0"
else None
),
)
devices.append(device)

Expand Down Expand Up @@ -147,9 +151,9 @@ def collect_data_from_panorama(panorama: Panorama) -> List[DeviceInfo]:
device_certificate=device_certificate or "",
device_certificate_expiry_date=device_certificate_expiry_date or "",
software_version=software_version or "",
globalprotect_client=globalprotect_client
if globalprotect_client != "0.0.0"
else "",
globalprotect_client=(
globalprotect_client if globalprotect_client != "0.0.0" else ""
),
)
devices.append(device)
except Exception as e:
Expand Down Expand Up @@ -256,8 +260,8 @@ def collect_data_from_firewall(firewall: Firewall) -> DeviceInfo:
device_certificate=device_certificate_status or "",
device_certificate_expiry_date=device_certificate_expiry_date or "",
software_version=software_version or "",
globalprotect_client=globalprotect_client
if globalprotect_client != "0.0.0"
else "",
globalprotect_client=(
globalprotect_client if globalprotect_client != "0.0.0" else ""
),
)
return device
4 changes: 3 additions & 1 deletion device_certificate_report/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,9 @@ def firewall(
devices_with_certificates=devices_with_certificates,
output_file=output_file if len(output_file) > 0 else f"{hostname}.pdf",
)
typer.echo(f"Report generated at {output_file if len(output_file) > 0 else f'{hostname}.pdf'}")
typer.echo(
f"Report generated at {output_file if len(output_file) > 0 else f'{hostname}.pdf'}"
)
except Exception as e:
logger.error(f"Failed to process Firewall: {e}")
sys.exit(1)
Expand Down
5 changes: 4 additions & 1 deletion device_certificate_report/utilities/filters.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# device_certificate_report/components/filters.py

from typing import List, Tuple
from device_certificate_report.config.hardware_families import AffectedModels, UnaffectedModels
from device_certificate_report.config.hardware_families import (
AffectedModels,
UnaffectedModels,
)
from device_certificate_report.config.panos_versions import MinimumPatchedVersions
from device_certificate_report.models.device import DeviceInfo
from device_certificate_report.components.version import parse_version
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ mkdocs = "^1.6.1"
mkdocs-material = "^9.5.35"
pytest = "^8.3.3"
ipdb = "^0.13.13"
black = "^24.8.0"
flake8 = "^7.1.1"
factory-boy = "^3.3.1"

[build-system]
requires = ["poetry-core"]
Expand Down
8 changes: 8 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# tests/conftest.py

import pytest
from tests.factories import DeviceInfoFactory

@pytest.fixture
def sample_device():
return DeviceInfoFactory()
20 changes: 20 additions & 0 deletions tests/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# tests/factories.py

import factory
from device_certificate_report.models.device import DeviceInfo

class DeviceInfoFactory(factory.Factory):
class Meta:
model = DeviceInfo

device_name = factory.Sequence(lambda n: f"device{n}")
model = "PA-220"
serial_number = factory.Sequence(lambda n: f"serial{n}")
ipv4_address = factory.Sequence(lambda n: f"192.168.1.{n}")
device_state = "Connected"
device_certificate = "Valid"
device_certificate_expiry_date = "2024-12-31"
software_version = "10.0.0"
globalprotect_client = "5.2.6"
min_required_version = None
notes = None
28 changes: 28 additions & 0 deletions tests/test_cleaner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# tests/test_cleaner.py

from device_certificate_report.utilities.cleaner import clean_html_tags, clean_csv
import csv
import io

def test_clean_html_tags():
text = '<p>"Some;Text";</p>'
cleaned = clean_html_tags(text)
assert cleaned == '"Some;Text";'

def test_clean_csv(tmp_path):
input_content = '''"Column1","Column2"
"<p>Data1</p>","<p>Data2</p>"
"Data;3","Data;4"
'''
input_file = tmp_path / "input.csv"
output_file = tmp_path / "output.csv"
input_file.write_text(input_content)

clean_csv(str(input_file), str(output_file))

with open(output_file, "r", encoding="utf-8") as f:
reader = csv.reader(f)
rows = list(reader)
assert rows[0] == ['Column1', 'Column2']
assert rows[1] == ['Data1', 'Data2']
assert rows[2] == ['Data;3', 'Data;4']
154 changes: 154 additions & 0 deletions tests/test_data_collection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# tests/test_data_collection.py

import pytest
from unittest.mock import MagicMock
from device_certificate_report.components.data_collection import (
process_csv_file,
collect_data_from_panorama,
collect_data_from_firewall,
)
from panos.panorama import Panorama
from panos.firewall import Firewall
import xml.etree.ElementTree as ET

from tests.factories import DeviceInfoFactory

def test_process_csv_file(tmp_path):
# Create sample devices using factories
device1 = DeviceInfoFactory()
device2 = DeviceInfoFactory(
device_name="device2",
model="PA-3020",
software_version="9.1.0",
globalprotect_client=None,
)

# Create CSV content from devices
csv_content = f"""Device Name,IP Address Serial Number,IP Address IPv4,Status Device State,Status Device Certificate,Status Device Certificate Expiry Date,GlobalProtect Client,Model,Software Version
"{device1.device_name}","{device1.serial_number}","{device1.ipv4_address}","{device1.device_state}","{device1.device_certificate}","{device1.device_certificate_expiry_date}","{device1.globalprotect_client or '0.0.0'}","{device1.model}","{device1.software_version}"
"{device2.device_name}","{device2.serial_number}","{device2.ipv4_address}","{device2.device_state}","{device2.device_certificate}","{device2.device_certificate_expiry_date}","{device2.globalprotect_client or '0.0.0'}","{device2.model}","{device2.software_version}"
"""
csv_file = tmp_path / "test.csv"
csv_file.write_text(csv_content)

devices = process_csv_file(str(csv_file))

assert len(devices) == 2
assert devices[0].device_name == device1.device_name
assert devices[0].model == device1.model
assert devices[0].software_version == device1.software_version
assert devices[1].device_name == device2.device_name
assert devices[1].globalprotect_client is None # Should be None because it's "0.0.0"

def test_collect_data_from_panorama(monkeypatch):
# Mock Panorama instance
panorama = Panorama("hostname", "username", "password")

# Create sample devices using factories
device1 = DeviceInfoFactory()
device2 = DeviceInfoFactory(
device_name="device2",
model="PA-3020",
serial_number="serial2",
ipv4_address="192.168.1.2",
device_state="Disconnected",
device_certificate="Invalid",
device_certificate_expiry_date="2023-11-30",
software_version="9.1.0",
globalprotect_client=None,
)

# Create XML response using devices
mock_response = ET.fromstring(f"""
<response status="success">
<result>
<devices>
<entry>
<hostname>{device1.device_name}</hostname>
<model>{device1.model}</model>
<serial>{device1.serial_number}</serial>
<ip-address>{device1.ipv4_address}</ip-address>
<connected>{"yes" if device1.device_state == "Connected" else "no"}</connected>
<device-cert-present>{device1.device_certificate}</device-cert-present>
<device-cert-expiry-date>{device1.device_certificate_expiry_date}</device-cert-expiry-date>
<sw-version>{device1.software_version}</sw-version>
<global-protect-client-package-version>{device1.globalprotect_client or "0.0.0"}</global-protect-client-package-version>
</entry>
<entry>
<hostname>{device2.device_name}</hostname>
<model>{device2.model}</model>
<serial>{device2.serial_number}</serial>
<ip-address>{device2.ipv4_address}</ip-address>
<connected>{"yes" if device2.device_state == "Connected" else "no"}</connected>
<device-cert-present>{device2.device_certificate}</device-cert-present>
<device-cert-expiry-date>{device2.device_certificate_expiry_date}</device-cert-expiry-date>
<sw-version>{device2.software_version}</sw-version>
<global-protect-client-package-version>{device2.globalprotect_client or "0.0.0"}</global-protect-client-package-version>
</entry>
</devices>
</result>
</response>
""")

def mock_op(cmd, cmd_xml=False):
return mock_response

monkeypatch.setattr(panorama, "op", mock_op)

devices = collect_data_from_panorama(panorama)

assert len(devices) == 2
assert devices[0].device_name == device1.device_name
assert devices[0].device_state == device1.device_state
assert devices[1].device_name == device2.device_name
assert devices[1].device_state == device2.device_state

def test_collect_data_from_firewall(monkeypatch):
# Mock Firewall instance
firewall = Firewall("hostname", "username", "password")

# Create a sample device using factory
device = DeviceInfoFactory()

# Mock responses from firewall.op()
mock_system_info_response = ET.fromstring(f"""
<response status="success">
<result>
<system>
<hostname>{device.device_name}</hostname>
<model>{device.model}</model>
<serial>{device.serial_number}</serial>
<ip-address>{device.ipv4_address}</ip-address>
<sw-version>{device.software_version}</sw-version>
<global-protect-client-package-version>{device.globalprotect_client or "0.0.0"}</global-protect-client-package-version>
<device-certificate-status>{device.device_certificate}</device-certificate-status>
</system>
</result>
</response>
""")

mock_device_cert_response = ET.fromstring(f"""
<response status="success">
<result>
<device-certificate>
<validity>{device.device_certificate}</validity>
<not_valid_after>{device.device_certificate_expiry_date}</not_valid_after>
</device-certificate>
</result>
</response>
""")

def mock_op(cmd, cmd_xml=False):
if "system" in cmd:
return mock_system_info_response
elif "device-certificate" in cmd:
return mock_device_cert_response

monkeypatch.setattr(firewall, "op", mock_op)

collected_device = collect_data_from_firewall(firewall)

assert collected_device.device_name == device.device_name
assert collected_device.model == device.model
assert collected_device.device_certificate == device.device_certificate
assert collected_device.device_certificate_expiry_date == device.device_certificate_expiry_date
16 changes: 16 additions & 0 deletions tests/test_device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# tests/test_device.py

from tests.factories import DeviceInfoFactory

def test_device_info_creation():
device = DeviceInfoFactory()

assert device.device_name == "device5"
assert device.model == "PA-220"
assert device.serial_number == "serial5"
assert device.ipv4_address == "192.168.1.5"
assert device.device_state == "Connected"
assert device.device_certificate == "Valid"
assert device.device_certificate_expiry_date == "2024-12-31"
assert device.software_version == "10.0.0"
assert device.globalprotect_client == "5.2.6"
32 changes: 32 additions & 0 deletions tests/test_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# tests/test_filters.py

from device_certificate_report.utilities.filters import (
filter_devices_by_model,
split_devices_by_version,
)
from tests.factories import DeviceInfoFactory

def test_filter_devices_by_model():
device1 = DeviceInfoFactory(model="PA-220")
device2 = DeviceInfoFactory(device_name="device2", model="PA-460")
device3 = DeviceInfoFactory(device_name="device3", model="UnknownModel")

devices = [device1, device2, device3]
affected, unaffected = filter_devices_by_model(devices)
assert len(affected) == 1
assert len(unaffected) == 2
assert affected[0].device_name == device1.device_name
assert unaffected[0].device_name == device2.device_name
assert unaffected[1].notes == "Model not recognized; considered unaffected."

def test_split_devices_by_version():
device1 = DeviceInfoFactory(software_version="9.1.10")
device2 = DeviceInfoFactory(device_name="device2", software_version="10.2.12-h12")
device3 = DeviceInfoFactory(device_name="device3", software_version="11.2.0")

devices = [device1, device2, device3]
no_upgrade_required, upgrade_required = split_devices_by_version(devices)
assert len(no_upgrade_required) == 2
assert len(upgrade_required) == 1
assert upgrade_required[0].device_name == device1.device_name
assert upgrade_required[0].min_required_version == "9.1.11-h5"
18 changes: 18 additions & 0 deletions tests/test_hardware_families.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# tests/test_hardware_families.py

from device_certificate_report.utilities.filters import (
is_affected_model,
is_unaffected_model,
)


def test_is_affected_model():
assert is_affected_model("PA-220")
assert is_affected_model("PA-3020")
assert not is_affected_model("PA-460")


def test_is_unaffected_model():
assert is_unaffected_model("PA-460")
assert is_unaffected_model("PA-1410")
assert not is_unaffected_model("PA-220")
Loading

0 comments on commit 2228dfc

Please sign in to comment.