diff --git a/keystoneclient/fixture/__init__.py b/keystoneclient/fixture/__init__.py index 3373a8fba..faece1e46 100644 --- a/keystoneclient/fixture/__init__.py +++ b/keystoneclient/fixture/__init__.py @@ -11,8 +11,8 @@ # under the License. """ -The generators in this directory produce keystone compliant tokens for use in -testing. +The generators in this directory produce keystone compliant structures for use +in testing. They should be considered part of the public API because they may be relied upon to generate test tokens for other clients. However they should never be @@ -21,8 +21,15 @@ may be dependencies from this module on libraries that are only available in testing. """ +from keystoneclient.fixture.discovery import * # noqa from keystoneclient.fixture.exception import FixtureValidationError # noqa from keystoneclient.fixture.v2 import Token as V2Token # noqa from keystoneclient.fixture.v3 import Token as V3Token # noqa -__all__ = ['V2Token', 'V3Token', 'FixtureValidationError'] +__all__ = ['DiscoveryList', + 'FixtureValidationError', + 'V2Discovery', + 'V3Discovery', + 'V2Token', + 'V3Token', + ] diff --git a/keystoneclient/fixture/discovery.py b/keystoneclient/fixture/discovery.py new file mode 100644 index 000000000..0a7f625cb --- /dev/null +++ b/keystoneclient/fixture/discovery.py @@ -0,0 +1,267 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import datetime + +from keystoneclient.openstack.common import timeutils +from keystoneclient import utils + +__all__ = ['DiscoveryList', + 'V2Discovery', + 'V3Discovery', + ] + +_DEFAULT_DAYS_AGO = 30 + + +class DiscoveryBase(dict): + """The basic version discovery structure. + + All version discovery elements should have access to these values. + """ + + @utils.positional() + def __init__(self, id, status=None, updated=None): + """Create a new structure. + + :param string id: The version id for this version entry. + :param string status: The status of this entry. + :param DateTime updated: When the API was last updated. + """ + super(DiscoveryBase, self).__init__() + + self.id = id + self.status = status or 'stable' + self.updated = updated or (timeutils.utcnow() - + datetime.timedelta(days=_DEFAULT_DAYS_AGO)) + + @property + def id(self): + return self.get('id') + + @id.setter + def id(self, value): + self['id'] = value + + @property + def status(self): + return self.get('status') + + @status.setter + def status(self, value): + self['status'] = value + + @property + def links(self): + return self.setdefault('links', []) + + @property + def updated_str(self): + return self.get('updated') + + @updated_str.setter + def updated_str(self, value): + self['updated'] = value + + @property + def updated(self): + return timeutils.parse_isotime(self.updated_str) + + @updated.setter + def updated(self, value): + self.updated_str = timeutils.isotime(value) + + @utils.positional() + def add_link(self, href, rel='self', type=None): + link = {'href': href, 'rel': rel} + if type: + link['type'] = type + self.links.append(link) + return link + + @property + def media_types(self): + return self.setdefault('media-types', []) + + @utils.positional(1) + def add_media_type(self, base, type): + mt = {'base': base, 'type': type} + self.media_types.append(mt) + return mt + + +class V2Discovery(DiscoveryBase): + """A Version element for a V2 identity service endpoint. + + Provides some default values and helper methods for creating a v2.0 + endpoint version structure. Clients should use this instead of creating + their own structures. + """ + + _DESC_URL = 'http://docs.openstack.org/api/openstack-identity-service/2.0/' + + @utils.positional() + def __init__(self, href, id=None, html=True, pdf=True, **kwargs): + """Create a new structure. + + :param string href: The url that this entry should point to. + :param string id: The version id that should be reported. (optional) + Defaults to 'v2.0'. + :param bool html: Add HTML describedby links to the structure. + :param bool pdf: Add PDF describedby links to the structure. + """ + super(V2Discovery, self).__init__(id or 'v2.0', **kwargs) + + self.add_link(href) + + if html: + self.add_html_description() + if pdf: + self.add_pdf_description() + + def add_html_description(self): + """Add the HTML described by links. + + The standard structure includes a link to a HTML document with the + API specification. Add it to this entry. + """ + self.add_link(href=self._DESC_URL + 'content', + rel='describedby', + type='text/html') + + def add_pdf_description(self): + """Add the PDF described by links. + + The standard structure includes a link to a PDF document with the + API specification. Add it to this entry. + """ + self.add_link(href=self._DESC_URL + 'identity-dev-guide-2.0.pdf', + rel='describedby', + type='application/pdf') + + +class V3Discovery(DiscoveryBase): + """A Version element for a V3 identity service endpoint. + + Provides some default values and helper methods for creating a v3 + endpoint version structure. Clients should use this instead of creating + there own structures. + """ + + @utils.positional() + def __init__(self, href, id=None, json=True, xml=True, **kwargs): + """Create a new structure. + + :param href: The url that this entry should point to. + :param string id: The version id that should be reported. (optional) + Defaults to 'v3.0'. + :param bool json: Add JSON media-type elements to the structure. + :param bool xml: Add XML media-type elements to the structure. + """ + super(V3Discovery, self).__init__(id or 'v3.0', **kwargs) + + self.add_link(href) + + if json: + self.add_json_media_type() + if xml: + self.add_xml_media_type() + + def add_json_media_type(self): + """Add the JSON media-type links. + + The standard structure includes a list of media-types that the endpoint + supports. Add JSON to the list. + """ + self.add_media_type(base='application/json', + type='application/vnd.openstack.identity-v3+json') + + def add_xml_media_type(self): + """Add the XML media-type links. + + The standard structure includes a list of media-types that the endpoint + supports. Add XML to the list. + """ + self.add_media_type(base='application/xml', + type='application/vnd.openstack.identity-v3+xml') + + +class DiscoveryList(dict): + """A List of version elements. + + Creates a correctly structured list of identity service endpoints for + use in testing with discovery. + """ + + TEST_URL = 'http://keystone.host:5000/' + + @utils.positional(2) + def __init__(self, href=None, v2=True, v3=True, v2_id=None, v3_id=None, + v2_status=None, v2_updated=None, v2_html=True, v2_pdf=True, + v3_status=None, v3_updated=None, v3_json=True, v3_xml=True): + """Create a new structure. + + :param string href: The url that this should be based at. + :param bool v2: Add a v2 element. + :param bool v3: Add a v3 element. + :param string v2_status: The status to use for the v2 element. + :param DateTime v2_updated: The update time to use for the v2 element. + :param bool v2_html: True to add a html link to the v2 element. + :param bool v2_pdf: True to add a pdf link to the v2 element. + :param string v3_status: The status to use for the v3 element. + :param DateTime v3_updated: The update time to use for the v3 element. + :param bool v3_json: True to add a html link to the v2 element. + :param bool v3_xml: True to add a pdf link to the v2 element. + """ + + super(DiscoveryList, self).__init__(versions={'values': []}) + + href = href or self.TEST_URL + + if v2: + v2_href = href.rstrip('/') + '/v2.0' + self.add_v2(v2_href, id=v2_id, status=v2_status, + updated=v2_updated, html=v2_html, pdf=v2_pdf) + + if v3: + v3_href = href.rstrip('/') + '/v3' + self.add_v3(v3_href, id=v3_id, status=v3_status, + updated=v3_updated, json=v3_json, xml=v3_xml) + + @property + def versions(self): + return self['versions']['values'] + + def add_version(self, version): + """Add a new version structure to the list. + + :param dict version: A new version structure to add to the list. + """ + self.versions.append(version) + + def add_v2(self, href, **kwargs): + """Add a v2 version to the list. + + The parameters are the same as V2Discovery. + """ + obj = V2Discovery(href, **kwargs) + self.add_version(obj) + return obj + + def add_v3(self, href, **kwargs): + """Add a v3 version to the list. + + The parameters are the same as V3Discovery. + """ + obj = V3Discovery(href, **kwargs) + self.add_version(obj) + return obj diff --git a/keystoneclient/tests/test_auth_token_middleware.py b/keystoneclient/tests/test_auth_token_middleware.py index 48d1afdf9..248db145c 100644 --- a/keystoneclient/tests/test_auth_token_middleware.py +++ b/keystoneclient/tests/test_auth_token_middleware.py @@ -64,37 +64,9 @@ FAKE_ADMIN_TOKEN = jsonutils.dumps( 'expires': '2022-10-03T16:58:01Z'}}}) -VERSION_LIST_v3 = jsonutils.dumps({ - "versions": { - "values": [ - { - "id": "v3.0", - "status": "stable", - "updated": "2013-03-06T00:00:00Z", - "links": [{'href': '%s/v3' % BASE_URI, 'rel': 'self'}] - }, - { - "id": "v2.0", - "status": "stable", - "updated": "2011-11-19T00:00:00Z", - "links": [{'href': '%s/v2.0' % BASE_URI, 'rel': 'self'}] - } - ] - } -}) - -VERSION_LIST_v2 = jsonutils.dumps({ - "versions": { - "values": [ - { - "id": "v2.0", - "status": "stable", - "updated": "2011-11-19T00:00:00Z", - "links": [] - } - ] - } -}) +VERSION_LIST_v2 = jsonutils.dumps(fixture.DiscoveryList(href=BASE_URI, + v3=False)) +VERSION_LIST_v3 = jsonutils.dumps(fixture.DiscoveryList(href=BASE_URI)) ERROR_TOKEN = '7ae290c2a06244c4b41692eb4e9225f2' MEMCACHED_SERVERS = ['localhost:11211'] diff --git a/keystoneclient/tests/test_discovery.py b/keystoneclient/tests/test_discovery.py index 7d8b47112..725777de1 100644 --- a/keystoneclient/tests/test_discovery.py +++ b/keystoneclient/tests/test_discovery.py @@ -18,6 +18,7 @@ from keystoneclient import _discover from keystoneclient import client from keystoneclient import discover from keystoneclient import exceptions +from keystoneclient import fixture from keystoneclient.openstack.common import jsonutils from keystoneclient.tests import utils from keystoneclient.v2_0 import client as v2_client @@ -76,20 +77,8 @@ TEST_SERVICE_CATALOG = [{ }] V2_URL = "%sv2.0" % BASE_URL -V2_DESCRIBED_BY_HTML = {'href': 'http://docs.openstack.org/api/' - 'openstack-identity-service/2.0/content/', - 'rel': 'describedby', - 'type': 'text/html'} -V2_DESCRIBED_BY_PDF = {'href': 'http://docs.openstack.org/api/openstack-ident' - 'ity-service/2.0/identity-dev-guide-2.0.pdf', - 'rel': 'describedby', - 'type': 'application/pdf'} - -V2_VERSION = {'id': 'v2.0', - 'links': [{'href': V2_URL, 'rel': 'self'}, - V2_DESCRIBED_BY_HTML, V2_DESCRIBED_BY_PDF], - 'status': 'stable', - 'updated': UPDATED} +V2_VERSION = fixture.V2Discovery(V2_URL) +V2_VERSION.updated_str = UPDATED V2_AUTH_RESPONSE = jsonutils.dumps({ "access": { @@ -108,16 +97,9 @@ V2_AUTH_RESPONSE = jsonutils.dumps({ }) V3_URL = "%sv3" % BASE_URL -V3_MEDIA_TYPES = [{'base': 'application/json', - 'type': 'application/vnd.openstack.identity-v3+json'}, - {'base': 'application/xml', - 'type': 'application/vnd.openstack.identity-v3+xml'}] - -V3_VERSION = {'id': 'v3.0', - 'links': [{'href': V3_URL, 'rel': 'self'}], - 'media-types': V3_MEDIA_TYPES, - 'status': 'stable', - 'updated': UPDATED} +V3_VERSION = fixture.V3Discovery(V3_URL) +V3_MEDIA_TYPES = V3_VERSION.media_types +V3_VERSION.updated_str = UPDATED V3_TOKEN = six.u('3e2813b7ba0b4006840c3825860b86ed'), V3_AUTH_RESPONSE = jsonutils.dumps({ @@ -434,12 +416,9 @@ class ClientDiscoveryTests(utils.TestCase): self.assertVersionNotAvailable(auth_url=V3_URL, version=2) def test_discover_unstable_versions(self): - v3_unstable_version = V3_VERSION.copy() - v3_unstable_version['status'] = 'beta' - version_list = _create_version_list([v3_unstable_version, V2_VERSION]) - + version_list = fixture.DiscoveryList(BASE_URL, v3_status='beta') httpretty.register_uri(httpretty.GET, BASE_URL, status=300, - body=version_list) + body=jsonutils.dumps(version_list)) self.assertCreatesV2(auth_url=BASE_URL) self.assertVersionNotAvailable(auth_url=BASE_URL, version=3) @@ -489,23 +468,15 @@ class ClientDiscoveryTests(utils.TestCase): self.assertCreatesV2(auth_url=BASE_URL) def test_greater_version_than_required(self): - resp = [{'id': 'v3.6', - 'links': [{'href': V3_URL, 'rel': 'self'}], - 'media-types': V3_MEDIA_TYPES, - 'status': 'stable', - 'updated': UPDATED}] + versions = fixture.DiscoveryList(BASE_URL, v3_id='v3.6') httpretty.register_uri(httpretty.GET, BASE_URL, status=200, - body=_create_version_list(resp)) + body=jsonutils.dumps(versions)) self.assertCreatesV3(auth_url=BASE_URL, version=(3, 4)) def test_lesser_version_than_required(self): - resp = [{'id': 'v3.4', - 'links': [{'href': V3_URL, 'rel': 'self'}], - 'media-types': V3_MEDIA_TYPES, - 'status': 'stable', - 'updated': UPDATED}] + versions = fixture.DiscoveryList(BASE_URL, v3_id='v3.4') httpretty.register_uri(httpretty.GET, BASE_URL, status=200, - body=_create_version_list(resp)) + body=jsonutils.dumps(versions)) self.assertVersionNotAvailable(auth_url=BASE_URL, version=(3, 6)) def test_bad_response(self): @@ -557,13 +528,24 @@ class ClientDiscoveryTests(utils.TestCase): 'media-types': V3_MEDIA_TYPES, 'status': 'stable', 'updated': UPDATED} - body = _create_version_list([V4_VERSION, V3_VERSION, V2_VERSION]) - httpretty.register_uri(httpretty.GET, BASE_URL, status=300, body=body) + versions = fixture.DiscoveryList() + versions.add_version(V4_VERSION) + httpretty.register_uri(httpretty.GET, BASE_URL, status=300, + body=jsonutils.dumps(versions)) disc = discover.Discover(auth_url=BASE_URL) self.assertRaises(exceptions.DiscoveryFailure, disc.create_client, version=4) + def test_discovery_fail_for_missing_v3(self): + versions = fixture.DiscoveryList(v2=True, v3=False) + httpretty.register_uri(httpretty.GET, BASE_URL, status=300, + body=jsonutils.dumps(versions)) + + disc = discover.Discover(auth_url=BASE_URL) + self.assertRaises(exceptions.DiscoveryFailure, + disc.create_client, version=(3, 0)) + @httpretty.activate class DiscoverQueryTests(utils.TestCase): @@ -715,13 +697,10 @@ class DiscoverQueryTests(utils.TestCase): def test_allow_unknown(self): status = 'abcdef' - version_list = [{'id': 'v3.0', - 'links': [{'href': V3_URL, 'rel': 'self'}], - 'media-types': V3_MEDIA_TYPES, - 'status': status, - 'updated': UPDATED}] - body = jsonutils.dumps({'versions': version_list}) - httpretty.register_uri(httpretty.GET, BASE_URL, status=200, body=body) + version_list = fixture.DiscoveryList(BASE_URL, v2=False, + v3_status=status) + httpretty.register_uri(httpretty.GET, BASE_URL, status=200, + body=jsonutils.dumps(version_list)) disc = discover.Discover(auth_url=BASE_URL)