Repository: kids-first/kf-api-dataservice

Issue:
Fix "/" path to return 200

Discussion:
dankolbman: Need to update the README too
znatty22: Looks good 👍 

Patch:
diff --git a/README.md b/README.md
index ee05429..8780af7 100644
--- a/README.md
+++ b/README.md
@@ -27,11 +27,11 @@ flask db upgrade
 flask run 
 ```
 
-The API should now be available at `localhost:5000/v1`.
+The API should now be available at `localhost:5000/`.
 
 ## Documentation
 
-The swagger docs are located at the `/v1` endpoint.
+The swagger docs are located at the root `localhost:5000/`.
 
 ## Testing
 
diff --git a/dataservice/api/__init__.py b/dataservice/api/__init__.py
index 3e9a051..5d1beab 100644
--- a/dataservice/api/__init__.py
+++ b/dataservice/api/__init__.py
@@ -1,16 +1,19 @@
 from flask import Blueprint
 from flask_restplus import Api
 from dataservice.api.participant import participant_api
+from dataservice.api.status import status_api
+from dataservice.utils import _get_version
 
-api_v1 = Blueprint('api', __name__, url_prefix='/v1')
+api_v1 = Blueprint('api', __name__, url_prefix='')
 
 api = Api(api_v1,
           title='Kids First Data Service',
           description=open('dataservice/api/README.md').read(),
-          version='0.1',
+          version=_get_version(),
           default='',
           default_label='')
 
+api.add_namespace(status_api)
 api.add_namespace(participant_api)
 
 
diff --git a/dataservice/api/status/README.md b/dataservice/api/status/README.md
new file mode 100644
index 0000000..0a331cb
--- /dev/null
+++ b/dataservice/api/status/README.md
@@ -0,0 +1,1 @@
+Contains version and health information about the Dataservice API.
diff --git a/dataservice/api/status/__init__.py b/dataservice/api/status/__init__.py
new file mode 100644
index 0000000..92ca513
--- /dev/null
+++ b/dataservice/api/status/__init__.py
@@ -0,0 +1,1 @@
+from .resources import status_api
diff --git a/dataservice/api/status/resources.py b/dataservice/api/status/resources.py
new file mode 100644
index 0000000..d7b5982
--- /dev/null
+++ b/dataservice/api/status/resources.py
@@ -0,0 +1,49 @@
+import os
+import subprocess
+
+from flask_restplus import Namespace, Resource, fields
+
+from dataservice.utils import _get_version
+
+THIS_DIR = os.path.abspath(os.path.dirname(__file__))
+README_FILE = os.path.join(THIS_DIR, 'README.md')
+
+status_api = Namespace(name='status', description=open(README_FILE).read())
+
+from dataservice.api.status.serializers import (  # noqa
+    version_response,
+    _version_fields
+)
+
+
+status_api.models['VersionFields'] = _version_fields
+status_api.models['VersionResponse'] = version_response
+
+
+@status_api.route("")
+class Status(Resource):
+    """
+    Service Status
+    """
+    @status_api.marshal_with(version_response)
+    def get(self):
+        """
+        Get the service status
+
+        Returns information about the current API's version and status
+        """
+        commit = (subprocess.check_output(
+                  ["git", "rev-parse", "--short", "HEAD"])
+                  .decode("utf-8").strip())
+
+        tags = (subprocess.check_output(
+                ["git", "tag", "-l", "--points-at", "HEAD"])
+                .decode("utf-8").split('\n'))
+        tags = [] if tags[0] == "" else tags
+        return {"_status": {
+                    "message": "Welcome to the Kids First Dataservice API",
+                    "code": 200,
+                    "version": _get_version(),
+                    "commit": commit,
+                    "tags": tags
+                }}
diff --git a/dataservice/api/status/serializers.py b/dataservice/api/status/serializers.py
new file mode 100644
index 0000000..8325dd9
--- /dev/null
+++ b/dataservice/api/status/serializers.py
@@ -0,0 +1,23 @@
+from flask_restplus import fields
+
+from dataservice.api.common.serializers import (
+    _status_fields,
+    base_response
+)
+
+_version_fields = _status_fields.clone('VersionFields', {
+    'version': fields.String(
+        example='1.0',
+        description='Current version of the API'),
+    'commit': fields.String(
+        example='23cf525',
+        description='Commit currently deployed'),
+    'tags': fields.List(fields.String(
+        example='rc-0.8.4',
+        description='Any tags on the current commit'))
+})
+
+# Version response model
+version_response = base_response.clone('VersionResponse', {
+    '_status': fields.Nested(_version_fields)
+})
diff --git a/dataservice/utils.py b/dataservice/utils.py
new file mode 100644
index 0000000..0f97a39
--- /dev/null
+++ b/dataservice/utils.py
@@ -0,0 +1,5 @@
+import pkg_resources
+
+
+def _get_version():
+    return pkg_resources.get_distribution("kf-api-dataservice").version


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..cc20c3f
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,13 @@
+import pytest
+
+from dataservice import create_app
+from dataservice.extensions import db
+
+
+@pytest.fixture
+def client():
+    app = create_app('testing')
+    app_context = app.app_context()
+    app_context.push()
+    db.create_all()
+    return app.test_client()
diff --git a/tests/test_api.py b/tests/test_api.py
new file mode 100644
index 0000000..2fb263c
--- /dev/null
+++ b/tests/test_api.py
@@ -0,0 +1,63 @@
+import json
+import pkg_resources
+import pytest
+
+import dataservice
+
+
+class TestAPI:
+    """
+    General API tests such as reponse code checks, envelope formatting checks,
+    and header checks
+    """
+
+    @pytest.mark.parametrize('endpoint,method,status_code', [
+        ('/', 'GET', 200),
+        ('', 'GET', 200),
+        ('/status', 'GET', 200),
+        ('/participants', 'GET', 200),
+        ('/persons', 'GET', 404),
+        ('/participants/123', 'GET', 404)
+    ])
+    def test_status_codes(self, client, endpoint, method, status_code):
+        """ Test endpoint response codes """
+        call_func = getattr(client, method.lower())
+        assert call_func(endpoint).status_code == status_code
+
+
+    @pytest.mark.parametrize('endpoint,method', [
+        ('/participants', 'GET')
+    ])
+    def test_status_format(self, client, endpoint, method):
+        """ Test that the _response field is consistent """
+        call_func = getattr(client, method.lower())
+        body = json.loads(call_func(endpoint).data.decode('utf-8'))
+        assert '_status' in body
+        assert 'message' in body['_status']
+        assert type(body['_status']['message']) is str
+        assert 'code' in body['_status']
+        assert type(body['_status']['code']) is int
+
+    def test_version(self, client):
+        """ Test response from /status returns correct fields """
+        status = json.loads(client.get('/status').data.decode('utf-8'))
+        status = status['_status']
+        assert 'commit' in status
+        assert len(status['commit']) == 7
+        assert 'version' in status
+        assert status['version'].count('.') == 2
+        assert 'tags' in status
+        assert type(status['tags']) is list
+        assert 'Dataservice' in status['message']
+
+    def test_versions(self, client):
+        """ Test that versions are aligned accross package, docs, and api """
+        package = pkg_resources.get_distribution("kf-api-dataservice").version
+        api_restplus = dataservice.api.api.version
+        api_version = json.loads(client.get('/status').data.decode('utf-8'))
+        api_version = api_version['_status']['version']
+        swagger = json.loads(client.get('/swagger.json').data.decode('utf-8'))
+
+        assert api_version == package
+        assert api_version == api_restplus 
+        assert api_version == swagger['info']['version']