From a2aac6e49a96fd6cd0420dc53d6ea119144e23c7 Mon Sep 17 00:00:00 2001
From: Pelle Koster <pelle.koster@geant.org>
Date: Tue, 4 Feb 2025 11:03:21 +0100
Subject: [PATCH] Speed up test by implementing minimal jsonschema validation

---
 .../interface_stats/vendors/nokia.py          |  4 +-
 test/interface_stats/conftest.py              | 40 +++++++++++++++++++
 test/interface_stats/test_interface_stats.py  |  6 +--
 test/interface_stats/test_juniper.py          | 18 ++++-----
 test/interface_stats/test_nokia.py            | 13 +++---
 5 files changed, 60 insertions(+), 21 deletions(-)

diff --git a/brian_polling_manager/interface_stats/vendors/nokia.py b/brian_polling_manager/interface_stats/vendors/nokia.py
index a8879fb..d458ed4 100644
--- a/brian_polling_manager/interface_stats/vendors/nokia.py
+++ b/brian_polling_manager/interface_stats/vendors/nokia.py
@@ -19,7 +19,7 @@ NCCLIENT_PARAMS = {
 }
 
 INTERFACE_COUNTERS = {
-    # Counters must be floats to be compatible with influx writing to the same
+    # Counters must be floats to be compatible with sensu writing to the same
     # measurement. cf https://github.com/sensu/sensu-go/issues/2213
     "__defaults__": {"transform": float, "required": False},
     "name": {
@@ -51,7 +51,7 @@ INTERFACE_COUNTERS = {
 }
 
 INTERFACE_COUNTERS_ALT = {
-    # Counters must be floats to be compatible with influx writing to the same
+    # Counters must be floats to be compatible with sensu writing to the same
     # measurement. cf https://github.com/sensu/sensu-go/issues/2213
     "__defaults__": {"transform": float, "required": False},
     "name": {
diff --git a/test/interface_stats/conftest.py b/test/interface_stats/conftest.py
index 9958582..6edb22a 100644
--- a/test/interface_stats/conftest.py
+++ b/test/interface_stats/conftest.py
@@ -97,3 +97,43 @@ def nokia_router_fqdn(request):
     # do a pytest.mark.parametrize instead but we'd have to redefine NOKIA_ROUTERS in
     # the test module or import from this conftest directly
     return request.param
+
+
+@pytest.fixture
+def schemavalidate():
+    """Minimal jsonschema validation. Just enough to validate the various interface
+    point dictionaries. Does not resolve $ref. Custom implementation because jsonschema
+    is slooooooow"""
+
+    def _validate(obj, schema):
+        type_ = schema.get("type")
+
+        if type_ == "object":
+            assert isinstance(obj, dict)
+            assert set(schema.get("required", [])).issubset(obj.keys())
+
+            additionalProperties = schema.get("additionalProperties", True)
+
+            properties = schema.get("properties", {})
+            if not additionalProperties:
+                assert set(obj).issubset(properties)
+
+            for key, val in obj.items():
+                if key not in properties:
+                    if isinstance(additionalProperties, dict):
+                        _validate(val, additionalProperties)
+                    continue
+                subschema = properties[key]
+                if "type" not in subschema:
+                    continue
+                _validate(val, subschema)
+        elif type_ == "number":
+            assert isinstance(obj, (float, int))
+        elif type_ == "integer":
+            assert isinstance(obj, int)
+        elif type_ == "string":
+            assert isinstance(obj, str)
+        else:
+            raise ValueError(f"unsuppported type '{type_}'")
+
+    return _validate
diff --git a/test/interface_stats/test_interface_stats.py b/test/interface_stats/test_interface_stats.py
index c5ada9c..169a09a 100644
--- a/test/interface_stats/test_interface_stats.py
+++ b/test/interface_stats/test_interface_stats.py
@@ -2,7 +2,6 @@ from datetime import datetime
 from unittest.mock import Mock, call, patch
 
 from brian_polling_manager import influx
-import jsonschema
 import pytest
 from brian_polling_manager.interface_stats import cli
 from brian_polling_manager.interface_stats.cli import PointGroup, Vendor
@@ -169,8 +168,9 @@ def test_no_error_point_counters():
 
 
 @patch.object(cli.OutputMethod, "write_points")
+@pytest.mark.usefixtures("mocked_get_netconf")
 def test_main_for_all_juniper_routers(
-    write_points, mocked_get_netconf, juniper_router_fqdn, juniper_inventory
+    write_points, juniper_router_fqdn, juniper_inventory, schemavalidate
 ):
     config = {
         "juniper": {"some": "params"},
@@ -187,7 +187,7 @@ def test_main_for_all_juniper_routers(
         assert points
         for point in points:
             total_points += 1
-            jsonschema.validate(point, influx.INFLUX_POINT)
+            schemavalidate(point, influx.INFLUX_POINT)
             assert point["fields"]  # must contain at least one field
 
     write_points.side_effect = validate
diff --git a/test/interface_stats/test_juniper.py b/test/interface_stats/test_juniper.py
index f6ecca5..1b4745f 100644
--- a/test/interface_stats/test_juniper.py
+++ b/test/interface_stats/test_juniper.py
@@ -1,7 +1,6 @@
 from datetime import datetime
 from unittest.mock import call, patch
 
-import jsonschema
 import pytest
 from brian_polling_manager import influx
 from brian_polling_manager.interface_stats.vendors import common, juniper
@@ -176,9 +175,10 @@ def test_juniper_router_docs_do_not_generate_errors(
 
 
 def test_validate_interface_counters_and_influx_points_for_all_juniper_routers(
-    juniper_router_fqdn, get_netconf, juniper_inventory
+    juniper_router_fqdn, get_netconf, juniper_inventory, schemavalidate
 ):
     doc = get_netconf(juniper_router_fqdn)
+
     interfaces = list(
         juniper.interface_counters(
             doc, interfaces=juniper_inventory[juniper_router_fqdn]
@@ -186,7 +186,8 @@ def test_validate_interface_counters_and_influx_points_for_all_juniper_routers(
     )
     assert interfaces
     for ifc in interfaces:
-        jsonschema.validate(ifc, common.INTERFACE_COUNTER_SCHEMA)
+        schemavalidate(ifc, common.INTERFACE_COUNTER_SCHEMA)
+        schemavalidate(ifc["brian"], common.BRIAN_POINT_FIELDS_SCHEMA)
 
     bpoints = list(
         common.brian_points(
@@ -198,10 +199,11 @@ def test_validate_interface_counters_and_influx_points_for_all_juniper_routers(
     )
     assert bpoints
     for point in bpoints:
-        jsonschema.validate(point, influx.INFLUX_POINT)
-        jsonschema.validate(point["fields"], common.BRIAN_POINT_FIELDS_SCHEMA)
+        schemavalidate(point, influx.INFLUX_POINT)
+        schemavalidate(point["fields"], common.BRIAN_POINT_FIELDS_SCHEMA)
         for value in point['fields'].values():
             assert isinstance(value, float)
+
     epoints = list(
         common.error_points(
             juniper_router_fqdn,
@@ -212,10 +214,8 @@ def test_validate_interface_counters_and_influx_points_for_all_juniper_routers(
     )
     assert epoints
     for point in epoints:
-        jsonschema.validate(point, influx.INFLUX_POINT)
-        jsonschema.validate(point["fields"], common.ERROR_POINT_FIELDS_SCHEMA)
-        for value in point['fields'].values():
-            assert isinstance(value, int)
+        schemavalidate(point, influx.INFLUX_POINT)
+        schemavalidate(point["fields"], common.ERROR_POINT_FIELDS_SCHEMA)
 
 
 class TestGetJuniperNetConf:
diff --git a/test/interface_stats/test_nokia.py b/test/interface_stats/test_nokia.py
index 6bffba1..1b368d1 100644
--- a/test/interface_stats/test_nokia.py
+++ b/test/interface_stats/test_nokia.py
@@ -2,7 +2,6 @@ from datetime import datetime
 from unittest.mock import call, patch
 from brian_polling_manager import influx
 from brian_polling_manager.interface_stats.vendors import common, nokia
-import jsonschema
 import pytest
 from lxml import etree
 
@@ -183,13 +182,13 @@ def test_nokia_router_docs_do_not_generate_errors(
 
 
 def test_validate_interface_counters_and_influx_points_for_all_nokia_routers(
-    nokia_router_fqdn, get_netconf
+    nokia_router_fqdn, get_netconf, schemavalidate
 ):
     doc = get_netconf(nokia_router_fqdn)
     interfaces = list(nokia.interface_counters(doc))
     assert interfaces
     for ifc in interfaces:
-        jsonschema.validate(ifc, common.INTERFACE_COUNTER_SCHEMA)
+        schemavalidate(ifc, common.INTERFACE_COUNTER_SCHEMA)
 
     bpoints = list(
         common.brian_points(
@@ -201,8 +200,8 @@ def test_validate_interface_counters_and_influx_points_for_all_nokia_routers(
     )
     assert bpoints
     for point in bpoints:
-        jsonschema.validate(point, influx.INFLUX_POINT)
-        jsonschema.validate(point["fields"], common.BRIAN_POINT_FIELDS_SCHEMA)
+        schemavalidate(point, influx.INFLUX_POINT)
+        schemavalidate(point["fields"], common.BRIAN_POINT_FIELDS_SCHEMA)
 
     epoints = list(
         common.error_points(
@@ -214,8 +213,8 @@ def test_validate_interface_counters_and_influx_points_for_all_nokia_routers(
     )
     assert epoints
     for point in epoints:
-        jsonschema.validate(point, influx.INFLUX_POINT)
-        jsonschema.validate(point["fields"], common.ERROR_POINT_FIELDS_SCHEMA)
+        schemavalidate(point, influx.INFLUX_POINT)
+        schemavalidate(point["fields"], common.ERROR_POINT_FIELDS_SCHEMA)
 
 
 def test_processes_specific_interfaces(get_netconf, caplog):
-- 
GitLab