Testing Request Body Parameter Processing in HTTParty
This test suite validates the HTTParty::Request::Body functionality, focusing on request body handling, parameter processing, and multipart form data management in the HTTParty library.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
jnunemaker/httparty
spec/httparty/request/body_spec.rb
require 'spec_helper'
require 'tempfile'
RSpec.describe HTTParty::Request::Body do
describe '#call' do
let(:options) { {} }
subject { described_class.new(params, **options).call }
context 'when params is string' do
let(:params) { 'name=Bob%20Jones' }
it { is_expected.to eq params }
end
context 'when params is hash' do
let(:params) { { people: ["Bob Jones", "Mike Smith"] } }
let(:converted_params) { "people%5B%5D=Bob%20Jones&people%5B%5D=Mike%20Smith"}
it { is_expected.to eq converted_params }
context 'when params has file' do
before do
allow(HTTParty::Request::MultipartBoundary)
.to receive(:generate).and_return("------------------------c772861a5109d5ef")
end
let(:file) { File.open('spec/fixtures/tiny.gif') }
let(:params) do
{
user: {
avatar: file,
first_name: 'John',
last_name: 'Doe',
enabled: true
}
}
end
let(:expected_file_name) { 'tiny.gif' }
let(:expected_file_contents) { "GIF89a\u0001\u0000\u0001\u0000\u0000\xFF\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0000;" }
let(:expected_content_type) { 'image/gif' }
let(:multipart_params) do
"--------------------------c772861a5109d5ef\r\n" \
"Content-Disposition: form-data; name=\"user[avatar]\"; filename=\"#{expected_file_name}\"\r\n" \
"Content-Type: #{expected_content_type}\r\n" \
"\r\n" \
"#{expected_file_contents}\r\n" \
"--------------------------c772861a5109d5ef\r\n" \
"Content-Disposition: form-data; name=\"user[first_name]\"\r\n" \
"\r\n" \
"John\r\n" \
"--------------------------c772861a5109d5ef\r\n" \
"Content-Disposition: form-data; name=\"user[last_name]\"\r\n" \
"\r\n" \
"Doe\r\n" \
"--------------------------c772861a5109d5ef\r\n" \
"Content-Disposition: form-data; name=\"user[enabled]\"\r\n" \
"\r\n" \
"true\r\n" \
"--------------------------c772861a5109d5ef--\r\n"
end
it { is_expected.to eq multipart_params }
it { expect { subject }.not_to change { file.pos } }
context 'when passing multipart as an option' do
let(:options) { { force_multipart: true } }
let(:params) do
{
user: {
first_name: 'John',
last_name: 'Doe',
enabled: true
}
}
end
let(:multipart_params) do
"--------------------------c772861a5109d5ef\r\n" \
"Content-Disposition: form-data; name=\"user[first_name]\"\r\n" \
"\r\n" \
"John\r\n" \
"--------------------------c772861a5109d5ef\r\n" \
"Content-Disposition: form-data; name=\"user[last_name]\"\r\n" \
"\r\n" \
"Doe\r\n" \
"--------------------------c772861a5109d5ef\r\n" \
"Content-Disposition: form-data; name=\"user[enabled]\"\r\n" \
"\r\n" \
"true\r\n" \
"--------------------------c772861a5109d5ef--\r\n"
end
it { is_expected.to eq multipart_params }
end
context 'file object responds to original_filename' do
let(:some_temp_file) { Tempfile.new(['some_temp_file','.gif']) }
let(:expected_file_name) { "some_temp_file.gif" }
let(:expected_file_contents) { "Hello" }
let(:file) { double(:mocked_action_dispatch, path: some_temp_file.path, original_filename: 'some_temp_file.gif', read: expected_file_contents) }
before { some_temp_file.write('Hello') }
it { is_expected.to eq multipart_params }
end
context 'when file name contains [ " \r \n ]' do
let(:options) { { force_multipart: true } }
let(:some_temp_file) { Tempfile.new(['basefile', '.txt']) }
let(:file_content) { 'test' }
let(:raw_filename) { "dummy=tampering.sh\"; \r\ndummy=a.txt" }
let(:expected_file_name) { 'dummy=tampering.sh%22; %0D%0Adummy=a.txt' }
let(:file) { double(:mocked_action_dispatch, path: some_temp_file.path, original_filename: raw_filename, read: file_content) }
let(:params) do
{
user: {
attachment_file: file,
enabled: true
}
}
end
let(:multipart_params) do
"--------------------------c772861a5109d5ef\r\n" \
"Content-Disposition: form-data; name=\"user[attachment_file]\"; filename=\"#{expected_file_name}\"\r\n" \
"Content-Type: text/plain\r\n" \
"\r\n" \
"test\r\n" \
"--------------------------c772861a5109d5ef\r\n" \
"Content-Disposition: form-data; name=\"user[enabled]\"\r\n" \
"\r\n" \
"true\r\n" \
"--------------------------c772861a5109d5ef--\r\n"
end
it { is_expected.to eq multipart_params }
end
end
end
end
describe '#multipart?' do
let(:force_multipart) { false }
let(:file) { File.open('spec/fixtures/tiny.gif') }
subject { described_class.new(params, force_multipart: force_multipart).multipart? }
context 'when params does not respond to to_hash' do
let(:params) { 'name=Bob%20Jones' }
it { is_expected.to be false }
end
context 'when params responds to to_hash' do
class HashLike
def initialize(hash)
@hash = hash
end
def to_hash
@hash
end
end
class ArrayLike
def initialize(ary)
@ary = ary
end
def to_ary
@ary
end
end
context 'when force_multipart is true' do
let(:params) { { name: 'Bob Jones' } }
let(:force_multipart) { true }
it { is_expected.to be true }
end
context 'when it does not contain a file' do
let(:hash_like_param) { HashLike.new(first: 'Bob', last: ArrayLike.new(['Jones'])) }
let(:params) { { name: ArrayLike.new([hash_like_param]) } }
it { is_expected.to eq false }
end
context 'when it contains file' do
let(:hash_like_param) { HashLike.new(first: 'Bob', last: 'Jones', file: ArrayLike.new([file])) }
let(:params) { { name: ArrayLike.new([hash_like_param]) } }
it { is_expected.to be true }
end
end
end
end