171 lines
5.1 KiB
Python
171 lines
5.1 KiB
Python
# dabmodel (c) by chacha
|
|
#
|
|
# dabmodel is licensed under a
|
|
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
|
|
#
|
|
# You should have received a copy of the license along with this
|
|
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
|
|
|
|
import unittest
|
|
from os import chdir
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
print(__name__)
|
|
print(__package__)
|
|
|
|
from src import dabmodel as dm
|
|
from typing import Annotated, Any, Optional
|
|
from uuid import uuid4
|
|
import json
|
|
from uuid import UUID
|
|
from datetime import datetime
|
|
|
|
testdir_path = Path(__file__).parent.resolve()
|
|
chdir(testdir_path.parent.resolve())
|
|
|
|
"""
|
|
class UUIDEncoder(json.JSONEncoder):
|
|
def default(self, obj):
|
|
if isinstance(obj, UUID):
|
|
# if the obj is uuid, we simply return the value of uuid
|
|
return obj.hex
|
|
elif isinstance(obj, datetime):
|
|
return str(obj)
|
|
return json.JSONEncoder.default(self, obj)
|
|
"""
|
|
from typing import Optional
|
|
|
|
from pydantic import ValidationError,StrictInt,StrictStr
|
|
|
|
|
|
|
|
# ---------------- Basic root appliance with one feature ----------------
|
|
|
|
class Router(dm.BaseAppliance):
|
|
class Features:
|
|
class Network(dm.BaseFeature):
|
|
mtu: StrictInt = 1500
|
|
class Defaults:
|
|
template_id = "00000000-0000-4000-8000-000000000001"
|
|
template_short_name = "net"
|
|
|
|
|
|
class Defaults:
|
|
template_id = "11111111-1111-4111-8111-111111111111"
|
|
template_short_name = "router"
|
|
class Network:
|
|
enabled = True
|
|
mtu = 9000
|
|
|
|
|
|
# ---------------- Subclass adds fields and features (allowed) ----------------
|
|
|
|
class RouterPlus(Router):
|
|
model_name: StrictStr = "plus" # new non-feature field (allowed)
|
|
|
|
class Features:
|
|
class Firewall(dm.BaseFeature):
|
|
enabled_by_default: bool = True
|
|
class Defaults:
|
|
template_id = "22222222-2222-4222-8222-222222222222"
|
|
template_short_name = "fw"
|
|
|
|
class Defaults:
|
|
class Firewall:
|
|
enabled = True
|
|
enabled_by_default = True
|
|
|
|
|
|
# ---------------- Subclass tweaks values only ----------------
|
|
|
|
class RouterLite(RouterPlus):
|
|
class Defaults:
|
|
template_short_name = "router-lite"
|
|
model_name = "lite"
|
|
class Network:
|
|
enabled = False # allowed: disable inherited feature
|
|
class Firewall:
|
|
enabled = True
|
|
enabled_by_default = False
|
|
|
|
|
|
# ---------------- Negative: remove parent feature ----------------
|
|
|
|
def _make_bad_remove_feature():
|
|
class Bad(RouterPlus):
|
|
# Attempt to remove Network by redefining inner Features without it
|
|
class Features:
|
|
class Firewall(dm.BaseFeature):
|
|
enabled_by_default: bool = True
|
|
pass
|
|
return Bad
|
|
|
|
|
|
# ---------------- Negative: change type of parent field ----------------
|
|
|
|
def _make_bad_change_type():
|
|
class Bad(Router):
|
|
cpu_cnt: int = 2 # lose StrictInt (type change forbidden)
|
|
return Bad
|
|
|
|
|
|
# ---------------- Negative: add new feature without declaring in Features ----------------
|
|
|
|
def _make_bad_undeclared_feature():
|
|
class Bad(Router):
|
|
# Inject an extra feature field not present in this class's `Features`
|
|
class Features:
|
|
class Network(dm.BaseFeature):
|
|
mtu: StrictInt = 1500
|
|
class Extra(dm.BaseFeature):
|
|
foo: StrictInt = 1
|
|
Extra: Optional[Extra] = Extra()
|
|
return Bad
|
|
|
|
|
|
# ---------------- Tests ----------------
|
|
|
|
class TestDabModelV2(unittest.TestCase):
|
|
def test_root_defaults_and_feature(self):
|
|
a = Router(dabinst_short_name="r1")
|
|
self.assertEqual(a.template_short_name, "router")
|
|
self.assertIsNotNone(a.Network)
|
|
self.assertEqual(a.Network.mtu, 9000)
|
|
|
|
def test_subclass_adds_field_and_feature(self):
|
|
a = RouterPlus(dabinst_short_name="rp1")
|
|
self.assertEqual(a.model_name, "plus")
|
|
self.assertIsNotNone(a.Network)
|
|
self.assertIsNotNone(a.Firewall)
|
|
self.assertTrue(a.Firewall.enabled_by_default)
|
|
|
|
def test_subclass_value_overrides(self):
|
|
a = RouterLite(dabinst_short_name="rl1")
|
|
self.assertEqual(a.template_short_name, "router-lite")
|
|
self.assertEqual(a.model_name, "lite")
|
|
self.assertIsNone(a.Network) # disabled
|
|
self.assertIsNotNone(a.Firewall)
|
|
self.assertFalse(a.Firewall.enabled_by_default)
|
|
|
|
def test_user_input_overrides(self):
|
|
a = Router(dabinst_short_name="u1", Network={'mtu': 4096})
|
|
self.assertEqual(a.Network.mtu, 4096)
|
|
b = RouterPlus(dabinst_short_name="u2", Firewall={'enabled': False})
|
|
self.assertIsNone(b.Firewall)
|
|
|
|
def test_cannot_remove_parent_feature(self):
|
|
with self.assertRaises(TypeError):
|
|
_ = _make_bad_remove_feature()
|
|
|
|
def test_cannot_change_parent_field_type(self):
|
|
with self.assertRaises(TypeError):
|
|
_ = _make_bad_change_type()
|
|
|
|
def test_new_feature_must_be_declared_in_features(self):
|
|
with self.assertRaises(ValidationError):
|
|
_ = _make_bad_undeclared_feature()
|
|
|
|
|