Add BetaMax Fixture
Add a BetaMax fixture that handles wrapping the constructor for KeystoneAuth Session in a way to provide the BetaMax interfaces. This is in support of enabling easy functional testing that includes responses to live-clouds for consumers of KeystoneAuth. A future patch will provide a template for stripping out sensitive (e.g. password) data from the recordings so that the cassetts can be more easily included in git repositories for functional testing. Change-Id: Ibd6e525fef9f2541f97d33de0d2f02f991d122e5 Depends-On: I8787954ab5d0e3963e97c160783301b48ab8299f
This commit is contained in:
parent
78b491cbbe
commit
491e7160cf
74
keystoneauth1/fixture/keystoneauth_betamax.py
Normal file
74
keystoneauth1/fixture/keystoneauth_betamax.py
Normal file
@ -0,0 +1,74 @@
|
||||
# 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.
|
||||
|
||||
"""A fixture to wrap the session constructor for use with Betamax"""
|
||||
|
||||
from functools import partial
|
||||
|
||||
import betamax
|
||||
import fixtures
|
||||
import mock
|
||||
import requests
|
||||
|
||||
from keystoneauth1 import session
|
||||
|
||||
|
||||
class BetamaxFixture(fixtures.Fixture):
|
||||
|
||||
def __init__(self, cassette_name, cassette_library_dir=None,
|
||||
serializer=None, record=False):
|
||||
self.cassette_library_dir = cassette_library_dir
|
||||
self.serializer = serializer
|
||||
self.record = record
|
||||
self.cassette_name = cassette_name
|
||||
if serializer:
|
||||
betamax.Betamax.register_serializer(serializer)
|
||||
|
||||
def setUp(self):
|
||||
super(BetamaxFixture, self).setUp()
|
||||
self.mockpatch = mock.patch.object(
|
||||
session, '_construct_session',
|
||||
partial(_construct_session_with_betamax, self))
|
||||
self.mockpatch.start()
|
||||
# Unpatch during cleanup
|
||||
self.addCleanup(self.mockpatch.stop)
|
||||
|
||||
|
||||
def _construct_session_with_betamax(fixture, session_obj=None):
|
||||
# NOTE(morganfainberg): This function should contain the logic of
|
||||
# keystoneauth1.session._construct_session as it replaces the
|
||||
# _construct_session function to apply betamax magic to the requests
|
||||
# session object.
|
||||
if not session_obj:
|
||||
session_obj = requests.Session()
|
||||
# Use TCPKeepAliveAdapter to fix bug 1323862
|
||||
for scheme in session_obj.adapters.keys():
|
||||
session_obj.mount(scheme, session.TCPKeepAliveAdapter())
|
||||
fixture.recorder = betamax.Betamax(
|
||||
session_obj, cassette_library_dir=fixture.cassette_library_dir)
|
||||
|
||||
record = 'none'
|
||||
serializer = None
|
||||
|
||||
if fixture.record:
|
||||
record = 'all'
|
||||
|
||||
if fixture.serializer:
|
||||
serializer = fixture.serializer.name
|
||||
|
||||
fixture.recorder.use_cassette(fixture.cassette_name,
|
||||
serialize_with=serializer,
|
||||
record=record)
|
||||
|
||||
fixture.recorder.start()
|
||||
fixture.addCleanup(fixture.recorder.stop)
|
||||
return session_obj
|
@ -41,6 +41,18 @@ USER_AGENT = 'keystoneauth1'
|
||||
_logger = utils.get_logger(__name__)
|
||||
|
||||
|
||||
def _construct_session(session_obj=None):
|
||||
# NOTE(morganfainberg): if the logic in this function changes be sure to
|
||||
# update the betamax fixture's '_construct_session_with_betamax" function
|
||||
# as well.
|
||||
if not session_obj:
|
||||
session_obj = requests.Session()
|
||||
# Use TCPKeepAliveAdapter to fix bug 1323862
|
||||
for scheme in session_obj.adapters.keys():
|
||||
session_obj.mount(scheme, TCPKeepAliveAdapter())
|
||||
return session_obj
|
||||
|
||||
|
||||
class _JSONEncoder(json.JSONEncoder):
|
||||
|
||||
def default(self, o):
|
||||
@ -101,14 +113,9 @@ class Session(object):
|
||||
def __init__(self, auth=None, session=None, original_ip=None, verify=True,
|
||||
cert=None, timeout=None, user_agent=None,
|
||||
redirect=_DEFAULT_REDIRECT_LIMIT):
|
||||
if not session:
|
||||
session = requests.Session()
|
||||
# Use TCPKeepAliveAdapter to fix bug 1323862
|
||||
for scheme in session.adapters.keys():
|
||||
session.mount(scheme, TCPKeepAliveAdapter())
|
||||
|
||||
self.auth = auth
|
||||
self.session = session
|
||||
self.session = _construct_session(session)
|
||||
self.original_ip = original_ip
|
||||
self.verify = verify
|
||||
self.cert = cert
|
||||
|
7
keystoneauth1/tests/unit/data/README
Normal file
7
keystoneauth1/tests/unit/data/README
Normal file
@ -0,0 +1,7 @@
|
||||
This directory holds the betamax test cassettes that are pre-generated
|
||||
for unit testing. This can be removed in the future with a functional
|
||||
test that stands up a full devstack, records a cassette and then
|
||||
replays it as part of the test suite.
|
||||
|
||||
Until the functional testing is implemented do not remove this
|
||||
directory or enclosed files.
|
@ -0,0 +1,2 @@
|
||||
{"http_interactions": [{"request": {"body": {"string": "{\"auth\": {\"tenantName\": \"test_tenant_name\", \"passwordCredentials\": {\"username\": \"test_user_name\", \"password\": \"test_password\"}}}", "encoding": "utf-8"}, "headers": {"Content-Length": ["128"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["application/json"], "User-Agent": ["keystoneauth1"], "Connection": ["keep-alive"], "Content-Type": ["application/json"]}, "method": "POST", "uri": "http://keystonauth.betamax_test/v2.0/tokens"}, "response": {"body": {"string": "{\"access\": {\"token\": {\"issued_at\": \"2015-11-27T15:17:19.755470\", \"expires\": \"2015-11-27T16:17:19Z\", \"id\": \"c000c5ee4ba04594a00886028584b50d\", \"tenant\": {\"description\": null, \"enabled\": true, \"id\": \"6932cad596634a61ac9c759fb91beef1\", \"name\": \"test_tenant_name\"}, \"audit_ids\": [\"jY3gYg_YTbmzY2a4ioGuCw\"]}, \"user\": {\"username\": \"test_user_name\", \"roles_links\": [], \"id\": \"96995e6cc15b40fa8e7cd762f6a5d4c0\", \"roles\": [{\"name\": \"_member_\"}], \"name\": \"67eff5f6-9477-4961-88b4-437e6596a795\"}, \"metadata\": {\"is_admin\": 0, \"roles\": [\"9fe2ff9ee4384b1894a90878d3e92bab\"]}}}", "encoding": null}, "headers": {"X-Openstack-Request-Id": ["req-f9e188b4-06fd-4a4c-a952-2315b368218c"], "Content-Length": ["2684"], "Connection": ["keep-alive"], "Date": ["Fri, 27 Nov 2015 15:17:19 GMT"], "Content-Type": ["application/json"], "Vary": ["X-Auth-Token"], "X-Distribution": ["Ubuntu"], "Server": ["Fake"]}, "status": {"message": "OK", "code": 200}, "url": "http://keystonauth.betamax_test/v2.0/tokens"}, "recorded_at": "2015-11-27T15:17:19"}], "recorded_with": "betamax/0.5.1"}
|
||||
|
60
keystoneauth1/tests/unit/test_betamax_fixture.py
Normal file
60
keystoneauth1/tests/unit/test_betamax_fixture.py
Normal file
@ -0,0 +1,60 @@
|
||||
# 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 testtools
|
||||
|
||||
from betamax import exceptions
|
||||
|
||||
from keystoneauth1.fixture import keystoneauth_betamax
|
||||
from keystoneauth1.fixture import v2 as v2Fixtures
|
||||
from keystoneauth1.identity import v2
|
||||
from keystoneauth1 import session
|
||||
|
||||
|
||||
class TestBetamaxFixture(testtools.TestCase):
|
||||
|
||||
TEST_USERNAME = 'test_user_name'
|
||||
TEST_PASSWORD = 'test_password'
|
||||
TEST_TENANT_NAME = 'test_tenant_name'
|
||||
TEST_AUTH_URL = 'http://keystonauth.betamax_test/v2.0/'
|
||||
|
||||
V2_TOKEN = v2Fixtures.Token(tenant_name=TEST_TENANT_NAME,
|
||||
user_name=TEST_USERNAME)
|
||||
|
||||
def setUp(self):
|
||||
super(TestBetamaxFixture, self).setUp()
|
||||
self.ksa_betamax_fixture = self.useFixture(
|
||||
keystoneauth_betamax.BetamaxFixture(
|
||||
cassette_name='ksa_betamax_test_cassette',
|
||||
cassette_library_dir='keystoneauth1/tests/unit/data/',
|
||||
record=False))
|
||||
|
||||
def _replay_cassette(self):
|
||||
plugin = v2.Password(
|
||||
auth_url=self.TEST_AUTH_URL,
|
||||
password=self.TEST_PASSWORD,
|
||||
username=self.TEST_USERNAME,
|
||||
tenant_name=self.TEST_TENANT_NAME)
|
||||
s = session.Session()
|
||||
s.get_token(auth=plugin)
|
||||
|
||||
def test_keystoneauth_betamax_fixture(self):
|
||||
self._replay_cassette()
|
||||
|
||||
def test_replay_of_bad_url_fails(self):
|
||||
plugin = v2.Password(
|
||||
auth_url='http://invalid-auth-url/v2.0/',
|
||||
password=self.TEST_PASSWORD,
|
||||
username=self.TEST_USERNAME,
|
||||
tenant_name=self.TEST_TENANT_NAME)
|
||||
s = session.Session()
|
||||
self.assertRaises(exceptions.BetamaxError, s.get_token, auth=plugin)
|
@ -27,6 +27,10 @@ kerberos =
|
||||
requests-kerberos>=0.6:python_version=='2.7' or python_version=='2.6' # MIT
|
||||
saml2 =
|
||||
lxml>=2.3
|
||||
betamax =
|
||||
betamax>=0.5.1
|
||||
fixtures>=1.3.1
|
||||
mock>=1.2
|
||||
|
||||
[entry_points]
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user