Source code for dhcpkit.tests.test_protocol_element

"""
Test whether the basic stuff of ProtocolElement works as intended
"""
import json
import unittest
from collections import OrderedDict
from ipaddress import IPv6Address

from dhcpkit.protocol_element import ElementDataRepresentation, JSONProtocolElementEncoder, ProtocolElement, \
    UnknownProtocolElement
from typing import Iterable, Union


[docs]class DemoElementBase(ProtocolElement): """ A simple element to test with """
[docs] def load_from(self, buffer: bytes, offset: int = 0, length: int = None) -> int: """ Intentionally left empty. Specific implementations must be tested separately. :param buffer: The buffer to read data from :param offset: The offset in the buffer where to start reading :param length: The amount of data we are allowed to read from the buffer :return: The number of bytes used from the buffer """
[docs] def save(self) -> Union[bytes, bytearray]: """ Intentionally left empty. Specific implementations must be tested separately. :return: The buffer with the data from this element """
[docs]class DemoElement(DemoElementBase): """ Sub-element to test with """
[docs]class OneParameterDemoElement(DemoElementBase): """ Sub-element to test with """ def __init__(self, one): self.one = one
[docs]class TwoParameterDemoElement(DemoElementBase): """ Sub-element to test with """ def __init__(self, one: int, two: DemoElementBase): self.one = one self.two = two
[docs]class ThreeParameterDemoElement(DemoElementBase): """ Sub-element to test with """ def __init__(self, one: int, two: str, three: Iterable[DemoElementBase]): self.one = one self.two = two self.three = three or []
[docs]class OneParameterDisplayDemoElement(DemoElementBase): """ Sub-element to test with """ def __init__(self, one): self.one = one
[docs] def display_one(self): """ Nicer display for property one """ return ElementDataRepresentation("**{}**".format(self.one))
[docs]class TwoParameterDisplayDemoElement(DemoElementBase): """ Sub-element to test with """ def __init__(self, one: int, two: DemoElementBase): self.one = one self.two = two
[docs] def display_one(self): """ Nicer display for property one """ return ElementDataRepresentation("**{}**".format(self.one))
[docs]class OneParameterDisplayHiddenDemoElement(DemoElementBase): """ Sub-element to test with """ def __init__(self, one): self.one = one display_one = ElementDataRepresentation('**HIDDEN**')
[docs]class TwoParameterDisplayHiddenDemoElement(DemoElementBase): """ Sub-element to test with """ def __init__(self, one: int, two: DemoElementBase): self.one = one self.two = two display_one = ElementDataRepresentation('**HIDDEN**')
[docs]class OneParameterDisplayHiddenStringDemoElement(DemoElementBase): """ Sub-element to test with """ def __init__(self, one): self.one = one display_one = '**HIDDEN**'
[docs]class TwoParameterDisplayHiddenStringDemoElement(DemoElementBase): """ Sub-element to test with """ def __init__(self, one: int, two: DemoElementBase): self.one = one self.two = two display_one = '**HIDDEN**'
[docs]class BadDemoElement(DemoElementBase): """ Sub-element to test with """
[docs]class ContainerElementBase(DemoElementBase): """ A simple element that contains DemoElements """ def __init__(self, elements: Iterable[ProtocolElement]): self.elements = elements or []
[docs] def validate(self): """ Validate the contents of this element """ self.validate_contains(self.elements)
[docs]class AnythingContainerElement(ContainerElementBase): """ Container that may contain as many as it wants """
[docs]class NothingContainerElement(ContainerElementBase): """ Container that may contain as many as it wants """
[docs]class MinOneContainerElement(ContainerElementBase): """ Container that must contain at least one sub-element """
[docs]class MaxOneContainerElement(ContainerElementBase): """ Container that must contain at most one sub-element """
[docs]class ExactlyOneContainerElement(ContainerElementBase): """ Container that must contain exactly one sub-element """
[docs]class ExactlyTwoContainerElement(ContainerElementBase): """ Container that must contain exactly two sub-elements """
[docs]class HardCodedContainerElement(ContainerElementBase): """ Container that will have its _may_contain class property overwritten in the test """
AnythingContainerElement.add_may_contain(DemoElement) NothingContainerElement.add_may_contain(DemoElement, 0, 0) MinOneContainerElement.add_may_contain(DemoElement, 1) MaxOneContainerElement.add_may_contain(DemoElement, 0, 1) ExactlyOneContainerElement.add_may_contain(DemoElement, 1, 1) ExactlyTwoContainerElement.add_may_contain(DemoElement, 2, 2)
[docs]class ProtocolElementTestCase(unittest.TestCase):
[docs] def test_determine_class(self): element = DemoElement() suggested_class = element.determine_class(b'') self.assertIs(suggested_class, UnknownProtocolElement)
[docs]class UnknownProtocolElementTestCase(unittest.TestCase):
[docs] def test_load_from(self): length, element = ProtocolElement.parse(b'some data') self.assertEqual(length, 9) self.assertIsInstance(element, UnknownProtocolElement) self.assertEqual(element.data, b'some data')
[docs] def test_save(self): element = UnknownProtocolElement(b'some data') data = element.save() self.assertEqual(len(data), 9) self.assertEqual(data, b'some data')
# noinspection PyMethodMayBeStatic
[docs]class ElementOccurrenceTestCase(unittest.TestCase):
[docs] def test_bad(self): container = AnythingContainerElement(elements=[BadDemoElement()]) with self.assertRaisesRegex(ValueError, 'cannot contain BadDemoElement'): container.validate()
[docs] def test_class_based(self): container = AnythingContainerElement(elements=[]) self.assertTrue(container.may_contain(DemoElement)) self.assertFalse(container.may_contain(BadDemoElement))
[docs] def test_anything_0(self): container = AnythingContainerElement(elements=[]) container.validate()
[docs] def test_anything_1(self): container = AnythingContainerElement(elements=[DemoElement()]) container.validate()
[docs] def test_anything_2(self): container = AnythingContainerElement(elements=[DemoElement(), DemoElement()]) container.validate()
[docs] def test_nothing_0(self): container = NothingContainerElement(elements=[]) container.validate()
[docs] def test_nothing_1(self): container = NothingContainerElement(elements=[DemoElement()]) with self.assertRaisesRegex(ValueError, 'cannot contain DemoElement'): container.validate()
[docs] def test_nothing_2(self): container = NothingContainerElement(elements=[DemoElement(), DemoElement()]) with self.assertRaisesRegex(ValueError, 'cannot contain DemoElement'): container.validate()
[docs] def test_min_one_0(self): container = MinOneContainerElement(elements=[]) with self.assertRaisesRegex(ValueError, 'must contain at least 1 DemoElement'): container.validate()
[docs] def test_min_one_1(self): container = MinOneContainerElement(elements=[DemoElement()]) container.validate()
[docs] def test_min_one_2(self): container = MinOneContainerElement(elements=[DemoElement(), DemoElement()]) container.validate()
[docs] def test_max_one_0(self): container = MaxOneContainerElement(elements=[]) container.validate()
[docs] def test_max_one_1(self): container = MaxOneContainerElement(elements=[DemoElement()]) container.validate()
[docs] def test_max_one_2(self): container = MaxOneContainerElement(elements=[DemoElement(), DemoElement()]) with self.assertRaisesRegex(ValueError, 'may only contain 1 DemoElement'): container.validate()
[docs] def test_exactly_one_0(self): container = ExactlyOneContainerElement(elements=[]) with self.assertRaisesRegex(ValueError, 'must contain at least 1 DemoElement'): container.validate()
[docs] def test_exactly_one_1(self): container = ExactlyOneContainerElement(elements=[DemoElement()]) container.validate()
[docs] def test_exactly_one_2(self): container = ExactlyOneContainerElement(elements=[DemoElement(), DemoElement()]) with self.assertRaisesRegex(ValueError, 'only contain 1'): container.validate()
[docs] def test_exactly_two_1(self): container = ExactlyTwoContainerElement(elements=[DemoElement()]) with self.assertRaisesRegex(ValueError, 'must contain at least 2 DemoElements'): container.validate()
[docs] def test_exactly_two_2(self): container = ExactlyTwoContainerElement(elements=[DemoElement(), DemoElement()]) container.validate()
[docs] def test_exactly_two_3(self): container = ExactlyTwoContainerElement(elements=[DemoElement(), DemoElement(), DemoElement()]) with self.assertRaisesRegex(ValueError, 'may only contain 2 DemoElements'): container.validate()
[docs] def test_element_class_case_more_specific(self): HardCodedContainerElement._may_contain = OrderedDict() HardCodedContainerElement._may_contain[DemoElementBase] = (0, 0) HardCodedContainerElement._may_contain[DemoElement] = (0, 1) HardCodedContainerElement._may_contain[ExactlyOneContainerElement] = (0, 0) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(DemoElement) self.assertEqual(klass, DemoElement) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(DemoElement()) self.assertEqual(klass, DemoElement)
[docs] def test_element_class_case_less_specific(self): HardCodedContainerElement._may_contain = OrderedDict() HardCodedContainerElement._may_contain[ExactlyOneContainerElement] = (0, 0) HardCodedContainerElement._may_contain[DemoElement] = (0, 1) HardCodedContainerElement._may_contain[DemoElementBase] = (0, 0) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(DemoElement) self.assertEqual(klass, DemoElement) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(DemoElement()) self.assertEqual(klass, DemoElement)
[docs] def test_element_class_superclasses_more_specific(self): HardCodedContainerElement._may_contain = OrderedDict() HardCodedContainerElement._may_contain[ProtocolElement] = (0, 1) HardCodedContainerElement._may_contain[DemoElementBase] = (0, 1) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(DemoElement) self.assertEqual(klass, DemoElementBase) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(DemoElement()) self.assertEqual(klass, DemoElementBase)
[docs] def test_element_class_superclasses_less_specific(self): HardCodedContainerElement._may_contain = OrderedDict() HardCodedContainerElement._may_contain[DemoElementBase] = (0, 1) HardCodedContainerElement._may_contain[ProtocolElement] = (0, 1) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(DemoElement) self.assertEqual(klass, DemoElementBase) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(DemoElement()) self.assertEqual(klass, DemoElementBase)
[docs] def test_element_class_forbidden(self): HardCodedContainerElement._may_contain = OrderedDict() HardCodedContainerElement._may_contain[DemoElementBase] = (0, 1) HardCodedContainerElement._may_contain[DemoElement] = (0, 0) HardCodedContainerElement._may_contain[ExactlyOneContainerElement] = (0, 1) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(DemoElement) self.assertIsNone(klass) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(DemoElement()) self.assertIsNone(klass)
[docs] def test_element_class_missing(self): HardCodedContainerElement._may_contain = OrderedDict() HardCodedContainerElement._may_contain[DemoElementBase] = (0, 1) HardCodedContainerElement._may_contain[DemoElement] = (0, 0) HardCodedContainerElement._may_contain[ExactlyOneContainerElement] = (0, 1) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(object) self.assertIsNone(klass) container = HardCodedContainerElement(elements=[]) klass = container.get_element_class(object()) self.assertIsNone(klass)
[docs] def test_compare(self): container1 = AnythingContainerElement(elements=[DemoElement(), DemoElement()]) container2 = AnythingContainerElement(elements=[DemoElement(), DemoElement()]) container3 = AnythingContainerElement(elements=[DemoElement(), BadDemoElement()]) self.assertEqual(container1, container2) self.assertNotEqual(container1, container3)
[docs] def test_repr(self): element = TwoParameterDemoElement(1608, DemoElement()) self.assertEqual(repr(element), "TwoParameterDemoElement(one=1608, two=DemoElement())")
[docs] def test_str_no_parameters(self): element = DemoElement() self.assertEqual(str(element), "DemoElement()")
[docs] def test_str_one_parameter(self): element = OneParameterDemoElement(DemoElement()) self.assertEqual(str(element), "OneParameterDemoElement(one=DemoElement())") element = OneParameterDemoElement(one=DemoElement()) self.assertEqual(str(element), "OneParameterDemoElement(one=DemoElement())") element = OneParameterDemoElement(one='text') self.assertEqual(str(element), "OneParameterDemoElement(one='text')") element = OneParameterDemoElement(one=TwoParameterDemoElement(1608, DemoElement())) self.assertEqual(str(element), "OneParameterDemoElement(\n" " one=TwoParameterDemoElement(\n" " one=1608,\n" " two=DemoElement(),\n" " )\n" ")")
[docs] def test_str_two_parameters(self): element = TwoParameterDemoElement(1608, DemoElement()) self.assertEqual(str(element), "TwoParameterDemoElement(\n" " one=1608,\n" " two=DemoElement(),\n" ")")
[docs] def test_str_three_parameters(self): element = ThreeParameterDemoElement(1608, 'Something', [ DemoElement(), OneParameterDemoElement(DemoElement()), TwoParameterDemoElement(1608, TwoParameterDemoElement(1608, DemoElement())) ]) self.assertEqual(str(element), "ThreeParameterDemoElement(\n" " one=1608,\n" " two='Something',\n" " three=[\n" " DemoElement(),\n" " OneParameterDemoElement(one=DemoElement()),\n" " TwoParameterDemoElement(\n" " one=1608,\n" " two=TwoParameterDemoElement(\n" " one=1608,\n" " two=DemoElement(),\n" " ),\n" " ),\n" " ],\n" ")")
[docs] def test_str_one_parameter_display(self): element = OneParameterDisplayDemoElement(DemoElement()) self.assertEqual(str(element), "OneParameterDisplayDemoElement(one=**DemoElement()**)") element = OneParameterDisplayDemoElement(one=DemoElement()) self.assertEqual(str(element), "OneParameterDisplayDemoElement(one=**DemoElement()**)") element = OneParameterDisplayDemoElement(one=TwoParameterDemoElement(1608, DemoElement())) self.assertEqual(str(element), "OneParameterDisplayDemoElement(\n" " one=**TwoParameterDemoElement(\n" " one=1608,\n" " two=DemoElement(),\n" " )**\n" ")")
[docs] def test_str_two_parameters_display(self): element = TwoParameterDisplayDemoElement(1608, DemoElement()) self.assertEqual(str(element), "TwoParameterDisplayDemoElement(\n" " one=**1608**,\n" " two=DemoElement(),\n" ")")
[docs] def test_str_one_parameter_display_hidden(self): element = OneParameterDisplayHiddenDemoElement(DemoElement()) self.assertEqual(str(element), "OneParameterDisplayHiddenDemoElement(one=**HIDDEN**)") element = OneParameterDisplayHiddenDemoElement(one=DemoElement()) self.assertEqual(str(element), "OneParameterDisplayHiddenDemoElement(one=**HIDDEN**)")
[docs] def test_str_two_parameters_display_hidden(self): element = TwoParameterDisplayHiddenDemoElement(1608, DemoElement()) self.assertEqual(str(element), "TwoParameterDisplayHiddenDemoElement(\n" " one=**HIDDEN**,\n" " two=DemoElement(),\n" ")")
[docs] def test_str_one_parameter_display_hidden_string(self): element = OneParameterDisplayHiddenStringDemoElement(DemoElement()) self.assertEqual(str(element), "OneParameterDisplayHiddenStringDemoElement(one='**HIDDEN**')") element = OneParameterDisplayHiddenStringDemoElement(one=DemoElement()) self.assertEqual(str(element), "OneParameterDisplayHiddenStringDemoElement(one='**HIDDEN**')")
[docs] def test_str_two_parameters_display_hidden_string(self): element = TwoParameterDisplayHiddenStringDemoElement(1608, DemoElement()) self.assertEqual(str(element), "TwoParameterDisplayHiddenStringDemoElement(\n" " one='**HIDDEN**',\n" " two=DemoElement(),\n" ")")
[docs]class JSONEncodingTestCase(unittest.TestCase):
[docs] def test_str_no_parameters(self): element = DemoElement() self.assertEqual(json.dumps(element, cls=JSONProtocolElementEncoder), '{"DemoElement": {}}')
[docs] def test_str_one_parameter(self): element = OneParameterDemoElement(DemoElement()) self.assertEqual(json.dumps(element, cls=JSONProtocolElementEncoder), '{"OneParameterDemoElement": {"one": {"DemoElement": {}}}}') element = OneParameterDemoElement(one=DemoElement()) self.assertEqual(json.dumps(element, cls=JSONProtocolElementEncoder), '{"OneParameterDemoElement": {"one": {"DemoElement": {}}}}') element = OneParameterDemoElement(one=TwoParameterDemoElement(1608, DemoElement())) self.assertEqual(json.dumps(element, cls=JSONProtocolElementEncoder), '{"OneParameterDemoElement": {"one": {"TwoParameterDemoElement": ' '{"one": 1608, "two": {"DemoElement": {}}}' '}}}') element = OneParameterDemoElement(IPv6Address('2001:0db8::0001')) self.assertEqual(json.dumps(element, cls=JSONProtocolElementEncoder), '{"OneParameterDemoElement": {"one": "2001:db8::1"}}') element = OneParameterDemoElement(b'Printable') self.assertEqual(json.dumps(element, cls=JSONProtocolElementEncoder), '{"OneParameterDemoElement": {"one": "Printable"}}') # Test with non-printable ASCII element = OneParameterDemoElement(bytes.fromhex('012345')) self.assertEqual(json.dumps(element, cls=JSONProtocolElementEncoder), '{"OneParameterDemoElement": {"one": "hex:012345"}}') # Test with non-ASCII element = OneParameterDemoElement(bytes.fromhex('e0')) self.assertEqual(json.dumps(element, cls=JSONProtocolElementEncoder), '{"OneParameterDemoElement": {"one": "hex:e0"}}') element = OneParameterDemoElement(object()) with self.assertRaisesRegex(TypeError, 'not JSON serializable'): json.dumps(element, cls=JSONProtocolElementEncoder)
[docs] def test_str_two_parameters(self): element = TwoParameterDemoElement(1608, DemoElement()) self.assertEqual(json.dumps(element, cls=JSONProtocolElementEncoder), '{"TwoParameterDemoElement": ' '{"one": 1608, "two": {"DemoElement": {}}}' '}')
[docs] def test_str_three_parameters(self): element = ThreeParameterDemoElement(1608, 'Something', [ DemoElement(), OneParameterDemoElement(DemoElement()), TwoParameterDemoElement(1608, TwoParameterDemoElement(1608, DemoElement())) ]) self.assertEqual(json.dumps(element, cls=JSONProtocolElementEncoder), '{"ThreeParameterDemoElement": ' '{"one": 1608, "two": "Something", "three": [' '{"DemoElement": {}}, ' '{"OneParameterDemoElement": ' '{"one": {"DemoElement": {}}}' '}, ' '{"TwoParameterDemoElement": ' '{"one": 1608, "two": {"TwoParameterDemoElement": ' '{"one": 1608, "two": {"DemoElement": {}}}' '}}}' ']}' '}')
if __name__ == '__main__': # pragma: no cover unittest.main()