Files
python-barbicanclient/barbicanclient/test/test_client.py
Douglas Mendizabal ddec180c41 Refactored Client to use Keystone Sessions
This CR changes the Client to use Keystone Sessions exclusively. All
authentication that was previously available in barbicanclient is
removed so that we can defer all authentication to keystoneclient.

Unit tests for the client module have been refactored to test the new
functionality.

The README file has been updated to give an example of how to
authenticate with Identity v3 using Sessions.

The Client will now give priority to an endpoint that is passed in over
endpoints found in the service catalog, which closes bug #1276002.  This
is helpful for development, where you might be running an instance of
Barbican locally that uses a Keystone instance for authentication that
does not have the Barbican service in its catalog.

The CLI uses Keystone API v3 as the default authentication method, but
can also use v2.0.

DocImpact
Implements: blueprint use-keystone-sessions
Closes-Bug: #1276002

Change-Id: I9d07cb50143c1f5cafea48318018a205f745999f
2014-10-20 13:28:26 -05:00

320 lines
12 KiB
Python

# Copyright (c) 2013 Rackspace, Inc.
#
# 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 mock
import testtools
from barbicanclient import client
class TestClient(testtools.TestCase):
def setUp(self):
super(TestClient, self).setUp()
self.endpoint = 'http://localhost:9311'
self.project_id = 'project_id'
self.client = client.Client(endpoint=self.endpoint,
project_id=self.project_id)
class WhenTestingClientInit(TestClient):
def _get_fake_session(self):
sess = mock.MagicMock()
sess.get_endpoint.return_value = self.endpoint
return sess
def test_can_be_used_without_a_session(self):
c = client.Client(endpoint=self.endpoint,
project_id=self.project_id)
self.assertIsNotNone(c._session)
def test_api_version_is_appended_to_endpoint(self):
c = client.Client(endpoint=self.endpoint,
project_id=self.project_id)
self.assertEqual(c._base_url, 'http://localhost:9311/v1')
def test_default_headers_are_empty(self):
c = client.Client(session=self._get_fake_session())
self.assertIsInstance(c._default_headers, dict)
self.assertFalse(bool(c._default_headers))
def test_project_id_is_added_to_default_headers(self):
c = client.Client(endpoint=self.endpoint,
project_id=self.project_id)
self.assertIn('X-Project-Id', c._default_headers.keys())
self.assertEqual(c._default_headers['X-Project-Id'], self.project_id)
def test_error_thrown_when_no_session_and_no_endpoint(self):
self.assertRaises(ValueError, client.Client,
**{"project_id": self.project_id})
def test_error_thrown_when_no_session_and_no_project_id(self):
self.assertRaises(ValueError, client.Client,
**{"endpoint": self.endpoint})
def test_client_strips_trailing_slash_from_endpoint(self):
c = client.Client(endpoint=self.endpoint + '/',
project_id=self.project_id)
self.assertEqual(c._barbican_endpoint, self.endpoint)
def test_base_url_starts_with_endpoint_url(self):
c = client.Client(endpoint=self.endpoint, project_id=self.project_id)
self.assertTrue(c._base_url.startswith(self.endpoint))
def test_base_url_ends_with_default_api_version(self):
c = client.Client(endpoint=self.endpoint, project_id=self.project_id)
self.assertTrue(c._base_url.endswith(client._DEFAULT_API_VERSION))
def test_gets_endpoint_from_keystone_session(self):
c = client.Client(session=self._get_fake_session())
self.assertEqual(c._barbican_endpoint, self.endpoint)
class TestClientWithSession(testtools.TestCase):
def setUp(self):
super(TestClientWithSession, self).setUp()
self.endpoint = 'http://localhost:9311'
def _get_fake_session_with_status_code(self, status_code):
resp = mock.MagicMock()
resp.status_code = status_code
sess = mock.MagicMock()
sess.get.return_value = resp
sess.post.return_value = resp
sess.delete.return_value = resp
sess.get_endpoint.return_value = self.endpoint
return sess
class WhenTestingClientPost(TestClientWithSession):
def setUp(self):
super(WhenTestingClientPost, self).setUp()
self.session = self._get_fake_session_with_status_code(201)
self.client = client.Client(session=self.session)
def test_post_normalizes_url_with_traling_slash(self):
self.client._post(path='secrets', data={'test_data': 'test'})
args, kwargs = self.session.post.call_args
url = args[0]
self.assertTrue(url.endswith('/'))
def test_post_includes_content_type_header_of_application_json(self):
self.client._post(path='secrets', data={'test_data': 'test'})
args, kwargs = self.session.post.call_args
headers = kwargs.get('headers')
self.assertIn('Content-Type', headers.keys())
self.assertEqual(headers['Content-Type'], 'application/json')
def test_post_includes_default_headers(self):
self.client._default_headers = {'Test-Default-Header': 'test'}
self.client._post(path='secrets', data={'test_data': 'test'})
args, kwargs = self.session.post.call_args
headers = kwargs.get('headers')
self.assertIn('Test-Default-Header', headers.keys())
def test_post_checks_status_code(self):
self.client._check_status_code = mock.MagicMock()
self.client._post(path='secrets', data={'test_data': 'test'})
resp = self.session.post()
self.client._check_status_code.assert_called_with(resp)
class WhenTestingClientGet(TestClientWithSession):
def setUp(self):
super(WhenTestingClientGet, self).setUp()
self.session = self._get_fake_session_with_status_code(200)
self.client = client.Client(session=self.session)
self.headers = dict()
self.href = 'http://test_href'
def test_get_uses_href_as_is(self):
self.client._get(self.href)
args, kwargs = self.session.get.call_args
url = args[0]
self.assertEqual(url, self.href)
def test_get_passes_params(self):
params = object()
self.client._get(self.href, params=params)
args, kwargs = self.session.get.call_args
passed_params = kwargs.get('params')
self.assertIs(params, passed_params)
def test_get_includes_accept_header_of_application_json(self):
self.client._get(self.href)
args, kwargs = self.session.get.call_args
headers = kwargs.get('headers')
self.assertIn('Accept', headers.keys())
self.assertEqual(headers['Accept'], 'application/json')
def test_get_includes_default_headers(self):
self.client._default_headers = {'Test-Default-Header': 'test'}
self.client._get(self.href)
args, kwargs = self.session.get.call_args
headers = kwargs.get('headers')
self.assertIn('Test-Default-Header', headers.keys())
def test_get_checks_status_code(self):
self.client._check_status_code = mock.MagicMock()
self.client._get(self.href)
resp = self.session.get()
self.client._check_status_code.assert_called_with(resp)
def test_get_raw_uses_href_as_is(self):
self.client._get_raw(self.href, self.headers)
args, kwargs = self.session.get.call_args
url = args[0]
self.assertEqual(url, self.href)
def test_get_raw_passes_headers(self):
self.client._get_raw(self.href, self.headers)
args, kwargs = self.session.get.call_args
headers = kwargs.get('headers')
self.assertIs(headers, self.headers)
def test_get_raw_includes_default_headers(self):
self.client._default_headers = {'Test-Default-Header': 'test'}
self.client._get_raw(self.href, self.headers)
self.assertIn('Test-Default-Header', self.headers.keys())
def test_get_raw_checks_status_code(self):
self.client._check_status_code = mock.MagicMock()
self.client._get_raw(self.href, self.headers)
resp = self.session.get()
self.client._check_status_code.assert_called_with(resp)
class WhenTestingClientDelete(TestClientWithSession):
def setUp(self):
super(WhenTestingClientDelete, self).setUp()
self.session = self._get_fake_session_with_status_code(200)
self.client = client.Client(session=self.session)
self.href = 'http://test_href'
def test_delete_uses_href_as_is(self):
self.client._delete(self.href)
args, kwargs = self.session.delete.call_args
url = args[0]
self.assertEqual(url, self.href)
def test_delete_passes_json(self):
json = '{"test": "test"}'
self.client._delete(self.href, json=json)
args, kwargs = self.session.delete.call_args
passed_json = kwargs.get('json')
self.assertEqual(passed_json, json)
def test_delete_includes_default_headers(self):
self.client._default_headers = {'Test-Default-Header': 'test'}
self.client._delete(self.href)
args, kwargs = self.session.delete.call_args
headers = kwargs.get('headers')
self.assertIn('Test-Default-Header', headers.keys())
def test_delete_checks_status_code(self):
self.client._check_status_code = mock.MagicMock()
self.client._delete(self.href)
resp = self.session.get()
self.client._check_status_code.assert_called_with(resp)
class WhenTestingCheckStatusCodes(TestClient):
def test_raises_http_auth_error_for_401_response(self):
resp = mock.MagicMock()
resp.status_code = 401
self.assertRaises(client.HTTPAuthError, self.client._check_status_code,
resp)
def test_raises_http_server_error_for_500_response(self):
resp = mock.MagicMock()
resp.status_code = 500
self.assertRaises(client.HTTPServerError,
self.client._check_status_code, resp)
def test_raises_http_client_error_for_400_response(self):
resp = mock.MagicMock()
resp.status_code = 400
self.assertRaises(client.HTTPClientError,
self.client._check_status_code, resp)
class WhenTestingGetErrorMessage(TestClient):
def test_gets_error_message_from_title_in_json(self):
resp = mock.MagicMock()
resp.json.return_value = {'title': 'test_text'}
msg = self.client._get_error_message(resp)
self.assertEqual(msg, 'test_text')
def test_gets_error_message_from_content_when_no_json(self):
resp = mock.MagicMock()
resp.json.side_effect = ValueError()
resp.content = content = 'content'
msg = self.client._get_error_message(resp)
self.assertEqual(msg, content)
class BaseEntityResource(testtools.TestCase):
# TODO: The compatibility of unittest between versions is horrible
# Reported as https://bugs.launchpad.net/testtools/+bug/1373139
if hasattr(testtools.TestCase, 'assertItemsEqual'):
# If this function is available, do nothing (PY27)
pass
elif hasattr(testtools.TestCase, 'assertCountEqual'):
# If this function is available, alias it (PY32+)
assertItemsEqual = testtools.TestCase.assertCountEqual
else:
# If neither is available, make our own version (PY26, PY30-31)
def assertItemsEqual(self, expected_seq, actual_seq, msg=None):
first_seq, second_seq = list(expected_seq), list(actual_seq)
differences = []
for item in first_seq:
if item not in second_seq:
differences.append(item)
for item in second_seq:
if item not in first_seq:
differences.append(item)
if differences:
if not msg:
msg = "Items differ: {0}".format(differences)
self.fail(msg)
if len(first_seq) != len(second_seq):
if not msg:
msg = "Size of collection differs: {0} != {1}".format(
len(first_seq), len(second_seq)
)
self.fail(msg)
def _setUp(self, entity):
super(BaseEntityResource, self).setUp()
self.endpoint = 'http://localhost:9311'
self.project_id = '1234567'
self.entity = entity
self.entity_base = self.endpoint + "/" + self.entity + "/"
self.entity_href = self.entity_base + \
'abcd1234-eabc-5678-9abc-abcdef012345'
self.api = mock.MagicMock()
self.api._base_url = self.endpoint