Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for matching request body multipart form data #647

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

mparent61
Copy link

@mparent61 mparent61 commented May 11, 2022

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 #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 headers parameter, as this is needed to determine the multipart form data "boundary" separator.

@mezhaka
Copy link

mezhaka commented Oct 27, 2023

@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).

@mparent61
Copy link
Author

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants