Skip to content

Commit

Permalink
Merge branch 'pr/98'
Browse files Browse the repository at this point in the history
  • Loading branch information
sigmavirus24 committed Apr 29, 2016
2 parents bd8f3a5 + 24202d4 commit 28a5e8f
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 30 deletions.
2 changes: 1 addition & 1 deletion betamax/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def load_cassette(self, cassette_name, serialize, options):
self.cassette_name = cassette_name
self.serialize = serialize
self.options.update(options.items())
placeholders = self.options.get('placeholders', [])
placeholders = self.options.get('placeholders', {})
cassette_options = {}

default_options = cassette.Cassette.default_cassette_options
Expand Down
35 changes: 30 additions & 5 deletions betamax/cassette/cassette.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ def __init__(self, cassette_name, serialization_format, **kwargs):
self.cassette_path = self.serializer.cassette_path

# Determine which placeholders to use
self.placeholders = kwargs.get('placeholders')
if not self.placeholders:
self.placeholders = defaults['placeholders']
default_placeholders = defaults['placeholders'][:]
cassette_placeholders = kwargs.get('placeholders', [])
self.placeholders = merge_placeholder_lists(default_placeholders,
cassette_placeholders)

# Determine whether to preserve exact body bytes
self.preserve_exact_body_bytes = _option_from(
Expand Down Expand Up @@ -171,11 +172,11 @@ def load_interactions(self):

for i in self.interactions:
dispatch_hooks('before_playback', i, self)
i.replace_all(self.placeholders, ('placeholder', 'replace'))
i.replace_all(self.placeholders, False)

def sanitize_interactions(self):
for i in self.interactions:
i.replace_all(self.placeholders)
i.replace_all(self.placeholders, True)

def save_interaction(self, response, request):
serialized_data = self.serialize_interaction(response, request)
Expand Down Expand Up @@ -210,6 +211,30 @@ def _save_cassette(self):
self.serializer.serialize(cassette_data)


class Placeholder(collections.namedtuple('Placeholder',
'placeholder replace')):
"""Encapsulate some logic about Placeholders."""

@classmethod
def from_dict(cls, dictionary):
return cls(**dictionary)

def unpack(self, serializing):
if serializing:
return self.replace, self.placeholder
else:
return self.placeholder, self.replace


def merge_placeholder_lists(defaults, overrides):
overrides = [Placeholder.from_dict(override) for override in overrides]
overrides_dict = dict((p.placeholder, p) for p in overrides)
placeholders = [overrides_dict.pop(p.placeholder, p)
for p in map(Placeholder.from_dict, defaults)]
return placeholders + [p for p in overrides
if p.placeholder in overrides_dict]


def dispatch_hooks(hook_name, *args):
"""Dispatch registered hooks."""
# Cassette.hooks is a dictionary that defaults to an empty list,
Expand Down
7 changes: 3 additions & 4 deletions betamax/cassette/interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,10 @@ def replace(self, text_to_replace, placeholder):
self.replace_in_body(text_to_replace, placeholder)
self.replace_in_uri(text_to_replace, placeholder)

def replace_all(self, replacements, key_order=('replace', 'placeholder')):
def replace_all(self, replacements, serializing):
"""Easy way to accept all placeholders registered."""
(replace_key, placeholder_key) = key_order
for r in replacements:
self.replace(r[replace_key], r[placeholder_key])
for placeholder in replacements:
self.replace(*placeholder.unpack(serializing))

def replace_in_headers(self, text_to_replace, placeholder):
for obj in ('request', 'response'):
Expand Down
2 changes: 1 addition & 1 deletion betamax/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,5 @@ def define_cassette_placeholder(self, placeholder, replace):
"""
self.default_cassette_options['placeholders'].append({
'placeholder': placeholder,
'replace': replace
'replace': replace,
})
1 change: 1 addition & 0 deletions betamax/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def validate_serializer(serializer):


def validate_placeholders(placeholders):
"""Validate placeholders is a dict-like structure"""
keys = ['placeholder', 'replace']
return all(
sorted(list(p.keys())) == keys for p in placeholders
Expand Down
14 changes: 6 additions & 8 deletions docs/configuring.rst
Original file line number Diff line number Diff line change
Expand Up @@ -244,14 +244,12 @@ example above and convert it.
session = requests.Session()
recorder = betamax.Betamax(session, default_cassette_options={
'placeholders': [
{
'placeholder': '<GITHUB-AUTH>',
'replace': base64.b64encode(
'{0}:{1}'.format(username, password).encode('utf-8')
)
}
]
'placeholders': [{
'placeholder': '<GITHUB-AUTH>',
'replace': base64.b64encode(
'{0}:{1}'.format(username, password).encode('utf-8')
),
}]
})
Note that what we passed as our first argument is assigned to the
Expand Down
10 changes: 4 additions & 6 deletions tests/integration/test_placeholders.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,21 @@ def tearDown(self):

def test_placeholders_work(self):
placeholders = Cassette.default_cassette_options['placeholders']
placeholder = {
assert placeholders == [{
'placeholder': '<AUTHORIZATION>',
'replace': b64_foobar
}
assert placeholders != []
assert placeholder in placeholders
'replace': b64_foobar,
}]

s = self.session
cassette = None
with Betamax(s).use_cassette('test_placeholders') as recorder:
r = s.get('http://httpbin.org/get', auth=('foo', 'bar'))
cassette = recorder.current_cassette
self.cassette_path = cassette.cassette_path
assert r.status_code == 200
auth = r.json()['headers']['Authorization']
assert b64_foobar in auth

#cassette.sanitize_interactions()
self.cassette_path = cassette.cassette_path
i = cassette.interactions[0]
auth = i.data['request']['headers']['Authorization']
Expand Down
33 changes: 32 additions & 1 deletion tests/unit/test_cassette.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from datetime import datetime

from betamax import __version__
from betamax import cassette
from betamax.cassette import cassette
from betamax import mock_response
from betamax import recorder
from betamax import serializers
from betamax import util
from requests.models import Response, Request
Expand Down Expand Up @@ -182,6 +183,36 @@ def test_add_urllib3_response(self):
mock_response.MockHTTPResponse)


def test_cassette_initialization():
serializers.serializer_registry['test'] = Serializer()
cassette.Cassette.default_cassette_options['placeholders'] = []

with recorder.Betamax.configure() as config:
config.define_cassette_placeholder('<TO-OVERRIDE>', 'default')
config.define_cassette_placeholder('<KEEP-DEFAULT>', 'config')
placeholders = [{
'placeholder': '<TO-OVERRIDE>',
'replace': 'override',
}, {
'placeholder': '<ONLY-OVERRIDE>',
'replace': 'cassette',
}]
instance = cassette.Cassette(
'test_cassette',
'test',
placeholders=placeholders
)

expected = [
cassette.Placeholder('<TO-OVERRIDE>', 'override'),
cassette.Placeholder('<KEEP-DEFAULT>', 'config'),
cassette.Placeholder('<ONLY-OVERRIDE>', 'cassette'),
]
assert instance.placeholders == expected

cassette.Cassette.default_cassette_options['placeholders'] = []


class TestCassette(unittest.TestCase):
cassette_name = 'test_cassette'

Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ def test_is_a_context_manager(self):
def test_allows_registration_of_placeholders(self):
opts = copy.deepcopy(Cassette.default_cassette_options)
c = Configuration()

c.define_cassette_placeholder('<TEST>', 'test')

assert opts != Cassette.default_cassette_options
placeholders = Cassette.default_cassette_options['placeholders']
assert placeholders[0]['placeholder'] == '<TEST>'
Expand Down
18 changes: 15 additions & 3 deletions tests/unit/test_recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from betamax import matchers, serializers
from betamax.adapter import BetamaxAdapter
from betamax.cassette import Cassette
from betamax.cassette import cassette
from betamax.recorder import Betamax
from requests import Session
from requests.adapters import HTTPAdapter
Expand All @@ -18,6 +18,16 @@ def test_initialization_does_not_alter_the_session(self):
assert not isinstance(v, BetamaxAdapter)
assert isinstance(v, HTTPAdapter)

def test_initialization_converts_placeholders(self):
placeholders = [{'placeholder': '<FOO>', 'replace': 'replace-with'}]
default_cassette_options = {'placeholders': placeholders}
self.vcr = Betamax(self.session,
default_cassette_options=default_cassette_options)
assert self.vcr.config.default_cassette_options['placeholders'] == [{
'placeholder': '<FOO>',
'replace': 'replace-with',
}]

def test_entering_context_alters_adapters(self):
with self.vcr:
for v in self.session.adapters.values():
Expand All @@ -32,7 +42,7 @@ def test_exiting_resets_the_adapters(self):
def test_current_cassette(self):
assert self.vcr.current_cassette is None
self.vcr.use_cassette('test')
assert isinstance(self.vcr.current_cassette, Cassette)
assert isinstance(self.vcr.current_cassette, cassette.Cassette)

def test_use_cassette_returns_cassette_object(self):
assert self.vcr.use_cassette('test') is self.vcr
Expand Down Expand Up @@ -61,4 +71,6 @@ def test_stores_the_session_instance(self):
def test_use_cassette_passes_along_placeholders(self):
placeholders = [{'placeholder': '<FOO>', 'replace': 'replace-with'}]
self.vcr.use_cassette('test', placeholders=placeholders)
assert self.vcr.current_cassette.placeholders == placeholders
assert self.vcr.current_cassette.placeholders == [
cassette.Placeholder.from_dict(p) for p in placeholders
]

0 comments on commit 28a5e8f

Please sign in to comment.