From b9d6ebde4802a285fad0668dca9c3960b37256f2 Mon Sep 17 00:00:00 2001
From: Nikolay Stanchev <ns17@it-innovation.soton.ac.uk>
Date: Wed, 11 Jul 2018 09:57:34 +0100
Subject: [PATCH] Improves tests for the CLMC service Graph API

---
 src/service/clmcservice/graphapi/tests.py     | 180 ++++++++----------
 src/service/clmcservice/graphapi/utilities.py |   9 +-
 2 files changed, 92 insertions(+), 97 deletions(-)

diff --git a/src/service/clmcservice/graphapi/tests.py b/src/service/clmcservice/graphapi/tests.py
index c818a3a..566626e 100644
--- a/src/service/clmcservice/graphapi/tests.py
+++ b/src/service/clmcservice/graphapi/tests.py
@@ -51,66 +51,65 @@ class TestGraphAPI(object):
 
         testing.tearDown()
 
-    def test_build(self, db_testing_data):
+    @pytest.mark.parametrize("body, from_timestamp, to_timestamp, error_msg", [
+        (None, None, None, "A bad request error must have been raised in case of missing request body."),
+        ('{}', 12341412, 1234897, "A bad request error must have been raised in case of invalid request body."),
+        ('{"database": "CLMCMetrics", "retention_policy": "autogen", "service_function_chain_instance": "sfc_i"}', 12341412, 1234897, "A bad request error must have been raised in case of invalid request body."),
+        ('{"database": "TestInfluxDB", "retention_policy": "autogen", "service_function_chain_instance": "sfc_1", "service_functions": "{invalid_json}"}', 1528386860, 1528389860, "A bad request error must have been raised in case of invalid request body."),
+        ('{"database": "TestInfluxDB", "retention_policy": "autogen", "service_function_chain_instance": "sfc_1", "service_functions": ["nginx", "minio"]}', 1528386860, 1528389860, "A bad request error must have been raised in case of invalid request body."),
+        ('{"retention_policy": "autogen", "service_function_chain_instance": "sfc_1", "service_functions": {"nginx": {"measurement_name": "nginx", "response_time_field": "mean(avg_processing_time)"}, "minio": {"measurement_name": "minio_http", "response_time_field": "mean(total_processing_time)/mean(total_requests_count)"}, "apache": {"measurement_name": "apache", "response_time_field": "mean(avg_processing_time)"}}}',
+         1528386860, 1528389860, "A bad request error must have been raised in case of missing database value in the request body"),
+        ('{"database": "TestInfluxDB", "retention_policy": "autogen", "service_function_chain_instance": "sfc_id", "service_functions": {"nginx": {"measurement_name": "nginx", "response_time_field": "mean(avg_processing_time)"}, "minio": {"measurement_name": "minio_http", "response_time_field": "mean(total_processing_time)/mean(total_requests_count)"}, "apache": {"measurement_name": "apache", "response_time_field": "mean(avg_processing_time)"}}}',
+         1528386860, 1528389860, "A bad request error must have been raised in case of invalid sfc_i ID in the request body"),
+        ('{"database": "TestInfluxDB", "retention_policy": "autogen", "service_function_chain_instance": "testsfc1", "service_functions": {"nginx": {"measurement_name": "nginx", "response_time_field": "mean(avg_processing_time)"}, "minio": {"measurement_name": "minio_http", "response_time_field": "mean(total_processing_time)/mean(total_requests_count)"}}}',
+         1528386860, 1528389860, "A bad request error must have been raised in case of invalid sfc_i ID in the request body"),
+        ('{"database": "TestInfluxDB", "retention_policy": "autogen", "service_function_chain_instance": "sfc_1", "service_functions": {"nginx": {"measurement_name": "nginx", "response_time_field": "mean(avg_processing_time)"}, "minio": {"measurement_name": "minio_http", "response_time_field": "mean(total_processing_time)/mean(total_requests_count)"}, "apache": {"measurement_name": "apache", "response_time_field": "mean(avg_processing_time)"}}}',
+         "not a timestamp", "not a timestamp", "A bad request error must have been raised in case of invalid URL parameters."),
+        ('{"database": "TestInfluxDB", "retention_policy": "autogen", "service_function_chain_instance": "sfc_1", "service_functions": {"nginx": {"measurement_name": "nginx", "response_time_field": "mean(avg_processing_time)"}, "minio": {"measurement_name": "minio_http", "response_time_field": "mean(total_processing_time)/mean(total_requests_count)"}, "apache": {"measurement_name": "apache", "response_time_field": "mean(avg_processing_time)"}}}',
+         None, "not a timestamp", "A bad request error must have been raised in case of invalid URL parameters."),
+        ('{"database": "TestInfluxDB", "retention_policy": "autogen", "service_function_chain_instance": "sfc_1", "service_functions": {"nginx": {"measurement_name": "nginx", "response_time_field": "mean(avg_processing_time)"}, "minio": {"measurement_name": "minio_http", "response_time_field": "mean(total_processing_time)/mean(total_requests_count)"}, "apache": {"measurement_name": "apache", "response_time_field": "mean(avg_processing_time)"}}}',
+         2131212, None, "A bad request error must have been raised in case of invalid URL parameters."),
+
+    ])
+    def test_build_error_handling(self, body, from_timestamp, to_timestamp, error_msg):
         """
-        Tests the graph build API endpoint
+        Tests the error handling of the graph build API endpoint.
 
-        :param db_testing_data: pair of time stamps - the from-to range of the generated influx test data, test database name and the graph db client
+        :param body: body of the request to test
+        :param from_timestamp: the 'from' URL param
+        :param to_timestamp: the 'to' URL param
+        :param error_msg: the error message to pass in case of an error
         """
-
-        global graph_1_id, graph_2_id
-
-        from_timestamp, to_timestamp, test_db_name, graph_db = db_testing_data
-
-        dc_nodes = set([node["name"] for node in graph_db.nodes.match("ComputeNode")])
-        assert dc_nodes == set("DC" + str(i) for i in range(1, 7)), "Compute nodes must have been created by the db_testing_data fixture"
-
         request = testing.DummyRequest()
+        if body is not None:
+            request.body = body
         request.body = request.body.encode(request.charset)
+        if from_timestamp is not None:
+            request.params["from"] = from_timestamp
+        if to_timestamp is not None:
+            request.params["to"] = to_timestamp
         error_raised = False
         try:
             GraphAPI(request).build_temporal_graph()
         except HTTPBadRequest:
             error_raised = True
-        assert error_raised, "A bad request error must have been raised in case of missing request body."
+        assert error_raised, error_msg
 
-        body = dumps(dict(database=test_db_name, retention_policy="autogen", service_function_chain_instance="sfc_i"))
-        request = testing.DummyRequest()
-        request.params["from"] = 12341412
-        request.params["to"] = 12341412
-        request.body = body.encode(request.charset)
-        error_raised = False
-        try:
-            GraphAPI(request).build_temporal_graph()
-        except HTTPBadRequest:
-            error_raised = True
-        assert error_raised, "A bad request error must have been raised in case of invalid request body."
+    def test_build(self, db_testing_data):
+        """
+        Tests the graph build API endpoint
 
-        body = dumps(dict(database=test_db_name, retention_policy="autogen", service_function_chain_instance="sfc_1", service_functions="{invalid_json}"))
-        request = testing.DummyRequest()
-        request.params["from"] = 12341412
-        request.params["to"] = 12341412
-        request.body = body.encode(request.charset)
-        error_raised = False
-        try:
-            GraphAPI(request).build_temporal_graph()
-        except HTTPBadRequest:
-            error_raised = True
-        assert error_raised, "A bad request error must have been raised in case of invalid request body."
+        :param db_testing_data: pair of time stamps - the from-to range of the generated influx test data, test database name and the graph db client
+        """
 
-        service_functions = ["nginx", "minio"]
-        body = dumps(dict(database=test_db_name, retention_policy="autogen", service_function_chain_instance="sfc_1", service_functions=service_functions))
-        request = testing.DummyRequest()
-        request.params["from"] = 12341412
-        request.params["to"] = 12341412
-        request.body = body.encode(request.charset)
-        error_raised = False
-        try:
-            GraphAPI(request).build_temporal_graph()
-        except HTTPBadRequest:
-            error_raised = True
-        assert error_raised, "A bad request error must have been raised in case of invalid request body."
+        global graph_1_id, graph_2_id
 
+        from_timestamp, to_timestamp, test_db_name, graph_db = db_testing_data
+
+        dc_nodes = set([node["name"] for node in graph_db.nodes.match("ComputeNode")])
+        assert dc_nodes == set("DC" + str(i) for i in range(1, 7)), "Compute nodes must have been created by the db_testing_data fixture"
+
+        # test with invalid URL parameters naming
         service_functions = dict(nginx={"measurement_name": "nginx", "response_time_field": "mean(avg_processing_time)"},
                                  minio={"measurement_name": "minio_http", "response_time_field": "mean(total_processing_time)/mean(total_requests_count)"},
                                  apache={"measurement_name": "apache", "response_time_field": "mean(avg_processing_time)"})
@@ -126,36 +125,6 @@ class TestGraphAPI(object):
             error_raised = True
         assert error_raised, "A bad request error must have been raised in case of invalid URL parameters."
 
-        service_functions = dict(nginx={"measurement_name": "nginx", "response_time_field": "mean(avg_processing_time)"},
-                                 minio={"measurement_name": "minio_http", "response_time_field": "mean(total_processing_time)/mean(total_requests_count)"},
-                                 apache={"measurement_name": "apache", "response_time_field": "mean(avg_processing_time)"})
-        body = dumps(dict(database=test_db_name, retention_policy="autogen", service_function_chain_instance="sfc_1", service_functions=service_functions))
-        request = testing.DummyRequest()
-        request.params["from"] = "not a timestamp"
-        request.params["to"] = "not a timestamp"
-        request.body = body.encode(request.charset)
-        error_raised = False
-        try:
-            GraphAPI(request).build_temporal_graph()
-        except HTTPBadRequest:
-            error_raised = True
-        assert error_raised, "A bad request error must have been raised in case of invalid URL parameters."
-
-        service_functions = dict(nginx={"measurement_name": "nginx", "response_time_field": "mean(avg_processing_time)"},
-                                 minio={"measurement_name": "minio_http", "response_time_field": "mean(total_processing_time)/mean(total_requests_count)"})
-        build_json_body = dict(database=test_db_name, retention_policy="autogen", service_function_chain_instance="testsfc1", service_functions=service_functions)
-        body = dumps(build_json_body)
-        request = testing.DummyRequest()
-        request.params["from"] = from_timestamp
-        request.params["to"] = to_timestamp
-        request.body = body.encode(request.charset)
-        error_raised = False
-        try:
-            GraphAPI(request).build_temporal_graph()
-        except HTTPBadRequest:
-            error_raised = True
-        assert error_raised, "A bad request error must have been raised in case of invalid SFC ID."
-
         service_functions = dict(nginx={"measurement_name": "nginx", "response_time_field": "mean(avg_processing_time)"},
                                  minio={"measurement_name": "minio_http", "response_time_field": "mean(total_processing_time)/mean(total_requests_count)"})
         build_json_body = dict(database=test_db_name, retention_policy="autogen", service_function_chain_instance="test_sfc1_1", service_functions=service_functions)
@@ -290,6 +259,37 @@ class TestGraphAPI(object):
         assert set([node["name"] for node in graph_db.nodes.match("ServiceFunctionInstance")]) == {"nginx_1", "apache_1", "minio_1", "minio_2"}, "Service function instances must not be deleted."
         assert set([node["name"] for node in graph_db.nodes.match("ServiceFunction")]) == {"nginx", "minio", "apache"}, "Service functions must not be deleted"
 
+    @pytest.mark.parametrize("graph_id, endpoint, compute_node, error_type, error_msg", [
+        ('e8cd4768-47dd-48cd-9c74-7f8926ddbad8', None, None, HTTPBadRequest, "HTTP Bad Request must be thrown in case of missing or invalid url parameters"),
+        ('e8cd4768-47dd-48cd-9c74-7f8926ddbad8', None, "nginx", HTTPBadRequest, "HTTP Bad Request must be thrown in case of missing or invalid url parameters"),
+        ('e8cd4768-47dd-48cd-9c74-7f8926ddbad8', "nginx_1_ep1", None, HTTPBadRequest, "HTTP Bad Request must be thrown in case of missing or invalid url parameters"),
+        ('random-uuid', "nginx_1_ep1", "nginx", HTTPNotFound, "HTTP Not Found error must be thrown for an endpoint node with incorrect request ID"),
+        ('random-uuid', "minio_1_ep1", "minio", HTTPNotFound, "HTTP Not Found error must be thrown for an endpoint node with incorrect request ID"),
+    ])
+    def test_rtt_error_handling(self, graph_id, endpoint, compute_node, error_type, error_msg):
+        """
+        Tests the error handling of the graph round trip time API endpoint.
+
+        :param graph_id: the UUID of the subgraph
+        :param endpoint: endpoint ID
+        :param compute_node: compute node ID
+        :param error_type: error type to expect
+        :param error_msg: error message in case of a test failure
+        """
+
+        request = testing.DummyRequest()
+        request.matchdict["graph_id"] = graph_id
+        if endpoint is not None:
+            request.params["endpoint"] = endpoint
+        if compute_node is not None:
+            request.params["compute_node"] = compute_node
+        error_raised = False
+        try:
+            GraphAPI(request).run_rtt_query()
+        except error_type:
+            error_raised = True
+        assert error_raised, error_msg
+
     def test_rtt(self, db_testing_data):
         """
         Tests the rtt API endpoint of the Graph API
@@ -313,15 +313,6 @@ class TestGraphAPI(object):
         assert graph_subresponse.get("uuid") is not None, "Request UUID must be attached to the response."
         request_id = graph_subresponse["uuid"]
 
-        request = testing.DummyRequest()
-        request.matchdict["graph_id"] = request_id
-        error_raised = False
-        try:
-            GraphAPI(request).run_rtt_query()
-        except HTTPBadRequest:
-            error_raised = True
-        assert error_raised, "HTTP Bad Request must be thrown in case of missing or invalid url parameters"
-
         request = testing.DummyRequest()
         request.matchdict["graph_id"] = request_id
         request.params["endpoint"] = "nginx_1_ep1"
@@ -344,17 +335,6 @@ class TestGraphAPI(object):
             error_raised = True
         assert error_raised, "HTTP Not Found error must be thrown for non existing compute node"
 
-        request = testing.DummyRequest()
-        request.matchdict["graph_id"] = "random-uuid"
-        request.params["endpoint"] = "nginx_1_ep1"
-        request.params["compute_node"] = "DC0"
-        error_raised = False
-        try:
-            GraphAPI(request).run_rtt_query()
-        except HTTPNotFound:
-            error_raised = True
-        assert error_raised, "HTTP Not Found error must be thrown for an endpoint node with incorrect request ID"
-
         request = testing.DummyRequest()
         request.matchdict["graph_id"] = request_id
         request.params["endpoint"] = "apache_1_ep1"
@@ -408,6 +388,14 @@ class TestGraphAPI(object):
 
     @staticmethod
     def check_exist_relationship(relationships_tuple, graph, uuid):
+        """
+        Iterates through a tuple of relation ships and checks that each of those exists.
+
+        :param relationships_tuple: the tuple to iterate
+        :param graph: the graph object
+        :param uuid: the uuid of the request
+        """
+
         for relationship in relationships_tuple:
             from_node_name, from_node_type, to_node_name, to_node_type, relationship_type = relationship
             if from_node_type == "Endpoint":
diff --git a/src/service/clmcservice/graphapi/utilities.py b/src/service/clmcservice/graphapi/utilities.py
index 88e97b4..9680c17 100644
--- a/src/service/clmcservice/graphapi/utilities.py
+++ b/src/service/clmcservice/graphapi/utilities.py
@@ -71,7 +71,14 @@ def validate_json_queries_body(body):
     assert GRAPH_BUILD_QUERY_PARAMS == set(body.keys()), "Invalid JSON query document."
 
     sfc_i = body["service_function_chain_instance"]
-    assert len(sfc_i.split('_')) > 1, "Incorrect format of service function chain instance ID - use format <sfcID>_<instanceNum>"
+    sfc_i_subparts = sfc_i.split('_')
+    assert len(sfc_i_subparts) > 1, "Incorrect format of service function chain instance ID - use format <sfcID>_<instanceNum>"
+
+    # check the last part of the sfc_i ID is a number
+    try:
+        int(sfc_i_subparts[-1])
+    except ValueError:
+        assert False, "Incorrect format of service function chain instance ID - use format <sfcID>_<instanceNum>"
 
     assert type(body["service_functions"]) == dict, "The service function description should be represented with a dictionary."
 
-- 
GitLab