vcrpy
vcrpy copied to clipboard
Add support for matching request body multipart form data
Request bodies of content type multipart/form-data can have different "boundary" separators, but contain identical data. This can cause VCRPy's "body" matcher to fail on otherwise identical requests.
This adds support for parsing the encoded body data, essentially stripping out the "boundary" values, and comparing the contents.
Also, a similar approach could be used for handling "replace_post_data_parameters" mentioned in https://github.com/kevin1024/vcrpy/issues/521.
Testing Notes
Added 3 unit tests to confirm matching behavior, using sample 1x1 PNG image data. and differing "boundary" delimiters.
Implementation Notes
- This adds a dependency on requests_toolbelt, for parsing encoded multipart form data. If this is acceptable, my dependency configuration may need some polish.
- Modified the matcher "transformer" functions to take an additional
headersparameter, as this is needed to determine the multipart form data "boundary" separator.
@kevin1024 Any plans for integrating this?
@mparent61 Is there any workaround right now? I have a multpart/form-data and I cannot use the cassete I record (which is actually a sending and recieving a small file).
@mezhaka - I've been using this workaround --
import re
import pytest
from requests_toolbelt.multipart import decoder
from vcr.matchers import _get_transformer, _identity
from vcr.util import read_body
def decode_multipart_request_body(request):
return decoder.MultipartDecoder(
content=request.body, content_type=request.headers["content-type"]
)
def is_multipart_form_data_request(request):
return re.match(
r"multipart/form-data(;|$)", request.headers.get("content-type", "").lower()
)
# This is a workaround for request body "multipart form" handling
def request_body_matcher(r1, r2) -> None:
if is_multipart_form_data_request(r1) and is_multipart_form_data_request(r2):
decoded1 = decode_multipart_request_body(r1)
decoded2 = decode_multipart_request_body(r2)
assert decoded1.encoding == decoded2.encoding
assert len(decoded1.parts) == len(decoded2.parts)
for part1, part2 in zip(decoded1.parts, decoded2.parts):
assert part1.headers == part2.headers
assert (
part1.content == part2.content
), f"Multipart data doesn't match for part with content type: {part1.headers}"
else:
# Copied directly from VCRPy's `body` matcher. For some reason PyTest's custom ASSERT handlers (with
# much better diagnostic info) does not work when the assert fires inside VCRPy.
transformer = _get_transformer(r1)
r2_transformer = _get_transformer(r2)
if transformer != r2_transformer:
transformer = _identity
body1 = read_body(r1)
body2 = read_body(r2)
assert transformer(body1) == transformer(body2)
and then to setup -
@pytest.fixture(scope="module")
def vcr(vcr):
# Override VCRPy's default body matcher to support Multipart form data, and provide better ASSERT messages.
vcr.register_matcher("body", request_body_matcher)
return vcr