-
Karel van Klink authoredKarel van Klink authored
test_imports.py 16.94 KiB
import json
from pathlib import Path
from unittest.mock import patch
import pytest
from gso.cli.imports import (
import_edge_port,
import_iptrunks,
import_office_routers,
import_opengear,
import_routers,
import_sites,
import_super_pop_switches,
)
from gso.products.product_blocks.edge_port import EdgePortType, EncapsulationType
from gso.products.product_blocks.iptrunk import IptrunkType
from gso.products.product_blocks.router import RouterRole
from gso.products.product_blocks.site import SiteTier
from gso.products.product_types.router import Router
from gso.products.product_types.site import Site
from gso.utils.helpers import iso_from_ipv4
from gso.utils.shared_enums import Vendor
from gso.utils.types.interfaces import PhysicalPortCapacity
##############
# FIXTURES #
##############
@pytest.fixture()
def temp_file(tmp_path) -> Path:
return tmp_path / "data.json"
@pytest.fixture()
def iptrunk_data(temp_file, router_subscription_factory, faker) -> (Path, dict):
def _iptrunk_data(
*,
ipv4_network=None,
ipv6_network=None,
min_links=None,
isis_metric=None,
side_a_node=None,
side_b_node=None,
side_a_members=None,
side_b_members=None,
side_a_ae_name=None,
side_b_ae_name=None,
):
router_side_a = router_subscription_factory()
router_side_b = router_subscription_factory()
ipv4_network = ipv4_network or str(faker.ipv4_network(max_subnet=31))
ipv6_network = ipv6_network or str(faker.ipv6_network(max_subnet=126))
iptrunk_data = {
"id": faker.geant_sid(),
"config": {
"common": {
"link_speed": PhysicalPortCapacity.HUNDRED_GIGABIT_PER_SECOND,
"minimum_links": min_links or 3,
"isis_metric": isis_metric or 500,
"type": IptrunkType.DARK_FIBER,
},
"nodeA": {
"name": side_a_node or Router.from_subscription(router_side_a).router.router_fqdn,
"ae_name": side_a_ae_name or faker.network_interface(),
"port_sid": faker.geant_sid(),
"members": side_a_members
or [
{
"interface_name": faker.network_interface(),
"interface_description": faker.sentence(),
}
for _ in range(5)
],
"ipv4_address": ipv4_network,
"ipv6_address": ipv6_network,
},
"nodeB": {
"name": side_b_node or Router.from_subscription(router_side_b).router.router_fqdn,
"ae_name": side_b_ae_name or faker.network_interface(),
"port_sid": faker.geant_sid(),
"members": side_b_members
or [
{
"interface_name": faker.network_interface(),
"interface_description": faker.sentence(),
}
for _ in range(5)
],
"ipv4_address": ipv4_network,
"ipv6_address": ipv6_network,
},
},
}
temp_file.write_text(json.dumps([iptrunk_data]))
return {"path": str(temp_file), "data": iptrunk_data}
return _iptrunk_data
@pytest.fixture()
def site_data(faker, temp_file):
def _site_data(**kwargs):
site_data = {
"site_name": faker.site_name(),
"site_city": faker.city(),
"site_country": faker.country(),
"site_country_code": faker.country_code(),
"site_latitude": str(faker.latitude()),
"site_longitude": str(faker.longitude()),
"site_bgp_community_id": faker.pyint(),
"site_internal_id": faker.pyint(),
"site_tier": SiteTier.TIER1,
"site_ts_address": faker.ipv4(),
}
site_data.update(**kwargs)
temp_file.write_text(json.dumps([site_data]))
return {"path": str(temp_file), "data": site_data}
return _site_data
@pytest.fixture()
def router_data(temp_file, faker, site_subscription_factory):
def _router_data(**kwargs):
mock_ipv4 = faker.ipv4()
router_data = {
"router_site": Site.from_subscription(site_subscription_factory()).site.site_name,
"hostname": str(faker.ipv4()),
"ts_port": faker.port_number(is_user=True),
"router_role": RouterRole.PE,
"router_vendor": Vendor.JUNIPER,
"router_lo_ipv4_address": mock_ipv4,
"router_lo_ipv6_address": str(faker.ipv6()),
"router_lo_iso_address": iso_from_ipv4(mock_ipv4),
}
router_data.update(**kwargs)
temp_file.write_text(json.dumps([router_data]))
return {"path": str(temp_file), "data": router_data}
return _router_data
@pytest.fixture()
def super_pop_switch_data(temp_file, faker, site_subscription_factory):
def _super_pop_switch_data(**kwargs):
super_pop_switch_data = {
"hostname": str(faker.ipv4()),
"super_pop_switch_site": Site.from_subscription(site_subscription_factory()).site.site_name,
"super_pop_switch_ts_port": faker.port_number(is_user=True),
"super_pop_switch_mgmt_ipv4_address": str(faker.ipv4()),
}
super_pop_switch_data.update(**kwargs)
temp_file.write_text(json.dumps([super_pop_switch_data]))
return {"path": str(temp_file), "data": super_pop_switch_data}
return _super_pop_switch_data
@pytest.fixture()
def office_router_data(temp_file, faker, site_subscription_factory):
def _office_router_data(**kwargs):
office_router_data = {
"office_router_fqdn": faker.domain_name(levels=4),
"office_router_site": Site.from_subscription(site_subscription_factory()).site.site_name,
"office_router_ts_port": faker.port_number(is_user=True),
"office_router_lo_ipv4_address": str(faker.ipv4()),
"office_router_lo_ipv6_address": str(faker.ipv6()),
}
office_router_data.update(**kwargs)
temp_file.write_text(json.dumps([office_router_data]))
return {"path": str(temp_file), "data": office_router_data}
return _office_router_data
@pytest.fixture()
def opengear_data(temp_file, faker, site_subscription_factory):
def _opengear_data(**kwargs):
opengear_data = {
"opengear_site": Site.from_subscription(site_subscription_factory()).site.site_name,
"opengear_hostname": faker.domain_name(levels=4),
"opengear_wan_address": str(faker.ipv4()),
"opengear_wan_netmask": str(faker.ipv4()),
"opengear_wan_gateway": str(faker.ipv4()),
}
opengear_data.update(**kwargs)
temp_file.write_text(json.dumps([opengear_data]))
return {"path": str(temp_file), "data": opengear_data}
return _opengear_data
@pytest.fixture()
def edge_port_data(temp_file, faker, nokia_router_subscription_factory, partner_factory):
def _edge_port_data(**kwargs):
edge_port_data = {
"node": Router.from_subscription(nokia_router_subscription_factory()).router.router_fqdn,
"service_type": EdgePortType.CUSTOMER,
"speed": PhysicalPortCapacity.TEN_GIGABIT_PER_SECOND,
"encapsulation": EncapsulationType.DOT1Q,
"name": "lag34",
"minimum_links": 2,
"geant_ga_id": faker.geant_gid(),
"mac_address": faker.mac_address(),
"partner": partner_factory()["name"],
"enable_lacp": True,
"ignore_if_down": False,
"ae_members": [
{
"interface_name": faker.network_interface(),
"interface_description": faker.sentence(),
},
{
"interface_name": faker.network_interface(),
"interface_description": faker.sentence(),
},
],
"description": faker.sentence(),
}
edge_port_data.update(**kwargs)
temp_file.write_text(json.dumps([edge_port_data]))
return {"path": str(temp_file), "data": edge_port_data}
return _edge_port_data
###########
# TESTS #
###########
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_iptrunk_success(mock_start_process, mock_sleep, iptrunk_data):
import_iptrunks(iptrunk_data()["path"])
assert mock_start_process.call_count == 1
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_site_success(mock_start_process, mock_sleep, site_data):
import_sites(site_data()["path"])
assert mock_start_process.call_count == 1
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_site_twice(mock_start_process, mock_sleep, site_data, site_subscription_factory, capfd):
site_import_data = site_data()
# Create an initial site
site_subscription_factory(
site_bgp_community_id=site_import_data["data"]["site_bgp_community_id"],
site_internal_id=site_import_data["data"]["site_internal_id"],
site_ts_address=site_import_data["data"]["site_ts_address"],
site_name=site_import_data["data"]["site_name"],
)
# Second identical import should print ValidationError to stdout
import_sites(site_import_data["path"])
captured_output, _ = capfd.readouterr()
assert "Validation error: 4 validation errors for SiteImportModel" in captured_output
assert "Value error, site_bgp_community_id must be unique [type=value_error, input_value=" in captured_output
assert "Value error, site_internal_id must be unique [type=value_error, input_value=" in captured_output
assert "Value error, site_ts_address must be unique [type=value_error, input_value=" in captured_output
assert "Value error, site_name must be unique [type=value_error, input_value=" in captured_output
assert mock_start_process.call_count == 0
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_site_with_invalid_data(mock_start_process, mock_sleep, site_data, capfd):
# invalid data, missing site_latitude and invalid site_longitude
incorrect_site_data = site_data(site_latitude=None, site_longitude="broken")
import_sites(incorrect_site_data["path"])
captured_output, _ = capfd.readouterr()
assert "Validation error: 2 validation errors for SiteImportModel" in captured_output
assert (
"""site_latitude
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]"""
in captured_output
)
assert (
"""site_longitude
Value error, Invalid longitude coordinate. Valid examples: '40.7128', '-74.0060', '180', '-180', '0'. [type=value_e"""
in captured_output
)
assert mock_start_process.call_count == 0
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_router_success(mock_start_process, mock_sleep, site_subscription_factory, router_data):
import_routers(router_data()["path"])
assert mock_start_process.call_count == 1
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_router_with_invalid_data(mock_start_process, mock_sleep, router_data, capfd):
broken_data = router_data(hostname="", router_lo_ipv6_address="Not an IP address")
import_routers(broken_data["path"])
captured_output, _ = capfd.readouterr()
# The extra space at the end of the next line is required, and not dangling by accident.
assert (
"""Validation error: 1 validation error for RouterImportModel
router_lo_ipv6_address
Input is not a valid IPv6 address [type=ip_v6_address, input_value='Not an IP address', input_type=str]"""
in captured_output
)
assert mock_start_process.call_count == 0
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_iptrunk_successful(mock_start_process, mock_sleep, iptrunk_data):
import_iptrunks(iptrunk_data()["path"])
assert mock_start_process.call_count == 1
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_iptrunk_invalid_router_id_side_a_and_b(mock_start_process, mock_sleep, iptrunk_data, capfd):
broken_data = iptrunk_data(side_a_node="Doesn't exist", side_b_node="Also doesn't exist")
import_iptrunks(broken_data["path"])
captured_output, _ = capfd.readouterr()
assert (
"""Validation error: 2 validation errors for IptrunkImportModel
side_a_node_id
Value error, Router not found [type=value_error, input_value='', input_type=str]
For further information visit https://errors.pydantic.dev/2.7/v/value_error
side_b_node_id
Value error, Router not found [type=value_error, input_value='', input_type=str]"""
in captured_output
)
assert mock_start_process.call_count == 0
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_iptrunk_non_unique_members_side_a_and_b(mock_start_process, mock_sleep, iptrunk_data, faker, capfd):
duplicate_interface = {"interface_name": faker.network_interface(), "interface_description": faker.sentence()}
side_a_members = [duplicate_interface for _ in range(5)]
side_b_members = [duplicate_interface for _ in range(5)]
broken_data = iptrunk_data(side_a_members=side_a_members, side_b_members=side_b_members)
import_iptrunks(broken_data["path"])
captured_output, _ = capfd.readouterr()
assert "Validation error: 2 validation errors for IptrunkImportModel" in captured_output
assert (
"""side_a_ae_members
List must be unique [type=unique_list, input_value=[{'interface_name':"""
) in captured_output
assert (
"""side_b_ae_members
List must be unique [type=unique_list, input_value=[{'interface_name':"""
) in captured_output
assert mock_start_process.call_count == 0
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_iptrunk_side_a_member_count_mismatch(mock_start_process, mock_sleep, iptrunk_data, faker, capfd):
side_a_members = [
{"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5)
]
side_b_members = [
{"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(6)
]
broken_data = iptrunk_data(side_a_members=side_a_members, side_b_members=side_b_members)
import_iptrunks(broken_data["path"])
captured_output, _ = capfd.readouterr()
assert (
"""Validation error: 1 validation error for IptrunkImportModel
Value error, Mismatch between Side A and B members [type=value_error, input_value={'partner': 'GEANT',"""
in captured_output
)
assert mock_start_process.call_count == 0
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_office_router_success(mock_start_process, mock_sleep, office_router_data):
import_office_routers(office_router_data()["path"])
assert mock_start_process.call_count == 1
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_super_pop_switch_success(mock_start_process, mock_sleep, super_pop_switch_data):
import_super_pop_switches(super_pop_switch_data()["path"])
assert mock_start_process.call_count == 1
@patch("gso.cli.imports.start_process")
def test_import_opengear_success(mock_start_process, opengear_data):
import_opengear(opengear_data()["path"])
assert mock_start_process.call_count == 1
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_edge_port_successful(mock_start_process, mock_sleep, edge_port_data):
import_edge_port(edge_port_data()["path"])
assert mock_start_process.call_count == 1
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_edge_port_with_invalid_router(
mock_start_process, mock_sleep, edge_port_data, capfd, nokia_router_subscription_factory
):
p_router = nokia_router_subscription_factory(router_role=RouterRole.P)
broken_data = edge_port_data(node=Router.from_subscription(p_router).router.router_fqdn)
import_edge_port(broken_data["path"])
captured_output, _ = capfd.readouterr()
assert f"Router {p_router} not found" in captured_output
assert mock_start_process.call_count == 0
@patch("gso.cli.imports.time.sleep")
@patch("gso.cli.imports.start_process")
def test_import_edge_port_with_invalid_partner(mock_start_process, mock_sleep, edge_port_data, capfd):
broken_data = edge_port_data(partner="INVALID")
import_edge_port(broken_data["path"])
captured_output, _ = capfd.readouterr()
assert "Partner INVALID not found" in captured_output
assert mock_start_process.call_count == 0