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

Improved support for multipart/form-data example recording #891

Merged
merged 10 commits into from
Jul 14, 2023
3 changes: 1 addition & 2 deletions lib/apipie/extractor/collector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ def ignore_call?(record)
end

def handle_record(record)
add_to_records(record)
if ignore_call?(record)
Extractor.logger.info("REST_API: skipping #{record_to_s(record)}")
else
add_to_records(record)
refine_description(record)
end
end
Expand Down Expand Up @@ -114,4 +114,3 @@ def record_to_s(record)
end
end
end

30 changes: 29 additions & 1 deletion lib/apipie/extractor/recorder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ def analyze_functional_test(test_context)
@path = request.path
@params = request.request_parameters
if [:POST, :PUT, :PATCH, :DELETE].include?(@verb)
@request_data = @params
if request.content_type == "multipart/form-data"
@request_data = reformat_multipart_data(@params)
else
@request_data = @params
end
else
@query = request.query_string
end
Expand All @@ -68,6 +72,12 @@ def reformat_multipart_data(form)
form.each do |key, attrs|
if attrs.is_a?(String)
lines << boundary << content_disposition(key) << "Content-Length: #{attrs.size}" << '' << attrs
elsif attrs.is_a?(Rack::Test::UploadedFile) || attrs.is_a?(ActionDispatch::Http::UploadedFile)
reformat_uploaded_file(boundary, attrs, key, lines)
elsif attrs.is_a?(Array)
reformat_array(boundary, attrs, key, lines)
elsif attrs.is_a?(TrueClass) || attrs.is_a?(FalseClass)
reformat_boolean(boundary, attrs, key, lines)
else
reformat_hash(boundary, attrs, lines)
end
Expand All @@ -88,6 +98,24 @@ def reformat_hash(boundary, attrs, lines)
end
end

def reformat_boolean(boundary, attrs, key, lines)
lines << boundary << content_disposition(key)
lines << '' << attrs.to_s
end

def reformat_array(boundary, attrs, key, lines)
attrs.each do |item|
lines << boundary << content_disposition("#{key}[]")
lines << '' << item
end
end

def reformat_uploaded_file(boundary, file, key, lines)
lines << boundary << %{#{content_disposition(key)}; filename="#{file.original_filename}"}
lines << "Content-Length: #{file.size}" << "Content-Type: #{file.content_type}" << "Content-Transfer-Encoding: binary"
lines << '' << %{... contents of "#{key}" ...}
end

def content_disposition(name)
%{Content-Disposition: form-data; name="#{name}"}
end
Expand Down
49 changes: 43 additions & 6 deletions spec/lib/apipie/extractor/recorder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

describe 'Apipie::Extractor::Recorder' do
let(:recorder) { Apipie::Extractor::Recorder.new }
let(:controller) do
controller = ActionController::Metal.new
controller.set_request!(request)
controller
end

describe '#analyse_controller' do
subject do
Expand All @@ -19,12 +24,6 @@
request
end

let(:controller) do
controller = ActionController::Metal.new
controller.set_request!(request)
controller
end

it { is_expected.to eq(action) }

context 'when a api_action_matcher is configured' do
Expand All @@ -37,4 +36,42 @@
it { is_expected.to eq(matcher_action) }
end
end

describe '#analyse_functional_test' do
context 'with multipart-form data' do
let(:test_context) do
double(controller:, request:, response: ActionDispatch::Response.new(200))
end

let(:file) do
instance_double(
ActionDispatch::Http::UploadedFile,
original_filename: 'file.txt',
content_type: 'text/plain',
size: '1MB'
)
end

let(:request) do
request = ActionDispatch::Request.new({})
request.request_method = 'POST'
request.headers['Content-Type'] = "multipart/form-data"
request.request_parameters = { file: }
request
end

subject do
recorder.analyse_controller(controller)
recorder.analyze_functional_test(test_context)
recorder.record[:request_data]
end

before do
allow(file).to receive(:is_a?).and_return(false)
allow(file).to receive(:is_a?).with(ActionDispatch::Http::UploadedFile).and_return(true)
end

it { is_expected.to include("filename=\"#{file.original_filename}\"") }
end
end
end