6da2d42137
This change allows the Betamax default workflow to be used via the fixture. That workflow is: - Write a test that uses Betamax but for which no cassette presently exists - Run test which causes Betamax to create the cassette and record the interactions - Re-run tests and ensure that no network activity has actually happened Keystoneauth1's YamlJsonSerializer relied on yaml.safe_load to return something other than None if a file was loaded. Unfortunately, if the file is zero-length then it will return None which breaks the above workflow. Instead, let's check the return value and if it is not None, return it, otherwise, simply return an empty dictionary as Betamax expects. Change-Id: I5b7d01439f391e2ecb589850e90573c74cd38080 Closes-bug: #1670697
99 lines
2.9 KiB
Python
99 lines
2.9 KiB
Python
# 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 six
|
|
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=six.text_type)
|
|
|
|
|
|
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(six.text_type, _unicode_representer)
|
|
|
|
return yaml.dump(
|
|
cassette_data, Dumper=MyDumper, default_flow_style=False)
|
|
|
|
def deserialize(self, cassette_data):
|
|
try:
|
|
deserialized = yaml.safe_load(cassette_data)
|
|
except yaml.error.YAMLError:
|
|
deserialized = None
|
|
|
|
if deserialized is not None:
|
|
return deserialized
|
|
return {}
|