devicetype-library/tests/definitions_test.py
aurelienbyi ad8ad1589e
directory separator appropriate for exec env (#1250)
Co-authored-by: aurelien.bailly <aurelien.bailly@destiny.eu>
2023-04-01 15:44:41 -04:00

174 lines
5.9 KiB
Python

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
from yaml_loader import DecimalSafeLoader
SCHEMAS = (
('device-types', 'devicetype.json'),
('module-types', 'moduletype.json'),
)
IMAGE_FILETYPES = (
'bmp', 'gif', 'pjp', 'jpg', 'pjpeg', 'jpeg', 'jfif', 'png', 'tif', 'tiff', 'webp'
)
COMPONENT_TYPES = (
'console-ports',
'console-server-ports',
'power-ports',
'power-outlets',
'interfaces',
'front-ports',
'rear-ports',
'device-bays',
'module-bays',
)
def _get_definition_files():
"""
Return a list of all definition files within the specified path.
"""
ret = []
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
for f in sorted(glob.glob(f"{path}/*/*", recursive=True)):
ret.append((f, schema))
return ret
def _get_image_files():
"""
Return a list of all image files within the specified path and manufacturer.
"""
ret = []
for f in sorted(glob.glob(f"elevation-images{os.path.sep}*{os.path.sep}*", recursive=True)):
assert f.split(os.path.sep)[2].split('.')[-1] in IMAGE_FILETYPES, f"Invalid file extension: {f}"
ret.append((f.split(os.path.sep)[1], f))
return ret
definition_files = _get_definition_files()
image_files = _get_image_files()
known_slugs = set()
def _decimal_file_handler(uri):
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!"
@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
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
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},
)
Draft4Validator(schema, resolver=resolver).validate(definition)
except ValidationError as e:
pytest.fail(f"{file_path} failed validation: {e}", False)
# Check for duplicate slug
if file_path.startswith('device-types/'):
slug = definition.get('slug')
if slug and slug in known_slugs:
pytest.fail(f'{file_path} device type has duplicate slug "{slug}"', False)
elif slug:
known_slugs.add(slug)
# Check for duplicate components
for component_type in COMPONENT_TYPES:
known_names = set()
defined_components = definition.get(component_type, [])
for idx, component in enumerate(defined_components):
name = component.get('name')
if name in known_names:
pytest.fail(f'Duplicate entry "{name}" in {component_type} list', False)
known_names.add(name)
# Check for empty quotes
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 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] == 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={slug})', False)
elif len(manufacturer_images)>2:
pytest.fail(f'More than 2 images found for device with slug {slug}: {manufacturer_images}', False)
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(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)