ArgumentError: WebMock does not support matching body for multipart/form-data requests yet :(
:(
Is there an open issue for this?
No there isn't.
It's not easy to fix (that's why it hasn't been fixed yet :). Direct body matching won't work as body parts boundaries change on each requested. The only way to handle that would be to split parts and match each of them.
If someone has any idea how to deal with it, a pull request is always welcome.
Out of curiosity, what used to happen, did it just ignore the body? I ask as we had a bunch of passing tests and after updating to webmock 2.x I started getting this error and the tests started failing, so I'm curious what the proper way to emulate the previous behavior is (I'm guessing just removing the body match).
@chadcf matching multipart body was never supported and never worked.
The error messages were unclear so the assertion has been added to 2.x recently,
which raises an error on an attempt to match multipart body.
I'm surprised that it used to work for you before. How have you been matching the body?
yes I was pretty surprised too!
I just updated the app from 1.18 to 2.1.0 and these stubs which used to work started throwing the error:
stub_request(:post, url)
.with(headers: { "Content-Type" => %r[multipart/form-data] })
.with(body: %r[#{Regexp.escape attachment_data.read}])
I see. Yes, regexp would have worked before as it doesn't care about boundaries.
Perhaps the error should be raised only if trying to match body against hash.
You can use the block form:
stub_request(:post, "http://example.com").
with(:headers => { "Content-Type" => %r|\Amultipart/form-data| }) { |request| request.body =~ /some-part-of-body/ }
Works for me under 2.3.2.
@sshaw it works only because most likely your http client creates part boundary strings which are the same for every request, therefore the generated body is always the same.
This can't be guaranteed though as various clients will generate different boundary strings and these strings can be different across requests.
It only works because I'm using a regex and not trying to match an entire part, just contents. Boundary string is not relevant . (Maybe regex can be update to indicate this better?)
Right, that works too :)
You can use regexp to match body without a block. Webmock supports body to
be declared as regexp.
On Tue, 3 Jan 2017 at 17:41, Skye Shaw notifications@github.com wrote:
It only works because I'm using a regex and not trying to match an entire
part, just contents. Boundary string is not relevant .—
You are receiving this because you commented.Reply to this email directly, view it on GitHub
https://github.com/bblimke/webmock/issues/623#issuecomment-270159252,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAA8u-d6mTJADEsG450FShU3yJjycmnOks5rOnpIgaJpZM4Io8QD
.
You can use regexp to match body without a block. Webmock supports body to
be declared as regexp.
Not for multipart/for-data (hence this issue). It results in: ArgumentError: WebMock does not support matching body for multipart/form-data requests yet
Using 2.3.2.
😿
With 3.0.1 and 2.3.2 I notice that when trying to follow one of the examples above if I try test with
stub_request(:post, 'http://mockserver.com/api/v2/')
.with(body: %r[.*],
headers: default_headers('Authorization' => 'Basic ZblahC5jb236cGFzcblah=',
'Content-Length' => '258',
'Content-Type' => 'multipart/form-data; boundary=-----------RubyMultipartPost')) do |request|
boundry = Regexp.quote(request.headers['Content-Type'][/ boundary=(.+)/, 1])
request.body == %r{
start
-------------#{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test_file\"\r\nContent-Length: 38\r\nContent-Type: image/jpeg\r\nContent-Transfer-Encoding: binary\r\n\r\nPellentesque dapibus suscipit ligula.\n\r\n-------------#{boundary}--\r\n\r\n
end
}x
end.to_return(status: 200, body: '', headers: {})
I get
ArgumentError:
WebMock does not support matching body for multipart/form-data requests yet :(
# /Users/rsim/.gem/ruby/2.2.3/gems/webmock-2.3.2/lib/webmock/request_pattern.rb:263:in `assert_non_multipart_body'
# /Users/rsim/.gem/ruby/2.2.3/gems/webmock-2.3.2/lib/webmock/request_pattern.rb:231:in `matches?'
# /Users/rsim/.gem/ruby/2.2.3/gems/webmock-2.3.2/lib/webmock/request_pattern.rb:34:in `matches?'
# /Users/rsim/.gem/ruby/2.2.3/gems/webmock-2.3.2/lib/webmock/stub_registry.rb:58:in `block in request_stub_for'
# /Users/rsim/.gem/ruby/2.2.3/gems/webmock-2.3.2/lib/webmock/stub_registry.rb:57:in `each'
# /Users/rsim/.gem/ruby/2.2.3/gems/webmock-2.3.2/lib/webmock/stub_registry.rb:57:in `detect'
# /Users/rsim/.gem/ruby/2.2.3/gems/webmock-2.3.2/lib/webmock/stub_registry.rb:57:in `request_stub_for'
# /Users/rsim/.gem/ruby/2.2.3/gems/webmock-2.3.2/lib/webmock/stub_registry.rb:50:in `response_for_request'
# /Users/rsim/.gem/ruby/2.2.3/gems/webmock-2.3.2/lib/webmock/http_lib_adapters/net_http.rb:79:in `request'
And if i omit the body section from the with call, then i get WebMock::NetConnectNotAllowedError
where it shows that there is a mock endpoint is registered but it dosen't match the multipart one. The only difference I can see is the body doesn't exist for the stub.
@russell it's because of this part .with(body: %r[.*],
please remove body: %r[.*]
completely.
It's not necessary since you are matching body inside the block anyway.
I also upgraded to 3.0.1 recently, and tests began to fail with this error too. My test was to ensure that two files were sent, like so:
expect(a_request(:post, "https://domain.com/").
with(:body => (a_string_matching(/Content-Disposition: form-data;.+ filename="result1.tap"/).
and a_string_matching(/Content-Disposition: form-data;.+ filename="result2.tap"/)),
:headers => {'Content-Type' => 'multipart/form-data; boundary=-----------RubyMultipartPost'})
).to have_been_made
I had to rewrite it like this to prevent raising the exception
expect(a_request(:post, "https://domain.com/").
with { |req|
expect(req.body).to match(/Content-Disposition: form-data;.+ filename="result1.tap"/).
and match(/Content-Disposition: form-data;.+ filename="result2.tap"/)
expect(req.headers).to include('Content-Type' => 'multipart/form-data; boundary=-----------RubyMultipartPost')
}).to have_been_made
also getting this when upgrading to ruby and upgrading this lib.
You can use the block form:
stub_request(:post, "http://example.com"). with(:headers => { "Content-Type" => %r|\Amultipart/form-data| }) { |request| request.body =~ /some-part-of-body/ }
Works for me under 2.3.2.
What if you want to use to_return
?
stub_request(:post, "https://upload.box.com/api/2.0/files/content").
with(:headers => { "Content-Type" => %r|\Amultipart/form-data| }) { |request| request.body =~ /PDF/ }.
to_return(:status => 200, :body => {message: 'file uploaded'}.to_json, :headers => {})
I get ArgumentError:
invalid byte sequence in UTF-8
What if you want to use
to_return
? ...
I getArgumentError: invalid byte sequence in UTF-8
Likely has nothing to do with to_return
but rather request.body
is not encoded in UTF-8.
Stacktrace can help here.
I realise this is old, but I have solved this in an almost nice way with the following, using this gem https://github.com/danabr/multipart-parser
require "multipart_parser/reader"
MultipartPart = Struct.new(:filename, :name, :content_type, :data)
def parse_multipart_request(request)
boundary = MultipartParser::Reader.extract_boundary_value(request.headers["Content-Type"])
r = MultipartParser::Reader.new(boundary)
parts = []
r.on_part do |p|
part = MultipartPart.new(
p.filename,
p.name,
p.mime,
""
)
p.on_data do |data|
part.data += data
end
parts << part
end
r.write(request.body)
parts
end
stub_request(
:post,
"/my-endpoint"
) do |request|
parts = parse_multipart_request(request)
parts.length == 1 &&
parts.first.name == "image" &&
parts.first.filename == "cat.jpg" &&
parts.first.content_type == "image/jpeg" &&
parts.first.data == cat_jpg_data
end.to_return(body: "OK", status: 200)
I do not handle any errors from multipart-parser, but I guess if your code is generating an error, this will blow up or return incorrect data at some point, making the test fail, so good enough for me right now.
As a workaround, I'm using this:
headers = { 'Content-Type' => /multipart\/form-data/ }
match_multipart_body = ->(request) do
request.body.force_encoding('BINARY')
request.body =~ /Content-Type: image\/jpeg/
end
stub_request(:post, '/my-endpoint').
with(headers: headers, &match_multipart_body).
to_return(status: 200, body: successful_response)
Most helpful comment
As a workaround, I'm using this: