devicetype-library/tests/definitions_test.py
Daniel W. Anner d0cb58d96e
Adding Tests: Device Power (#1463)
* broke component validation out to its own function. Adding logic for validating device power input.

* Added console-port poe true for adapters. Updating devices to add proper power sources.

* Adding switches to allow for tests to properly determine POE status.

* Fixing files with missing power definitions.
2023-07-17 15:23:56 -04:00

164 lines
6.6 KiB
Python

from test_configuration import COMPONENT_TYPES, IMAGE_FILETYPES, SCHEMAS
from yaml_loader import DecimalSafeLoader
from device_types import DeviceType, ModuleType, verify_filename, validate_components
import decimal
import glob
import json
import os
from urllib.request import urlopen
import pytest
import yaml
from jsonschema import Draft4Validator, RefResolver
from jsonschema.exceptions import ValidationError
def _get_definition_files():
"""
Return a list of all definition files within the specified path.
"""
file_list = []
for path, schema in SCHEMAS:
# Initialize the schema
with open(f"schema/{schema}") as schema_file:
schema = json.loads(schema_file.read(), parse_float=decimal.Decimal)
# Validate that the schema exists
assert schema, f"Schema definition for {path} is empty!"
# Map each definition file to its schema as a tuple (file, schema)
for file in sorted(glob.glob(f"{path}/*/*", recursive=True)):
file_list.append((file, schema))
return file_list
def _get_image_files():
"""
Return a list of all image files within the specified path and manufacturer.
"""
file_list = []
# Map each image file to its manufacturer
for file in sorted(glob.glob(f"elevation-images{os.path.sep}*{os.path.sep}*", recursive=True)):
# Validate that the file extension is valid
assert file.split(os.path.sep)[2].split('.')[-1] in IMAGE_FILETYPES, f"Invalid file extension: {file}"
# Map each image file to its manufacturer as a tuple (manufacturer, file)
file_list.append((file.split(os.path.sep)[1], file))
return file_list
def _decimal_file_handler(uri):
"""
Handler to work with floating decimals that fail normal validation.
"""
with urlopen(uri) as url:
result = json.loads(url.read().decode("utf-8"), parse_float=decimal.Decimal)
return result
def test_environment():
"""
Run basic sanity checks on the environment to ensure tests are running correctly.
"""
# Validate that definition files exist
assert definition_files, "No definition files found!"
definition_files = _get_definition_files()
image_files = _get_image_files()
@pytest.mark.parametrize(('file_path', 'schema'), definition_files)
def test_definitions(file_path, schema):
"""
Validate each definition file using the provided JSON schema and check for duplicate entries.
"""
# Check file extension. Only .yml or .yaml files are supported.
assert file_path.split('.')[-1] in ('yaml', 'yml'), f"Invalid file extension: {file_path}"
# Read file
with open(file_path) as definition_file:
content = definition_file.read()
# Check for trailing newline. YAML files must end with an emtpy newline.
assert content.endswith('\n'), "Missing trailing newline"
# Load YAML data from file
definition = yaml.load(content, Loader=DecimalSafeLoader)
# Validate YAML definition against the supplied schema
try:
resolver = RefResolver(
f"file://{os.getcwd()}/schema/devicetype.json",
schema,
handlers={"file": _decimal_file_handler},
)
# Validate definition against schema
Draft4Validator(schema, resolver=resolver).validate(definition)
except ValidationError as e:
# Schema validation failure. Ensure you are following the proper format.
pytest.fail(f"{file_path} failed validation: {e}", False)
# Identify if the definition is for a Device or Module
if "device-types" in file_path:
# A device
this_device = DeviceType(definition, file_path)
else:
# A module
this_device = ModuleType(definition, file_path)
# Verify the slug is valid, only if the definition type is a Device
if this_device.isDevice:
assert this_device.verify_slug(), pytest.fail(this_device.failureMessage, False)
# Verify the filename is valid. Must either be the model or part_number.
assert verify_filename(this_device), pytest.fail(this_device.failureMessage, False)
# Check for duplicate components within the definition
assert validate_components(COMPONENT_TYPES, this_device), pytest.fail(this_device.failureMessage, False)
# Check for empty quotes and fail if found
def iterdict(var):
for dict_value in var.values():
if isinstance(dict_value, dict):
iterdict(dict_value)
if isinstance(dict_value, list):
iterlist(dict_value)
else:
if(isinstance(dict_value, str) and not dict_value):
pytest.fail(f'{file_path} has empty quotes', False)
def iterlist(var):
for list_value in var:
if isinstance(list_value, dict):
iterdict(list_value)
elif isinstance(list_value, list):
iterlist(list_value)
# Check for valid power definitions
if this_device.isDevice:
assert this_device.validate_power(), pytest.fail(this_device.failureMessage, False)
# Check for images if front_image or rear_image is True
if (definition.get('front_image') or definition.get('rear_image')):
# Find images for given manufacturer, with matching device slug (exact match including case)
manufacturer_images = [image[1] for image in image_files if image[0] == file_path.split('/')[1] and os.path.basename(image[1]).split('.')[0] == this_device.get_slug()]
if not manufacturer_images:
pytest.fail(f'{file_path} has Front or Rear Image set to True but no images found for manufacturer/device (slug={this_device.get_slug()})', False)
elif len(manufacturer_images)>2:
pytest.fail(f'More than 2 images found for device with slug {this_device.get_slug()}: {manufacturer_images}', False)
# If front_image is True, verify that a front image exists
if(definition.get('front_image')):
front_image = [image_path.split('/')[2] for image_path in manufacturer_images if os.path.basename(image_path).split('.')[1] == 'front']
if not front_image:
pytest.fail(f'{file_path} has front_image set to True but no matching image found for device ({manufacturer_images})', False)
# If rear_image is True, verify that a front image exists
if(definition.get('rear_image')):
rear_image = [image_path.split('/')[2] for image_path in manufacturer_images if os.path.basename(image_path).split('.')[1] == 'rear']
if not rear_image:
pytest.fail(f'{file_path} has rear_image set to True but no images found for device', False)
iterdict(definition)