Merge "Display all versions info in versions controller"
This commit is contained in:
commit
094530c2e6
@ -26,11 +26,6 @@ except ImportError:
|
|||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from barbican.api.controllers import cas
|
|
||||||
from barbican.api.controllers import containers
|
|
||||||
from barbican.api.controllers import orders
|
|
||||||
from barbican.api.controllers import secrets
|
|
||||||
from barbican.api.controllers import transportkeys
|
|
||||||
from barbican.api.controllers import versions
|
from barbican.api.controllers import versions
|
||||||
from barbican.api import hooks
|
from barbican.api import hooks
|
||||||
from barbican.common import config
|
from barbican.common import config
|
||||||
@ -44,16 +39,6 @@ if newrelic_loaded:
|
|||||||
newrelic.agent.initialize('/etc/newrelic/newrelic.ini')
|
newrelic.agent.initialize('/etc/newrelic/newrelic.ini')
|
||||||
|
|
||||||
|
|
||||||
class RootController(object):
|
|
||||||
def __init__(self):
|
|
||||||
# Adding the controllers at runtime to due config loading issues
|
|
||||||
self.secrets = secrets.SecretsController()
|
|
||||||
self.orders = orders.OrdersController()
|
|
||||||
self.containers = containers.ContainersController()
|
|
||||||
self.transport_keys = transportkeys.TransportKeysController()
|
|
||||||
self.cas = cas.CertificateAuthoritiesController()
|
|
||||||
|
|
||||||
|
|
||||||
def build_wsgi_app(controller=None, transactional=False):
|
def build_wsgi_app(controller=None, transactional=False):
|
||||||
"""WSGI application creation helper
|
"""WSGI application creation helper
|
||||||
|
|
||||||
@ -68,42 +53,47 @@ def build_wsgi_app(controller=None, transactional=False):
|
|||||||
|
|
||||||
# Create WSGI app
|
# Create WSGI app
|
||||||
wsgi_app = pecan.Pecan(
|
wsgi_app = pecan.Pecan(
|
||||||
controller or RootController(),
|
controller or versions.AVAILABLE_VERSIONS[versions.DEFAULT_VERSION](),
|
||||||
hooks=request_hooks,
|
hooks=request_hooks,
|
||||||
force_canonical=False
|
force_canonical=False
|
||||||
)
|
)
|
||||||
return wsgi_app
|
return wsgi_app
|
||||||
|
|
||||||
|
|
||||||
def create_main_app(global_config, **local_conf):
|
def main_app(func):
|
||||||
|
def _wrapper(global_config, **local_conf):
|
||||||
|
# Queuing initialization
|
||||||
|
queue.init(CONF, is_server_side=False)
|
||||||
|
|
||||||
|
# Configure oslo logging and configuration services.
|
||||||
|
log.setup(CONF, 'barbican')
|
||||||
|
|
||||||
|
config.setup_remote_pydev_debug()
|
||||||
|
|
||||||
|
# Initializing the database engine and session factory before the app
|
||||||
|
# starts ensures we don't lose requests due to lazy initialiation of db
|
||||||
|
# connections.
|
||||||
|
repositories.setup_database_engine_and_factory()
|
||||||
|
|
||||||
|
wsgi_app = func(global_config, **local_conf)
|
||||||
|
|
||||||
|
if newrelic_loaded:
|
||||||
|
wsgi_app = newrelic.agent.WSGIApplicationWrapper(wsgi_app)
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
LOG.info(u._LI('Barbican app created and initialized'))
|
||||||
|
return wsgi_app
|
||||||
|
return _wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@main_app
|
||||||
|
def create_main_app_v1(global_config, **local_conf):
|
||||||
"""uWSGI factory method for the Barbican-API application."""
|
"""uWSGI factory method for the Barbican-API application."""
|
||||||
|
|
||||||
# Queuing initialization
|
|
||||||
queue.init(CONF, is_server_side=False)
|
|
||||||
|
|
||||||
# Configure oslo logging and configuration services.
|
|
||||||
log.setup(CONF, 'barbican')
|
|
||||||
config.setup_remote_pydev_debug()
|
|
||||||
|
|
||||||
# Initializing the database engine and session factory before the app
|
|
||||||
# starts ensures we don't lose requests due to lazy initialiation of db
|
|
||||||
# connections.
|
|
||||||
repositories.setup_database_engine_and_factory()
|
|
||||||
|
|
||||||
# Setup app with transactional hook enabled
|
# Setup app with transactional hook enabled
|
||||||
wsgi_app = build_wsgi_app(transactional=True)
|
return build_wsgi_app(versions.V1Controller(), transactional=True)
|
||||||
|
|
||||||
if newrelic_loaded:
|
|
||||||
wsgi_app = newrelic.agent.WSGIApplicationWrapper(wsgi_app)
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
LOG.info(u._LI('Barbican app created and initialized'))
|
|
||||||
|
|
||||||
return wsgi_app
|
|
||||||
|
|
||||||
|
|
||||||
def create_admin_app(global_config, **local_conf):
|
def create_admin_app(global_config, **local_conf):
|
||||||
wsgi_app = pecan.make_app(versions.VersionController())
|
wsgi_app = pecan.make_app(versions.VersionsController())
|
||||||
return wsgi_app
|
return wsgi_app
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,8 +11,14 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import pecan
|
import pecan
|
||||||
|
from six.moves.urllib import parse
|
||||||
|
|
||||||
from barbican.api import controllers
|
from barbican.api import controllers
|
||||||
|
from barbican.api.controllers import cas
|
||||||
|
from barbican.api.controllers import containers
|
||||||
|
from barbican.api.controllers import orders
|
||||||
|
from barbican.api.controllers import secrets
|
||||||
|
from barbican.api.controllers import transportkeys
|
||||||
from barbican.common import utils
|
from barbican.common import utils
|
||||||
from barbican import i18n as u
|
from barbican import i18n as u
|
||||||
from barbican import version
|
from barbican import version
|
||||||
@ -20,21 +26,145 @@ from barbican import version
|
|||||||
LOG = utils.getLogger(__name__)
|
LOG = utils.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class VersionController(object):
|
MIME_TYPE_JSON = 'application/json'
|
||||||
|
MIME_TYPE_JSON_HOME = 'application/json-home'
|
||||||
|
MEDIA_TYPE_JSON = 'application/vnd.openstack.keymanagement-%s+json'
|
||||||
|
|
||||||
|
|
||||||
|
def _version_not_found():
|
||||||
|
"""Throw exception indicating version not found."""
|
||||||
|
pecan.abort(404, u._("The version you requested wasn't found"))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_versioned_url(full_url, version):
|
||||||
|
parsed_url, _ = parse.urldefrag(full_url)
|
||||||
|
|
||||||
|
if version[-1] != '/':
|
||||||
|
version += '/'
|
||||||
|
return parse.urljoin(parsed_url, version)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseVersionController(object):
|
||||||
|
"""Base class for the version-specific controllers"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_version_info(cls, request):
|
||||||
|
return {
|
||||||
|
'id': cls.version_id,
|
||||||
|
'status': 'stable',
|
||||||
|
'updated': cls.last_updated,
|
||||||
|
'links': [
|
||||||
|
{
|
||||||
|
'rel': 'self',
|
||||||
|
'href': _get_versioned_url(request.url,
|
||||||
|
cls.version_string),
|
||||||
|
}, {
|
||||||
|
'rel': 'describedby',
|
||||||
|
'type': 'text/html',
|
||||||
|
'href': 'http://docs.openstack.org/'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'media-types': [
|
||||||
|
{
|
||||||
|
'base': MIME_TYPE_JSON,
|
||||||
|
'type': MEDIA_TYPE_JSON % cls.version_string
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class V1Controller(BaseVersionController):
|
||||||
|
"""Root controller for the v1 API"""
|
||||||
|
|
||||||
|
version_string = 'v1'
|
||||||
|
|
||||||
|
# NOTE(jaosorior): We might start using decimals in the future, meanwhile
|
||||||
|
# this is the same as the version string.
|
||||||
|
version_id = 'v1'
|
||||||
|
|
||||||
|
last_updated = '2015-04-28T00:00:00Z'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
LOG.debug('=== Creating VersionController ===')
|
LOG.debug('=== Creating V1Controller ===')
|
||||||
|
self.secrets = secrets.SecretsController()
|
||||||
|
self.orders = orders.OrdersController()
|
||||||
|
self.containers = containers.ContainersController()
|
||||||
|
self.transport_keys = transportkeys.TransportKeysController()
|
||||||
|
self.cas = cas.CertificateAuthoritiesController()
|
||||||
|
|
||||||
|
self.__controllers = [
|
||||||
|
self.secrets,
|
||||||
|
self.orders,
|
||||||
|
self.containers,
|
||||||
|
self.transport_keys,
|
||||||
|
self.cas,
|
||||||
|
]
|
||||||
|
|
||||||
@pecan.expose(generic=True)
|
@pecan.expose(generic=True)
|
||||||
def index(self):
|
def index(self):
|
||||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
||||||
|
|
||||||
@index.when(method='GET', template='json')
|
@index.when(method='GET', template='json')
|
||||||
|
@utils.allow_certain_content_types(MIME_TYPE_JSON, MIME_TYPE_JSON_HOME)
|
||||||
@controllers.handle_exceptions(u._('Version retrieval'))
|
@controllers.handle_exceptions(u._('Version retrieval'))
|
||||||
def on_get(self):
|
def on_get(self):
|
||||||
body = {
|
return {'version': self.get_version_info(pecan.request)}
|
||||||
'v1': 'current',
|
|
||||||
'build': version.__version__
|
|
||||||
|
AVAILABLE_VERSIONS = {
|
||||||
|
V1Controller.version_string: V1Controller,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_VERSION = V1Controller.version_string
|
||||||
|
|
||||||
|
|
||||||
|
class VersionsController(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
LOG.debug('=== Creating VersionsController ===')
|
||||||
|
|
||||||
|
@pecan.expose(generic=True)
|
||||||
|
def index(self, **kwargs):
|
||||||
|
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
||||||
|
|
||||||
|
@index.when(method='GET', template='json')
|
||||||
|
@utils.allow_certain_content_types(MIME_TYPE_JSON, MIME_TYPE_JSON_HOME)
|
||||||
|
def on_get(self, **kwargs):
|
||||||
|
"""The list of versions is dependent on the context."""
|
||||||
|
self._redirect_to_default_json_home_if_needed(pecan.request)
|
||||||
|
|
||||||
|
if 'build' in kwargs:
|
||||||
|
return {'build': version.__version__}
|
||||||
|
|
||||||
|
versions_info = [version_class.get_version_info(pecan.request)
|
||||||
|
for version_class in AVAILABLE_VERSIONS.itervalues()]
|
||||||
|
|
||||||
|
version_output = {
|
||||||
|
'versions': {
|
||||||
|
'values': versions_info
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LOG.info(u._LI('Retrieved version'))
|
|
||||||
return body
|
# Since we are returning all the versions available, the proper status
|
||||||
|
# code is Multiple Choices (300)
|
||||||
|
pecan.response.status = 300
|
||||||
|
return version_output
|
||||||
|
|
||||||
|
def _redirect_to_default_json_home_if_needed(self, request):
|
||||||
|
if self._mime_best_match(request.accept) == MIME_TYPE_JSON_HOME:
|
||||||
|
url = _get_versioned_url(request.url, DEFAULT_VERSION)
|
||||||
|
LOG.debug("Redirecting Request to " + url)
|
||||||
|
# NOTE(jaosorior): This issues an "external" redirect because of
|
||||||
|
# two reasons:
|
||||||
|
# * This module doesn't require authorization, and accessing
|
||||||
|
# specific version info needs that.
|
||||||
|
# * The resource is a separate app_factory and won't be found
|
||||||
|
# internally
|
||||||
|
pecan.redirect(url, request=request)
|
||||||
|
|
||||||
|
def _mime_best_match(self, accept):
|
||||||
|
if not accept:
|
||||||
|
return MIME_TYPE_JSON
|
||||||
|
|
||||||
|
SUPPORTED_TYPES = [MIME_TYPE_JSON, MIME_TYPE_JSON_HOME]
|
||||||
|
return accept.best_match(SUPPORTED_TYPES)
|
||||||
|
@ -35,13 +35,23 @@ CONF = config.CONF
|
|||||||
API_VERSION = 'v1'
|
API_VERSION = 'v1'
|
||||||
|
|
||||||
|
|
||||||
def allow_all_content_types(f):
|
def _do_allow_certain_content_types(func, content_types_list=[]):
|
||||||
# Pecan decorator to not limit content types for controller routes
|
# Allows you to bypass pecan's content-type restrictions
|
||||||
cfg = pecan.util._cfg(f)
|
cfg = pecan.util._cfg(func)
|
||||||
cfg.setdefault('content_types', {})
|
cfg.setdefault('content_types', {})
|
||||||
cfg['content_types'].update((value, '')
|
cfg['content_types'].update((value, '')
|
||||||
for value in mimetypes.types_map.values())
|
for value in content_types_list)
|
||||||
return f
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
def allow_certain_content_types(*content_types_list):
|
||||||
|
def _wrapper(func):
|
||||||
|
return _do_allow_certain_content_types(func, content_types_list)
|
||||||
|
return _wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def allow_all_content_types(f):
|
||||||
|
return _do_allow_certain_content_types(f, mimetypes.types_map.values())
|
||||||
|
|
||||||
|
|
||||||
def hostname_for_refs(resource=None):
|
def hostname_for_refs(resource=None):
|
||||||
|
@ -16,15 +16,32 @@ from barbican.api import controllers
|
|||||||
from barbican.tests import utils
|
from barbican.tests import utils
|
||||||
|
|
||||||
|
|
||||||
class WhenTestingVersionResource(utils.BarbicanAPIBaseTestCase):
|
class WhenTestingVersionsResource(utils.BarbicanAPIBaseTestCase):
|
||||||
root_controller = controllers.versions.VersionController()
|
root_controller = controllers.versions.VersionsController()
|
||||||
|
|
||||||
def test_should_return_200_on_get(self):
|
def test_should_return_multiple_choices_on_get(self):
|
||||||
resp = self.app.get('/')
|
resp = self.app.get('/')
|
||||||
self.assertEqual(200, resp.status_int)
|
self.assertEqual(300, resp.status_int)
|
||||||
|
|
||||||
|
def test_should_return_multiple_choices_on_get_if_json_accept_header(self):
|
||||||
|
headers = {'Accept': 'application/json'}
|
||||||
|
resp = self.app.get('/', headers=headers)
|
||||||
|
self.assertEqual(300, resp.status_int)
|
||||||
|
|
||||||
|
def test_should_redirect_if_json_home_accept_header_present(self):
|
||||||
|
headers = {'Accept': 'application/json-home'}
|
||||||
|
resp = self.app.get('/', headers=headers)
|
||||||
|
self.assertEqual(302, resp.status_int)
|
||||||
|
|
||||||
def test_should_return_version_json(self):
|
def test_should_return_version_json(self):
|
||||||
resp = self.app.get('/')
|
resp = self.app.get('/')
|
||||||
|
|
||||||
self.assertTrue('v1' in resp.json)
|
versions_response = resp.json['versions']['values']
|
||||||
self.assertEqual(resp.json.get('v1'), 'current')
|
v1_info = versions_response[0]
|
||||||
|
|
||||||
|
# NOTE(jaosorior): I used assertIn instead of assertEqual because we
|
||||||
|
# might start using decimal numbers in the future. So when that happens
|
||||||
|
# this test will still be valid.
|
||||||
|
self.assertIn('v1', v1_info['id'])
|
||||||
|
self.assertEqual(len(v1_info['media-types']), 1)
|
||||||
|
self.assertEqual(v1_info['media-types'][0]['base'], 'application/json')
|
||||||
|
@ -69,8 +69,8 @@ class TestableResource(object):
|
|||||||
return self.controller.on_delete(*args, **kwargs)
|
return self.controller.on_delete(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class VersionResource(TestableResource):
|
class VersionsResource(TestableResource):
|
||||||
controller_cls = versions.VersionController
|
controller_cls = versions.VersionsController
|
||||||
|
|
||||||
|
|
||||||
class SecretsResource(TestableResource):
|
class SecretsResource(TestableResource):
|
||||||
@ -203,32 +203,32 @@ class BaseTestCase(utils.BaseTestCase, utils.MockModelRepositoryMixin):
|
|||||||
self.assertEqual(403, exception.status_int)
|
self.assertEqual(403, exception.status_int)
|
||||||
|
|
||||||
|
|
||||||
class WhenTestingVersionResource(BaseTestCase):
|
class WhenTestingVersionsResource(BaseTestCase):
|
||||||
"""RBAC tests for the barbican.api.resources.VersionResource class."""
|
"""RBAC tests for the barbican.api.resources.VersionsResource class."""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(WhenTestingVersionResource, self).setUp()
|
super(WhenTestingVersionsResource, self).setUp()
|
||||||
|
|
||||||
self.resource = VersionResource()
|
self.resource = VersionsResource()
|
||||||
|
|
||||||
def test_rules_should_be_loaded(self):
|
def test_rules_should_be_loaded(self):
|
||||||
self.assertIsNotNone(self.policy_enforcer.rules)
|
self.assertIsNotNone(self.policy_enforcer.rules)
|
||||||
|
|
||||||
def test_should_pass_get_version(self):
|
def test_should_pass_get_versions(self):
|
||||||
# Can't use base method that short circuits post-RBAC processing here,
|
# Can't use base method that short circuits post-RBAC processing here,
|
||||||
# as version GET is trivial
|
# as version GET is trivial
|
||||||
for role in ['admin', 'observer', 'creator', 'audit']:
|
for role in ['admin', 'observer', 'creator', 'audit']:
|
||||||
self.req = self._generate_req(roles=[role] if role else [])
|
self.req = self._generate_req(roles=[role] if role else [])
|
||||||
self._invoke_on_get()
|
self._invoke_on_get()
|
||||||
|
|
||||||
def test_should_pass_get_version_with_bad_roles(self):
|
def test_should_pass_get_versions_with_bad_roles(self):
|
||||||
self.req = self._generate_req(roles=[None, 'bunkrolehere'])
|
self.req = self._generate_req(roles=[None, 'bunkrolehere'])
|
||||||
self._invoke_on_get()
|
self._invoke_on_get()
|
||||||
|
|
||||||
def test_should_pass_get_version_with_no_roles(self):
|
def test_should_pass_get_versions_with_no_roles(self):
|
||||||
self.req = self._generate_req()
|
self.req = self._generate_req()
|
||||||
self._invoke_on_get()
|
self._invoke_on_get()
|
||||||
|
|
||||||
def test_should_pass_get_version_multiple_roles(self):
|
def test_should_pass_get_versions_multiple_roles(self):
|
||||||
self.req = self._generate_req(roles=['admin', 'observer', 'creator',
|
self.req = self._generate_req(roles=['admin', 'observer', 'creator',
|
||||||
'audit'])
|
'audit'])
|
||||||
self._invoke_on_get()
|
self._invoke_on_get()
|
||||||
|
@ -130,7 +130,7 @@ function configure_barbican {
|
|||||||
## Set up keystone
|
## Set up keystone
|
||||||
|
|
||||||
# Turn on the middleware
|
# Turn on the middleware
|
||||||
iniset $BARBICAN_PASTE_CONF 'pipeline:barbican_api' pipeline 'keystone_authtoken context apiapp'
|
iniset $BARBICAN_PASTE_CONF 'pipeline:barbican_api' pipeline 'keystone_authtoken context apiapp_v1'
|
||||||
|
|
||||||
# Set the keystone parameters
|
# Set the keystone parameters
|
||||||
iniset $BARBICAN_PASTE_CONF 'filter:keystone_authtoken' auth_protocol $KEYSTONE_AUTH_PROTOCOL
|
iniset $BARBICAN_PASTE_CONF 'filter:keystone_authtoken' auth_protocol $KEYSTONE_AUTH_PROTOCOL
|
||||||
|
@ -32,7 +32,7 @@ the get version call.
|
|||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
[pipeline:barbican_api]
|
[pipeline:barbican_api]
|
||||||
pipeline = keystone_authtoken context apiapp
|
pipeline = keystone_authtoken context apiapp_v1
|
||||||
|
|
||||||
2. Replace ``keystone_authtoken`` filter values to match your Keystone
|
2. Replace ``keystone_authtoken`` filter values to match your Keystone
|
||||||
setup
|
setup
|
||||||
|
@ -9,21 +9,20 @@ pipeline = versionapp
|
|||||||
|
|
||||||
# Use this pipeline for Barbican API - DEFAULT no authentication
|
# Use this pipeline for Barbican API - DEFAULT no authentication
|
||||||
[pipeline:barbican_api]
|
[pipeline:barbican_api]
|
||||||
pipeline = unauthenticated-context apiapp
|
pipeline = unauthenticated-context apiapp_v1
|
||||||
####pipeline = simple apiapp
|
#pipeline = keystone_authtoken context apiapp_v1
|
||||||
#pipeline = keystone_authtoken context apiapp
|
|
||||||
|
|
||||||
#Use this pipeline to activate a repoze.profile middleware and HTTP port,
|
#Use this pipeline to activate a repoze.profile middleware and HTTP port,
|
||||||
# to provide profiling information for the REST API processing.
|
# to provide profiling information for the REST API processing.
|
||||||
[pipeline:barbican-profile]
|
[pipeline:barbican-profile]
|
||||||
pipeline = unauthenticated-context egg:Paste#cgitb egg:Paste#httpexceptions profile apiapp
|
pipeline = unauthenticated-context egg:Paste#cgitb egg:Paste#httpexceptions profile apiapp_v1
|
||||||
|
|
||||||
#Use this pipeline for keystone auth
|
#Use this pipeline for keystone auth
|
||||||
[pipeline:barbican-api-keystone]
|
[pipeline:barbican-api-keystone]
|
||||||
pipeline = keystone_authtoken context apiapp
|
pipeline = keystone_authtoken context apiapp_v1
|
||||||
|
|
||||||
[app:apiapp]
|
[app:apiapp_v1]
|
||||||
paste.app_factory = barbican.api.app:create_main_app
|
paste.app_factory = barbican.api.app:create_main_app_v1
|
||||||
|
|
||||||
[app:versionapp]
|
[app:versionapp]
|
||||||
paste.app_factory = barbican.api.app:create_version_app
|
paste.app_factory = barbican.api.app:create_version_app
|
||||||
|
@ -35,6 +35,13 @@ class VersionDiscoveryTestCase(base.TestCase):
|
|||||||
resp = self.client.get(url_without_version, use_auth=use_auth)
|
resp = self.client.get(url_without_version, use_auth=use_auth)
|
||||||
body = resp.json()
|
body = resp.json()
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 300)
|
||||||
self.assertEqual(body.get('v1'), 'current')
|
versions_response = body['versions']['values']
|
||||||
self.assertGreater(len(body.get('build')), 1)
|
v1_info = versions_response[0]
|
||||||
|
|
||||||
|
# NOTE(jaosorior): I used assertIn instead of assertEqual because we
|
||||||
|
# might start using decimal numbers in the future. So when that happens
|
||||||
|
# this test will still be valid.
|
||||||
|
self.assertIn('v1', v1_info['id'])
|
||||||
|
self.assertEqual(len(v1_info['media-types']), 1)
|
||||||
|
self.assertEqual(v1_info['media-types'][0]['base'], 'application/json')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user