Add unit tests for List and get in API. 85/31485/1
authorakhilbatra898 <akhil.batra@research.iiit.ac.in>
Sat, 18 Mar 2017 08:46:26 +0000 (14:16 +0530)
committerYujun Zhang <zhang.yujunz@zte.com.cn>
Thu, 23 Mar 2017 07:30:11 +0000 (07:30 +0000)
 - refactor controllers
 - remove abspath and other irrelvant data in response
 - move fixtures
 - refactor decorators

JIRA: QTIP-226

Change-Id: I5fac5b1bc998da198098992e7ddb47ba49685f31
Signed-off-by: akhilbatra898 <akhil.batra@research.iiit.ac.in>
(cherry picked from commit bef693f40ad87170b7233b9fef62f2fd8abfc8d8)

qtip/api/__main__.py
qtip/api/controllers/common.py
qtip/api/controllers/metric.py
qtip/api/controllers/plan.py
qtip/api/controllers/qpi.py
qtip/api/swagger/swagger.yaml
tests/unit/api/conftest.py [new file with mode: 0644]
tests/unit/api/metric_controller_test.py [new file with mode: 0644]
tests/unit/api/plan_controller_test.py [new file with mode: 0644]
tests/unit/api/qpi_controller_test.py [new file with mode: 0644]

index 05d9231..381622a 100644 (file)
@@ -14,9 +14,14 @@ import os
 swagger_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'swagger/'))
 
 
-def main():
+def get_app():
     app = connexion.App(__name__, specification_dir=swagger_dir)
-    app.add_api('swagger.yaml', base_path='/v1.0')
+    app.add_api('swagger.yaml', base_path='/v1.0', strict_validation=True)
+    return app
+
+
+def main():
+    app = get_app()
     app.run(host="0.0.0.0", port=5000)
 
 
index 6cabbc7..96101f8 100644 (file)
@@ -5,15 +5,22 @@ import connexion
 from qtip.base import error
 
 
-def get_one_exceptions(resource):
+def check_endpoint_for_error(resource, operation=None):
     def _decorator(func):
-        def _execute(name):
+        def _execute(name=None):
             try:
                 return func(name), httplib.OK
             except error.NotFoundError:
                 return connexion.problem(
                     httplib.NOT_FOUND,
-                    '{} Not Found'.format(resource),
-                    'Requested {} `{}` not found.'.format(resource, name))
+                    '{} not found'.format(resource),
+                    'Requested {} `{}` not found.'
+                    .format(resource.lower(), name))
+            except error.ToBeDoneError:
+                return connexion.problem(
+                    httplib.NOT_IMPLEMENTED,
+                    '{} handler not implemented'.format(operation),
+                    'Requested operation `{}` on {} not implemented.'
+                    .format(operation.lower(), resource.lower()))
         return _execute
     return _decorator
index dd4c8ac..96cd985 100644 (file)
@@ -14,13 +14,12 @@ from qtip.loader import metric
 
 
 def list_metrics():
-    metric_list = list(metric.MetricSpec.list_all())
-    return metric_list, httplib.OK
+    metrics = list(metric.MetricSpec.list_all())
+    metrics_by_name = [m['name'] for m in metrics]
+    return {'metrics': metrics_by_name}, httplib.OK
 
 
-@common.get_one_exceptions(resource='metric')
+@common.check_endpoint_for_error(resource='Metric')
 def get_metric(name):
-        metric_spec = metric.MetricSpec(name)
-        return {'name': metric_spec.name,
-                'abspath': metric_spec.abspath,
-                'content': metric_spec.content}
+    metric_spec = metric.MetricSpec(name)
+    return metric_spec.content
index 93836a3..0059387 100644 (file)
@@ -9,30 +9,23 @@
 
 import httplib
 
-import connexion
-
+from qtip.api.controllers import common
 from qtip.base import error
 from qtip.loader import plan
 
 
 def list_plans():
-    plan_list = list(plan.Plan.list_all())
-    return plan_list, httplib.OK
+    plans = list(plan.Plan.list_all())
+    plans_by_name = [p['name'] for p in plans]
+    return {'plans': plans_by_name}, httplib.OK
 
 
+@common.check_endpoint_for_error(resource='Plan')
 def get_plan(name):
-    try:
-        plan_spec = plan.Plan(name)
-        return {'name': plan_spec.name,
-                'abspath': plan_spec.abspath,
-                'content': plan_spec.content}, httplib.OK
-    except error.NotFoundError:
-        return connexion.problem(httplib.NOT_FOUND,
-                                 'Plan Not Found',
-                                 'requested plan `' + name + '` not found.')
+    plan_spec = plan.Plan(name)
+    return plan_spec.content
 
 
+@common.check_endpoint_for_error(resource='Plan', operation='Run')
 def run_plan(name, action="run"):
-    return connexion.problem(httplib.NOT_IMPLEMENTED,
-                             'Run a plan',
-                             'Plan runner not implemented')
+    raise error.ToBeDoneError('run_plan', 'plan')
index 3c4dd71..af08a82 100644 (file)
@@ -9,24 +9,17 @@
 
 import httplib
 
-import connexion
-
-from qtip.base import error
+from qtip.api.controllers import common
 from qtip.loader import qpi
 
 
 def list_qpis():
-    qpi_spec_list = list(qpi.QPISpec.list_all())
-    return qpi_spec_list, httplib.OK
+    qpi_specs = list(qpi.QPISpec.list_all())
+    qpis_by_name = [q['name'] for q in qpi_specs]
+    return {'qpis': qpis_by_name}, httplib.OK
 
 
+@common.check_endpoint_for_error(resource='QPI')
 def get_qpi(name):
-    try:
-        qpi_spec = qpi.QPISpec(name)
-        return {'name': qpi_spec.name,
-                'abspath': qpi_spec.abspath,
-                'content': qpi_spec.content}, httplib.OK
-    except error.NotFoundError:
-        return connexion.problem(httplib.NOT_FOUND,
-                                 'QPI Not Found',
-                                 'Requested QPI Spec `' + name + '` not found.')
+    qpi_spec = qpi.QPISpec(name)
+    return qpi_spec.content
index fb10317..842c0b7 100644 (file)
@@ -212,7 +212,7 @@ paths:
           schema:
             $ref: '#/definitions/Error'
 definitions:
-  PlanContent:
+  Plan:
     type: object
     required:
       - name
@@ -232,23 +232,13 @@ definitions:
   Plans:
     type: object
     required:
-      - name
-      - abspath
+      - plans
     properties:
-      name:
-        type: string
-      abspath:
-        type: string
-  Plan:
-    allOf:
-    - $ref: '#/definitions/Plans'
-    - type: object
-    - required:
-      - content
-      properties:
-        content:
-          $ref: '#/definitions/PlanContent'
-  MetricContent:
+      plans:
+        type: array
+        items:
+          type: string
+  Metric:
     type: object
     required:
       - name
@@ -268,22 +258,13 @@ definitions:
   Metrics:
     type: object
     required:
-      - name
-      - abspath
+      - metrics
     properties:
-      name:
-        type: string
-      abspath:
-        type: string
-  Metric:
-    allOf:
-    - $ref: '#/definitions/Metrics'
-    - required:
-      - content
-      properties:
-        content:
-          $ref: '#/definitions/MetricContent'
-  QPIContent:
+      metrics:
+        type: array
+        items:
+          type: string
+  QPI:
     type: object
     required:
       - name
@@ -301,21 +282,12 @@ definitions:
   QPIs:
     type: object
     required:
-      - name
-      - abspath
+      - qpis
     properties:
-      name:
-        type: string
-      abspath:
-        type: string
-  QPI:
-    allOf:
-      - $ref: '#/definitions/QPIs'
-      - required:
-        - content
-        properties:
-          content:
-            $ref: '#/definitions/QPIContent'
+      qpis:
+        type: array
+        items:
+          type: string
   Error:
     type: object
     properties:
diff --git a/tests/unit/api/conftest.py b/tests/unit/api/conftest.py
new file mode 100644 (file)
index 0000000..23a3be8
--- /dev/null
@@ -0,0 +1,22 @@
+##############################################################################
+# Copyright (c) 2017 akhil.batra@research.iiit.ac.in and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import pytest
+
+from qtip.api import __main__
+
+
+@pytest.fixture(scope="session")
+def app():
+    return __main__.get_app().app
+
+
+@pytest.fixture(scope="session")
+def app_client(app):
+    return app.test_client()
diff --git a/tests/unit/api/metric_controller_test.py b/tests/unit/api/metric_controller_test.py
new file mode 100644 (file)
index 0000000..caba797
--- /dev/null
@@ -0,0 +1,37 @@
+##############################################################################
+# Copyright (c) 2017 akhil.batra@research.iiit.ac.in and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import httplib
+import json
+
+from qtip.base.constant import BaseProp
+
+
+def test_get_list_metrics(app_client):
+    response_success = app_client.get("/v1.0/metrics")
+    assert response_success.status_code == httplib.OK
+    metric_list = json.loads(response_success.data)['metrics']
+    assert len(metric_list) > 0
+    assert metric_list[0].endswith('.yaml')
+
+
+def test_get_metric(app_client):
+    response_success = app_client.get("/v1.0/metrics/dpi.yaml")
+    assert response_success.status_code == httplib.OK
+    metric_data = json.loads(response_success.data)
+    assert BaseProp.NAME in metric_data
+    assert BaseProp.WORKLOADS in metric_data
+    assert isinstance(metric_data[BaseProp.WORKLOADS], list)
+
+
+def test_get_metric_not_found(app_client):
+    response_not_found = app_client.get("/v1.0/metrics/fake.yaml")
+    response_data = json.loads(response_not_found.data)
+    assert response_not_found.status_code == httplib.NOT_FOUND
+    assert response_data['title'] == "Metric not found"
diff --git a/tests/unit/api/plan_controller_test.py b/tests/unit/api/plan_controller_test.py
new file mode 100644 (file)
index 0000000..136bd3c
--- /dev/null
@@ -0,0 +1,49 @@
+##############################################################################
+# Copyright (c) 2017 akhil.batra@research.iiit.ac.in and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import httplib
+import json
+
+
+from qtip.loader.plan import PlanProp
+
+
+def test_invalid_url(app_client):
+    response_url_not_found = app_client.get("/v1.0/fakeresource")
+    assert response_url_not_found.status_code == httplib.NOT_FOUND
+
+
+def test_get_list_plans(app_client):
+    response_success = app_client.get("/v1.0/plans")
+    assert response_success.status_code == httplib.OK
+    plan_list = json.loads(response_success.data)['plans']
+    assert len(plan_list) > 0
+    assert plan_list[0].endswith('.yaml')
+
+
+def test_get_plan(app_client):
+    response_success = app_client.get("/v1.0/plans/sample.yaml")
+    assert response_success.status_code == httplib.OK
+    plan_data = json.loads(response_success.data)
+    assert PlanProp.NAME in plan_data
+    assert PlanProp.DESCRIPTION in plan_data
+    assert PlanProp.CONFIG in plan_data
+    assert PlanProp.QPIS in plan_data
+
+
+def test_get_plan_not_found(app_client):
+    response_not_found = app_client.get("/v1.0/plans/fake.yaml")
+    response_data = json.loads(response_not_found.data)
+    assert response_not_found.status_code == httplib.NOT_FOUND
+    assert response_data['title'] == "Plan not found"
+
+
+def test_runner_not_implemented(app_client):
+    response_error = app_client.post("/v1.0/plans/fake.yaml?action=run", follow_redirects=False)
+    assert response_error.status_code == httplib.NOT_IMPLEMENTED
diff --git a/tests/unit/api/qpi_controller_test.py b/tests/unit/api/qpi_controller_test.py
new file mode 100644 (file)
index 0000000..6291dd9
--- /dev/null
@@ -0,0 +1,43 @@
+##############################################################################
+# Copyright (c) 2017 akhil.batra@research.iiit.ac.in and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import httplib
+import json
+
+from qtip.base.constant import FormulaName
+from qtip.base.constant import SpecProp
+
+
+def test_get_list_qpis(app_client):
+    response_success = app_client.get("/v1.0/qpis")
+    assert response_success.status_code == httplib.OK
+    qpi_spec_list = json.loads(response_success.data)['qpis']
+    assert len(qpi_spec_list) > 0
+    assert qpi_spec_list[0].endswith('.yaml')
+
+
+def test_get_qpi(app_client):
+    response_success = app_client.get("/v1.0/qpis/compute.yaml")
+    assert response_success.status_code == httplib.OK
+    qpi_data = json.loads(response_success.data)
+    assert SpecProp.DESCRIPTION in qpi_data
+    assert SpecProp.FORMULA in qpi_data
+    assert SpecProp.SECTIONS in qpi_data
+    assert qpi_data[SpecProp.FORMULA] in FormulaName.__dict__.values()
+    sections = qpi_data[SpecProp.SECTIONS]
+    assert isinstance(sections, list)
+    for section in sections:
+        assert SpecProp.NAME in section
+
+
+def test_get_qpi_not_found(app_client):
+    response_not_found = app_client.get("/v1.0/qpis/fake.yaml")
+    response_data = json.loads(response_not_found.data)
+    assert response_not_found.status_code == httplib.NOT_FOUND
+    assert response_data['title'] == "QPI not found"