From ba79174a1faef3e6b476672c6b9b169c15868d6b Mon Sep 17 00:00:00 2001 From: kg Date: Wed, 9 May 2018 20:44:28 +0000 Subject: [PATCH] fixes before_record_response mutates response When no cassette exists, it's expected that the response returned, should be the original, unchanged response. The response recorded in the cassette should be that which is returned by the before_record_response callback. But on subsequent requests/responses (when a cassette exists), the responses returned should be exactly what is in the cassette. resolves #355 --- tests/integration/test_stubs.py | 48 +++++++++++++++++++++++++++++++++ vcr/cassette.py | 4 +++ 2 files changed, 52 insertions(+) diff --git a/tests/integration/test_stubs.py b/tests/integration/test_stubs.py index 23bf8ec..cbbd045 100644 --- a/tests/integration/test_stubs.py +++ b/tests/integration/test_stubs.py @@ -1,5 +1,6 @@ import vcr import zlib +import json import six.moves.http_client as httplib from assertions import assert_is_json @@ -83,3 +84,50 @@ def test_original_decoded_response_is_not_modified(tmpdir, httpbin): assert 'content-encoding' not in inside.headers assert_is_json(inside.read()) + + +def _make_before_record_response(fields, replacement='[REDACTED]'): + def before_record_response(response): + string_body = response['body']['string'].decode('utf8') + body = json.loads(string_body) + + for field in fields: + if field in body: + body[field] = replacement + + response['body']['string'] = json.dumps(body).encode() + return response + return before_record_response + + +def test_original_response_is_not_modified_by_before_filter(tmpdir, httpbin): + testfile = str(tmpdir.join('sensitive_data_scrubbed_response.yml')) + host, port = httpbin.host, httpbin.port + field_to_scrub = 'url' + replacement = '[YOU_CANT_HAVE_THE_MANGO]' + + conn = httplib.HTTPConnection(host, port) + conn.request('GET', '/get') + outside = conn.getresponse() + + callback = _make_before_record_response([field_to_scrub], replacement) + with vcr.use_cassette(testfile, before_record_response=callback): + conn = httplib.HTTPConnection(host, port) + conn.request('GET', '/get') + inside = conn.getresponse() + + # The scrubbed field should be the same, because no cassette existed. + # Furthermore, the responses should be identical. + inside_body = json.loads(inside.read().decode('utf-8')) + outside_body = json.loads(outside.read().decode('utf-8')) + assert not inside_body[field_to_scrub] == replacement + assert inside_body[field_to_scrub] == outside_body[field_to_scrub] + + # Ensure that when a cassette exists, the scrubbed response is returned. + with vcr.use_cassette(testfile, before_record_response=callback): + conn = httplib.HTTPConnection(host, port) + conn.request('GET', '/get') + inside = conn.getresponse() + + inside_body = json.loads(inside.read().decode('utf-8')) + assert inside_body[field_to_scrub] == replacement diff --git a/vcr/cassette.py b/vcr/cassette.py index e22bbea..d64dec6 100644 --- a/vcr/cassette.py +++ b/vcr/cassette.py @@ -1,4 +1,5 @@ import collections +import copy import sys import inspect import logging @@ -222,6 +223,9 @@ class Cassette(object): request = self._before_record_request(request) if not request: return + # Deepcopy is here because mutation of `response` will corrupt the + # real response. + response = copy.deepcopy(response) response = self._before_record_response(response) if response is None: return