diff --git a/keystoneauth1/fixture/keystoneauth_betamax.py b/keystoneauth1/fixture/keystoneauth_betamax.py index b7d62958..724d217d 100644 --- a/keystoneauth1/fixture/keystoneauth_betamax.py +++ b/keystoneauth1/fixture/keystoneauth_betamax.py @@ -20,6 +20,7 @@ import mock import requests from keystoneauth1.fixture import hooks +from keystoneauth1.fixture import serializer as yaml_serializer from keystoneauth1 import session @@ -29,11 +30,12 @@ class BetamaxFixture(fixtures.Fixture): serializer=None, record=False, pre_record_hook=hooks.pre_record_hook): 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) + if not serializer: + serializer = yaml_serializer.YamlJsonSerializer + self.serializer = serializer + betamax.Betamax.register_serializer(serializer) self.pre_record_hook = pre_record_hook def setUp(self): diff --git a/keystoneauth1/fixture/serializer.py b/keystoneauth1/fixture/serializer.py new file mode 100644 index 00000000..de890929 --- /dev/null +++ b/keystoneauth1/fixture/serializer.py @@ -0,0 +1,92 @@ +# 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 serializer to emit YAML but with request body in nicely formatted JSON.""" + +import json +import os + +import betamax.serializers.base +import yaml + + +def _should_use_block(value): + for c in u"\u000a\u000d\u001c\u001d\u001e\u0085\u2028\u2029": + if c in value: + return True + return False + + +def _represent_scalar(self, tag, value, style=None): + if style is None: + if _should_use_block(value): + style = '|' + else: + style = self.default_style + + node = yaml.representer.ScalarNode(tag, value, style=style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + return node + + +def _unicode_representer(dumper, uni): + node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=uni) + return node + + +def _indent_json(val): + if not val: + return '' + return json.dumps( + json.loads(val), indent=2, + separators=(',', ': '), sort_keys=False, + default=unicode) + + +def _is_json_body(interaction): + content_type = interaction['headers'].get('Content-Type', []) + return 'application/json' in content_type + + +class YamlJsonSerializer(betamax.serializers.base.BaseSerializer): + + name = "yamljson" + + @staticmethod + def generate_cassette_name(cassette_library_dir, cassette_name): + return os.path.join( + cassette_library_dir, "{name}.yaml".format(name=cassette_name)) + + def serialize(self, cassette_data): + # Reserialize internal json with indentation + for interaction in cassette_data['http_interactions']: + for key in ('request', 'response'): + if _is_json_body(interaction[key]): + interaction[key]['body']['string'] = _indent_json( + interaction[key]['body']['string']) + + class MyDumper(yaml.Dumper): + """Specialized Dumper which does nice blocks and unicode.""" + + yaml.representer.BaseRepresenter.represent_scalar = _represent_scalar + MyDumper.add_representer(unicode, _unicode_representer) + + return yaml.dump( + cassette_data, Dumper=MyDumper, default_flow_style=False) + + def deserialize(self, cassette_data): + try: + # There should be only one document + return list(yaml.load_all(cassette_data))[0] + except yaml.error.YAMLError: + return {} diff --git a/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.json b/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.json deleted file mode 100644 index d6a023c2..00000000 --- a/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.json +++ /dev/null @@ -1,2 +0,0 @@ -{"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"} - diff --git a/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.yaml b/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.yaml new file mode 100644 index 00000000..c85b2282 --- /dev/null +++ b/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.yaml @@ -0,0 +1,92 @@ +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": { + "enabled": true, + "description": null, + "name": "test_tenant_name", + "id": "6932cad596634a61ac9c759fb91beef1" + }, + "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 + diff --git a/test-requirements.txt b/test-requirements.txt index 16e7dc32..c8b8c1a7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -23,3 +23,4 @@ sphinx!=1.3b1,<1.3,>=1.2.1 # BSD testrepository>=0.0.18 # Apache-2.0/BSD testresources>=0.2.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT +PyYAML>=3.1.0 # MIT