Commit 863c276d authored by Stan Hu's avatar Stan Hu

Merge branch '10io-cleanup-multipart-uploaded-file-specs' into 'master'

Simplify multipart spec

See merge request gitlab-org/gitlab!40226
parents cad1d0b0 485d5ce4
......@@ -57,7 +57,8 @@ module Gitlab
yield
ensure
@open_files.each(&:close)
@open_files.compact
.each(&:close)
end
# This function calls itself recursively
......@@ -122,6 +123,7 @@ module Gitlab
def allowed_paths
[
Dir.tmpdir,
::FileUploader.root,
::Gitlab.config.uploads.storage_path,
::JobArtifactUploader.workhorse_upload_path,
......
......@@ -52,8 +52,7 @@ class UploadedFile
elsif path.present?
file_path = File.realpath(path)
paths = Array(upload_paths) << Dir.tmpdir
unless self.allowed_path?(file_path, paths.compact)
unless self.allowed_path?(file_path, Array(upload_paths).compact)
raise InvalidPathError, "insecure path used '#{file_path}'"
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Middleware::Multipart::Handler do
using RSpec::Parameterized::TableSyntax
let_it_be(:env) { Rack::MockRequest.env_for('/', method: 'post', params: {}) }
let_it_be(:message) { { 'rewritten_fields' => {} } }
describe '#allowed_paths' do
let_it_be(:expected_allowed_paths) do
[
Dir.tmpdir,
::FileUploader.root,
::Gitlab.config.uploads.storage_path,
::JobArtifactUploader.workhorse_upload_path,
::LfsObjectUploader.workhorse_upload_path,
File.join(Rails.root, 'public/uploads/tmp')
]
end
let_it_be(:expected_with_packages_path) { expected_allowed_paths + [::Packages::PackageFileUploader.workhorse_upload_path] }
subject { described_class.new(env, message).send(:allowed_paths) }
where(:package_features_enabled, :object_storage_enabled, :direct_upload_enabled, :expected_paths) do
false | false | true | :expected_allowed_paths
false | false | false | :expected_allowed_paths
false | true | true | :expected_allowed_paths
false | true | false | :expected_allowed_paths
true | false | true | :expected_with_packages_path
true | false | false | :expected_with_packages_path
true | true | true | :expected_allowed_paths
true | true | false | :expected_with_packages_path
end
with_them do
before do
stub_config(packages: {
enabled: package_features_enabled,
object_store: {
enabled: object_storage_enabled,
direct_upload: direct_upload_enabled
},
storage_path: '/any/dir'
})
end
it { is_expected.to eq(send(expected_paths)) }
end
end
end
......@@ -2,312 +2,139 @@
require 'spec_helper'
require 'tempfile'
RSpec.describe Gitlab::Middleware::Multipart do
include_context 'multipart middleware context'
RSpec.shared_examples_for 'multipart upload files' do
it 'opens top-level files' do
Tempfile.open('top-level') do |tempfile|
rewritten = { 'file' => tempfile.path }
in_params = { 'file.name' => original_filename, 'file.path' => file_path, 'file.remote_id' => remote_id, 'file.size' => file_size }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(file))
include MultipartHelpers
describe '#call' do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:secret) { Gitlab::Workhorse.secret }
let(:issuer) { 'gitlab-workhorse' }
subject do
env = post_env(
rewritten_fields: rewritten_fields,
params: params,
secret: secret,
issuer: issuer
)
middleware.call(env)
end
end
it 'opens files one level deep' do
Tempfile.open('one-level') do |tempfile|
rewritten = { 'user[avatar]' => tempfile.path }
in_params = { 'user' => { 'avatar' => { '.name' => original_filename, '.path' => file_path, '.remote_id' => remote_id, '.size' => file_size } } }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(user avatar))
context 'with remote file mode params' do
let(:mode) { :remote }
middleware.call(env)
end
end
it_behaves_like 'handling all upload parameters conditions'
it 'opens files two levels deep' do
Tempfile.open('two-levels') do |tempfile|
in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => original_filename, '.path' => file_path, '.remote_id' => remote_id, '.size' => file_size } } } }
rewritten = { 'project[milestone][themesong]' => tempfile.path }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
context 'and a path set' do
include_context 'with one temporary file for multipart'
expect_uploaded_file(tempfile, %w(project milestone themesong))
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
let(:params) { upload_parameters_for(key: 'file', filename: filename, remote_id: remote_id).merge('file.path' => '/should/not/be/read') }
middleware.call(env)
end
end
it 'builds an UploadedFile' do
expect_uploaded_files(original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file))
def expect_uploaded_file(tempfile, path)
expect(app).to receive(:call) do |env|
file = get_params(env).dig(*path)
expect(file).to be_a(::UploadedFile)
expect(file.original_filename).to eq(original_filename)
if remote_id
expect(file.remote_id).to eq(remote_id)
expect(file.path).to be_nil
else
expect(file.path).to eq(File.realpath(tempfile.path))
expect(file.remote_id).to be_nil
end
subject
end
end
end
RSpec.shared_examples_for 'handling CI artifact upload' do
it 'uploads both file and metadata' do
Tempfile.open('file') do |file|
Tempfile.open('metadata') do |metadata|
rewritten = { 'file' => file.path, 'metadata' => metadata.path }
in_params = { 'file.name' => 'file.txt', 'file.path' => file_path, 'file.remote_id' => file_remote_id, 'file.size' => file_size, 'metadata.name' => 'metadata.gz' }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
with_expected_uploaded_artifact_files(file, metadata) do |uploaded_file, uploaded_metadata|
expect(uploaded_file).to be_a(::UploadedFile)
expect(uploaded_file.original_filename).to eq('file.txt')
if file_remote_id
expect(uploaded_file.remote_id).to eq(file_remote_id)
expect(uploaded_file.size).to eq(file_size)
expect(uploaded_file.path).to be_nil
else
expect(uploaded_file.path).to eq(File.realpath(file.path))
expect(uploaded_file.remote_id).to be_nil
end
context 'local file mode' do
let(:mode) { :local }
expect(uploaded_metadata).to be_a(::UploadedFile)
expect(uploaded_metadata.original_filename).to eq('metadata.gz')
expect(uploaded_metadata.path).to eq(File.realpath(metadata.path))
expect(uploaded_metadata.remote_id).to be_nil
end
it_behaves_like 'handling all upload parameters conditions'
middleware.call(env)
end
end
end
context 'when file is' do
include_context 'with one temporary file for multipart'
def with_expected_uploaded_artifact_files(file, metadata)
expect(app).to receive(:call) do |env|
file = get_params(env).dig('file')
metadata = get_params(env).dig('metadata')
let(:allowed_paths) { [Dir.tmpdir] }
yield file, metadata
end
end
end
it 'rejects headers signed with the wrong secret' do
env = post_env({ 'file' => '/var/empty/nonesuch' }, {}, 'x' * 32, 'gitlab-workhorse')
expect { middleware.call(env) }.to raise_error(JWT::VerificationError)
before do
expect_next_instance_of(::Gitlab::Middleware::Multipart::Handler) do |handler|
expect(handler).to receive(:allowed_paths).and_return(allowed_paths)
end
it 'rejects headers signed with the wrong issuer' do
env = post_env({ 'file' => '/var/empty/nonesuch' }, {}, Gitlab::Workhorse.secret, 'acme-inc')
expect { middleware.call(env) }.to raise_error(JWT::InvalidIssuerError)
end
context 'with invalid rewritten field' do
invalid_field_names = [
'[file]',
';file',
'file]',
';file]',
'file]]',
'file;;'
]
context 'in allowed paths' do
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename, remote_id: remote_id) }
invalid_field_names.each do |invalid_field_name|
it "rejects invalid rewritten field name #{invalid_field_name}" do
env = post_env({ invalid_field_name => nil }, {}, Gitlab::Workhorse.secret, 'gitlab-workhorse')
it 'builds an UploadedFile' do
expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file))
expect { middleware.call(env) }.to raise_error(RuntimeError, "invalid field: \"#{invalid_field_name}\"")
end
subject
end
end
context 'with remote file' do
let(:remote_id) { 'someid' }
let(:file_size) { 300 }
let(:file_path) { '' }
context 'not in allowed paths' do
let(:allowed_paths) { [] }
it_behaves_like 'multipart upload files'
end
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file') }
context 'with remote file and a file path set' do
let(:remote_id) { 'someid' }
let(:file_size) { 300 }
let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
it 'returns an error' do
result = subject
it_behaves_like 'multipart upload files'
expect(result[0]).to eq(400)
expect(result[2]).to include('insecure path used')
end
context 'with local file' do
let(:remote_id) { nil }
let(:file_size) { nil }
let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
it_behaves_like 'multipart upload files'
end
context 'with remote CI artifact upload' do
let(:file_remote_id) { 'someid' }
let(:file_size) { 300 }
let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
it_behaves_like 'handling CI artifact upload'
end
context 'with local CI artifact upload' do
let(:file_remote_id) { nil }
let(:file_size) { nil }
let(:file_path) { 'not_a_valid_file_path' } # file path will come from the rewritten_fields
it_behaves_like 'handling CI artifact upload'
end
it 'allows files in uploads/tmp directory' do
with_tmp_dir('public/uploads/tmp') do |dir, env|
expect(app).to receive(:call) do |env|
expect(get_params(env)['file']).to be_a(::UploadedFile)
end
context 'with dummy params in remote mode' do
let(:rewritten_fields) { { 'file' => 'should/not/be/read' } }
let(:params) { upload_parameters_for(key: 'file') }
let(:mode) { :remote }
middleware.call(env)
end
end
context 'with an invalid secret' do
let(:secret) { 'INVALID_SECRET' }
it 'allows files in the job artifact upload path' do
with_tmp_dir('artifacts') do |dir, env|
expect(JobArtifactUploader).to receive(:workhorse_upload_path).and_return(File.join(dir, 'artifacts'))
expect(app).to receive(:call) do |env|
expect(get_params(env)['file']).to be_a(::UploadedFile)
it { expect { subject }.to raise_error(JWT::VerificationError) }
end
middleware.call(env)
end
end
context 'with an invalid issuer' do
let(:issuer) { 'INVALID_ISSUER' }
it 'allows files in the lfs upload path' do
with_tmp_dir('lfs-objects') do |dir, env|
expect(LfsObjectUploader).to receive(:workhorse_upload_path).and_return(File.join(dir, 'lfs-objects'))
expect(app).to receive(:call) do |env|
expect(get_params(env)['file']).to be_a(::UploadedFile)
it { expect { subject }.to raise_error(JWT::InvalidIssuerError) }
end
middleware.call(env)
end
end
context 'with invalid rewritten field key' do
invalid_keys = [
'[file]',
';file',
'file]',
';file]',
'file]]',
'file;;'
]
it 'allows symlinks for uploads dir' do
Tempfile.open('two-levels') do |tempfile|
symlinked_dir = '/some/dir/uploads'
symlinked_path = File.join(symlinked_dir, File.basename(tempfile.path))
env = post_env({ 'file' => symlinked_path }, { 'file.name' => original_filename, 'file.path' => symlinked_path }, Gitlab::Workhorse.secret, 'gitlab-workhorse')
allow(FileUploader).to receive(:root).and_return(symlinked_dir)
allow(UploadedFile).to receive(:allowed_paths).and_return([symlinked_dir, Gitlab.config.uploads.storage_path])
allow(File).to receive(:realpath).and_call_original
allow(File).to receive(:realpath).with(symlinked_dir).and_return(Dir.tmpdir)
allow(File).to receive(:realpath).with(symlinked_path).and_return(tempfile.path)
allow(File).to receive(:exist?).and_call_original
allow(File).to receive(:exist?).with(symlinked_dir).and_return(true)
# override Dir.tmpdir because this dir is in the list of allowed paths
# and it would match FileUploader.root path (which in this test is linked
# to /tmp too)
allow(Dir).to receive(:tmpdir).and_return(File.join(Dir.tmpdir, 'tmpsubdir'))
invalid_keys.each do |invalid_key|
context invalid_key do
let(:rewritten_fields) { { invalid_key => 'should/not/be/read' } }
expect(app).to receive(:call) do |env|
expect(get_params(env)['file']).to be_a(::UploadedFile)
it { expect { subject }.to raise_error(RuntimeError, "invalid field: \"#{invalid_key}\"") }
end
middleware.call(env)
end
end
describe '#call' do
context 'with packages storage' do
using RSpec::Parameterized::TableSyntax
let(:storage_path) { 'shared/packages' }
context 'with invalid key in parameters' do
include_context 'with one temporary file for multipart'
RSpec.shared_examples 'allowing the multipart upload' do
it 'allows files to be uploaded' do
with_tmp_dir('tmp/uploads', storage_path) do |dir, env|
allow(Packages::PackageFileUploader).to receive(:root).and_return(File.join(dir, storage_path))
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'wrong_key', filename: filename, remote_id: remote_id) }
it 'builds no UploadedFile' do
expect(app).to receive(:call) do |env|
expect(get_params(env)['file']).to be_a(::UploadedFile)
received_params = get_params(env)
expect(received_params['file']).to be_nil
expect(received_params['wrong_key']).to be_nil
end
middleware.call(env)
end
subject
end
end
RSpec.shared_examples 'not allowing the multipart upload when package upload path is used' do
it 'does not allow files to be uploaded' do
with_tmp_dir('tmp/uploads', storage_path) do |dir, env|
# with_tmp_dir sets the same workhorse_upload_path for all Uploaders,
# so we have to prevent JobArtifactUploader and LfsObjectUploader to
# allow the tested path
allow(JobArtifactUploader).to receive(:workhorse_upload_path).and_return(Dir.tmpdir)
allow(LfsObjectUploader).to receive(:workhorse_upload_path).and_return(Dir.tmpdir)
status, headers, body = middleware.call(env)
expect(status).to eq(400)
expect(headers).to eq({ 'Content-Type' => 'text/plain' })
expect(body).to start_with('insecure path used')
end
end
end
RSpec.shared_examples 'adding package storage to multipart allowed paths' do
before do
expect(::Packages::PackageFileUploader).to receive(:workhorse_upload_path).and_call_original
end
it_behaves_like 'allowing the multipart upload'
end
RSpec.shared_examples 'not adding package storage to multipart allowed paths' do
before do
expect(::Packages::PackageFileUploader).not_to receive(:workhorse_upload_path)
end
it_behaves_like 'not allowing the multipart upload when package upload path is used'
end
where(:object_storage_enabled, :direct_upload_enabled, :example_name) do
false | true | 'adding package storage to multipart allowed paths'
false | false | 'adding package storage to multipart allowed paths'
true | true | 'not adding package storage to multipart allowed paths'
true | false | 'adding package storage to multipart allowed paths'
end
with_them do
before do
stub_config(packages: {
enabled: true,
object_store: {
enabled: object_storage_enabled,
direct_upload: direct_upload_enabled
},
storage_path: storage_path
})
end
it_behaves_like params[:example_name]
end
end
end
end
......@@ -23,7 +23,7 @@ RSpec.describe UploadedFile do
end
subject do
described_class.from_params(params, :file, upload_path, file_path_override)
described_class.from_params(params, :file, [upload_path, Dir.tmpdir], file_path_override)
end
context 'when valid file is specified' do
......
# frozen_string_literal: true
module MultipartHelpers
def post_env(rewritten_fields:, params:, secret:, issuer:)
token = JWT.encode({ 'iss' => issuer, 'rewritten_fields' => rewritten_fields }, secret, 'HS256')
Rack::MockRequest.env_for(
'/',
method: 'post',
params: params,
described_class::RACK_ENV_KEY => token
)
end
# This function assumes a `mode` variable to be set
def upload_parameters_for(filepath: nil, key: nil, filename: 'filename', remote_id: 'remote_id')
result = {
"#{key}.name" => filename,
"#{key}.type" => "application/octet-stream",
"#{key}.sha256" => "1234567890"
}
case mode
when :local
result["#{key}.path"] = filepath
when :remote
result["#{key}.remote_id"] = remote_id
result["#{key}.size"] = 3.megabytes
else
raise ArgumentError, "can't handle #{mode} mode"
end
result
end
# This function assumes a `mode` variable to be set
def rewritten_fields_hash(hash)
if mode == :remote
# For remote uploads, workhorse still submits rewritten_fields,
# but all the values are empty strings.
hash.keys.each { |k| hash[k] = '' }
end
hash
end
def expect_uploaded_files(uploaded_file_expectations)
expect(app).to receive(:call) do |env|
Array.wrap(uploaded_file_expectations).each do |expectation|
file = get_params(env).dig(*expectation[:params_path])
expect_uploaded_file(file, expectation)
end
end
end
# This function assumes a `mode` variable to be set
def expect_uploaded_file(file, expectation)
expect(file).to be_a(::UploadedFile)
expect(file.original_filename).to eq(expectation[:original_filename])
expect(file.sha256).to eq('1234567890')
case mode
when :local
expect(file.path).to eq(File.realpath(expectation[:filepath]))
expect(file.remote_id).to be_nil
expect(file.size).to eq(expectation[:size])
when :remote
expect(file.remote_id).to eq(expectation[:remote_id])
expect(file.path).to be_nil
expect(file.size).to eq(3.megabytes)
else
raise ArgumentError, "can't handle #{mode} mode"
end
end
# Rails doesn't combine the GET/POST parameters in
# ActionDispatch::HTTP::Parameters if action_dispatch.request.parameters is set:
# https://github.com/rails/rails/blob/aea6423f013ca48f7704c70deadf2cd6ac7d70a1/actionpack/lib/action_dispatch/http/parameters.rb#L41
def get_params(env)
req = ActionDispatch::Request.new(env)
req.GET.merge(req.POST)
end
end
# frozen_string_literal: true
RSpec.shared_context 'multipart middleware context' do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:original_filename) { 'filename' }
# Rails 5 doesn't combine the GET/POST parameters in
# ActionDispatch::HTTP::Parameters if action_dispatch.request.parameters is set:
# https://github.com/rails/rails/blob/aea6423f013ca48f7704c70deadf2cd6ac7d70a1/actionpack/lib/action_dispatch/http/parameters.rb#L41
def get_params(env)
req = ActionDispatch::Request.new(env)
req.GET.merge(req.POST)
end
# This context provides one temporary file for the multipart spec
#
# Here are the available variables:
# - uploaded_file
# - uploaded_filepath
# - filename
# - remote_id
RSpec.shared_context 'with one temporary file for multipart' do |within_tmp_sub_dir: false|
let(:uploaded_filepath) { uploaded_file.path }
around do |example|
Tempfile.open('uploaded_file2') do |tempfile|
@uploaded_file = tempfile
@filename = 'test_file.png'
@remote_id = 'remote_id'
def post_env(rewritten_fields, params, secret, issuer)
token = JWT.encode({ 'iss' => issuer, 'rewritten_fields' => rewritten_fields }, secret, 'HS256')
Rack::MockRequest.env_for(
'/',
method: 'post',
params: params,
described_class::RACK_ENV_KEY => token
)
example.run
end
end
def with_tmp_dir(uploads_sub_dir, storage_path = '')
Dir.mktmpdir do |dir|
upload_dir = File.join(dir, storage_path, uploads_sub_dir)
FileUtils.mkdir_p(upload_dir)
attr_reader :uploaded_file, :filename, :remote_id
end
allow(Rails).to receive(:root).and_return(dir)
allow(Dir).to receive(:tmpdir).and_return(File.join(Dir.tmpdir, 'tmpsubdir'))
allow(GitlabUploader).to receive(:root).and_return(File.join(dir, storage_path))
# This context provides two temporary files for the multipart spec
#
# Here are the available variables:
# - uploaded_file
# - uploaded_filepath
# - filename
# - remote_id
# - tmp_sub_dir (only when using within_tmp_sub_dir: true)
# - uploaded_file2
# - uploaded_filepath2
# - filename2
# - remote_id2
RSpec.shared_context 'with two temporary files for multipart' do
include_context 'with one temporary file for multipart'
Tempfile.open('top-level', upload_dir) do |tempfile|
env = post_env({ 'file' => tempfile.path }, { 'file.name' => original_filename, 'file.path' => tempfile.path }, Gitlab::Workhorse.secret, 'gitlab-workhorse')
let(:uploaded_filepath2) { uploaded_file2.path }
yield dir, env
around do |example|
Tempfile.open('uploaded_file2') do |tempfile|
@uploaded_file2 = tempfile
@filename2 = 'test_file2.png'
@remote_id2 = 'remote_id2'
example.run
end
end
attr_reader :uploaded_file2, :filename2, :remote_id2
end
# This context provides three temporary files for the multipart spec
#
# Here are the available variables:
# - uploaded_file
# - uploaded_filepath
# - filename
# - remote_id
# - tmp_sub_dir (only when using within_tmp_sub_dir: true)
# - uploaded_file2
# - uploaded_filepath2
# - filename2
# - remote_id2
# - uploaded_file3
# - uploaded_filepath3
# - filename3
# - remote_id3
RSpec.shared_context 'with three temporary files for multipart' do
include_context 'with two temporary files for multipart'
let(:uploaded_filepath3) { uploaded_file3.path }
around do |example|
Tempfile.open('uploaded_file3') do |tempfile|
@uploaded_file3 = tempfile
@filename3 = 'test_file3.png'
@remote_id3 = 'remote_id3'
example.run
end
end
attr_reader :uploaded_file3, :filename3, :remote_id3
end
# frozen_string_literal: true
RSpec.shared_examples 'handling all upload parameters conditions' do
context 'one root parameter' do
include_context 'with one temporary file for multipart'
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename, remote_id: remote_id) }
it 'builds an UploadedFile' do
expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file))
subject
end
end
context 'two root parameters' do
include_context 'with two temporary files for multipart'
let(:rewritten_fields) { rewritten_fields_hash('file1' => uploaded_filepath, 'file2' => uploaded_filepath2) }
let(:params) do
upload_parameters_for(filepath: uploaded_filepath, key: 'file1', filename: filename, remote_id: remote_id).merge(
upload_parameters_for(filepath: uploaded_filepath2, key: 'file2', filename: filename2, remote_id: remote_id2)
)
end
it 'builds UploadedFiles' do
expect_uploaded_files([
{ filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file1) },
{ filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(file2) }
])
subject
end
end
context 'one nested parameter' do
include_context 'with one temporary file for multipart'
let(:rewritten_fields) { rewritten_fields_hash('user[avatar]' => uploaded_filepath) }
let(:params) { { 'user' => { 'avatar' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id) } } }
it 'builds an UploadedFile' do
expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar))
subject
end
end
context 'two nested parameters' do
include_context 'with two temporary files for multipart'
let(:rewritten_fields) { rewritten_fields_hash('user[avatar]' => uploaded_filepath, 'user[screenshot]' => uploaded_filepath2) }
let(:params) do
{
'user' => {
'avatar' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id),
'screenshot' => upload_parameters_for(filepath: uploaded_filepath2, filename: filename2, remote_id: remote_id2)
}
}
end
it 'builds UploadedFiles' do
expect_uploaded_files([
{ filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar) },
{ filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user screenshot) }
])
subject
end
end
context 'one deeply nested parameter' do
include_context 'with one temporary file for multipart'
let(:rewritten_fields) { rewritten_fields_hash('user[avatar][bananas]' => uploaded_filepath) }
let(:params) { { 'user' => { 'avatar' => { 'bananas' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id) } } } }
it 'builds an UploadedFile' do
expect_uploaded_files(filepath: uploaded_file, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar bananas))
subject
end
end
context 'two deeply nested parameters' do
include_context 'with two temporary files for multipart'
let(:rewritten_fields) { rewritten_fields_hash('user[avatar][bananas]' => uploaded_filepath, 'user[friend][ananas]' => uploaded_filepath2) }
let(:params) do
{
'user' => {
'avatar' => {
'bananas' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id)
},
'friend' => {
'ananas' => upload_parameters_for(filepath: uploaded_filepath2, filename: filename2, remote_id: remote_id2)
}
}
}
end
it 'builds UploadedFiles' do
expect_uploaded_files([
{ filepath: uploaded_file, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar bananas) },
{ filepath: uploaded_file2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user friend ananas) }
])
subject
end
end
context 'three parameters nested at different levels' do
include_context 'with three temporary files for multipart'
let(:rewritten_fields) do
rewritten_fields_hash(
'file' => uploaded_filepath,
'user[avatar]' => uploaded_filepath2,
'user[friend][avatar]' => uploaded_filepath3
)
end
let(:params) do
upload_parameters_for(filepath: uploaded_filepath, filename: filename, key: 'file', remote_id: remote_id).merge(
'user' => {
'avatar' => upload_parameters_for(filepath: uploaded_filepath2, filename: filename2, remote_id: remote_id2),
'friend' => {
'avatar' => upload_parameters_for(filepath: uploaded_filepath3, filename: filename3, remote_id: remote_id3)
}
}
)
end
it 'builds UploadedFiles' do
expect_uploaded_files([
{ filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file) },
{ filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user avatar) },
{ filepath: uploaded_filepath3, original_filename: filename3, remote_id: remote_id3, size: uploaded_file3.size, params_path: %w(user friend avatar) }
])
subject
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment