diff options
Diffstat (limited to 'spec')
-rw-r--r-- | spec/lib/gitlab/middleware/multipart_spec.rb | 109 | ||||
-rw-r--r-- | spec/lib/uploaded_file_spec.rb | 167 |
2 files changed, 245 insertions, 31 deletions
diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb index ec153e25d44..c99281ee12c 100644 --- a/spec/lib/gitlab/middleware/multipart_spec.rb +++ b/spec/lib/gitlab/middleware/multipart_spec.rb @@ -7,11 +7,11 @@ require 'tempfile' describe Gitlab::Middleware::Multipart do include_context 'multipart middleware context' - shared_examples_for 'multipart upload files' do + 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' => tempfile.path, 'file.remote_id' => remote_id } + 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)) @@ -22,8 +22,8 @@ describe Gitlab::Middleware::Multipart do it 'opens files one level deep' do Tempfile.open('one-level') do |tempfile| - in_params = { 'user' => { 'avatar' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } } 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)) @@ -34,7 +34,7 @@ describe Gitlab::Middleware::Multipart do it 'opens files two levels deep' do Tempfile.open('two-levels') do |tempfile| - in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } } } + 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') @@ -44,13 +44,61 @@ describe Gitlab::Middleware::Multipart do end end - def expect_uploaded_file(tempfile, path, remote: false) + 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.path).to eq(tempfile.path) expect(file.original_filename).to eq(original_filename) - expect(file.remote_id).to eq(remote_id) + + 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 + 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 + + 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 + + middleware.call(env) + end + end + end + + 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') + + yield file, metadata end end end @@ -67,18 +115,65 @@ describe Gitlab::Middleware::Multipart do 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;;' + ] + + 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') + + expect { middleware.call(env) }.to raise_error(RuntimeError, "invalid field: \"#{invalid_field_name}\"") + end + end + end + context 'with remote file' do let(:remote_id) { 'someid' } + let(:file_size) { 300 } + let(:file_path) { '' } + + it_behaves_like 'multipart upload files' + end + + 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_behaves_like 'multipart upload files' 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| diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb index 25536c07dd9..39055a2479f 100644 --- a/spec/lib/uploaded_file_spec.rb +++ b/spec/lib/uploaded_file_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' describe UploadedFile do let(:temp_dir) { Dir.tmpdir } - let(:temp_file) { Tempfile.new("test", temp_dir) } + let(:temp_file) { Tempfile.new(%w[test test], temp_dir) } before do FileUtils.touch(temp_file) @@ -16,13 +16,14 @@ describe UploadedFile do describe ".from_params" do let(:upload_path) { nil } + let(:file_path_override) { nil } after do FileUtils.rm_r(upload_path) if upload_path end subject do - described_class.from_params(params, :file, upload_path) + described_class.from_params(params, :file, upload_path, file_path_override) end context 'when valid file is specified' do @@ -31,9 +32,7 @@ describe UploadedFile do { 'file.path' => temp_file.path } end - it "succeeds" do - is_expected.not_to be_nil - end + it { is_expected.not_to be_nil } it "generates filename from path" do expect(subject.original_filename).to eq(::File.basename(temp_file.path)) @@ -41,33 +40,153 @@ describe UploadedFile do end context 'all parameters are specified' do - let(:params) do - { 'file.path' => temp_file.path, - 'file.name' => 'dir/my file&.txt', - 'file.type' => 'my/type', - 'file.sha256' => 'sha256', - 'file.remote_id' => 'remote_id' } + RSpec.shared_context 'filepath override' do + let(:temp_file_override) { Tempfile.new(%w[override override], temp_dir) } + let(:file_path_override) { temp_file_override.path } + + before do + FileUtils.touch(temp_file_override) + end + + after do + FileUtils.rm_f(temp_file_override) + end end - it "succeeds" do - is_expected.not_to be_nil + RSpec.shared_examples 'using the file path' do |filename:, content_type:, sha256:, path_suffix:| + it 'sets properly the attributes' do + expect(subject.original_filename).to eq(filename) + expect(subject.content_type).to eq(content_type) + expect(subject.sha256).to eq(sha256) + expect(subject.remote_id).to be_nil + expect(subject.path).to end_with(path_suffix) + end + + it 'handles a blank path' do + params['file.path'] = '' + + # Not a real file, so can't determine size itself + params['file.size'] = 1.byte + + expect { described_class.from_params(params, :file, upload_path) } + .not_to raise_error + end end - it "generates filename from path" do - expect(subject.original_filename).to eq('my_file_.txt') - expect(subject.content_type).to eq('my/type') - expect(subject.sha256).to eq('sha256') - expect(subject.remote_id).to eq('remote_id') + RSpec.shared_examples 'using the remote id' do |filename:, content_type:, sha256:, size:, remote_id:| + it 'sets properly the attributes' do + expect(subject.original_filename).to eq(filename) + expect(subject.content_type).to eq('application/octet-stream') + expect(subject.sha256).to eq('sha256') + expect(subject.path).to be_nil + expect(subject.size).to eq(123456) + expect(subject.remote_id).to eq('1234567890') + end + end + + context 'with a filepath' do + let(:params) do + { 'file.path' => temp_file.path, + 'file.name' => 'dir/my file&.txt', + 'file.type' => 'my/type', + 'file.sha256' => 'sha256' } + end + + it { is_expected.not_to be_nil } + + it_behaves_like 'using the file path', + filename: 'my_file_.txt', + content_type: 'my/type', + sha256: 'sha256', + path_suffix: 'test' + end + + context 'with a filepath override' do + include_context 'filepath override' + + let(:params) do + { 'file.path' => temp_file.path, + 'file.name' => 'dir/my file&.txt', + 'file.type' => 'my/type', + 'file.sha256' => 'sha256' } + end + + it { is_expected.not_to be_nil } + + it_behaves_like 'using the file path', + filename: 'my_file_.txt', + content_type: 'my/type', + sha256: 'sha256', + path_suffix: 'override' end - it 'handles a blank path' do - params['file.path'] = '' + context 'with a remote id' do + let(:params) do + { + 'file.name' => 'dir/my file&.txt', + 'file.sha256' => 'sha256', + 'file.remote_url' => 'http://localhost/file', + 'file.remote_id' => '1234567890', + 'file.etag' => 'etag1234567890', + 'file.size' => '123456' + } + end + + it { is_expected.not_to be_nil } + + it_behaves_like 'using the remote id', + filename: 'my_file_.txt', + content_type: 'application/octet-stream', + sha256: 'sha256', + size: 123456, + remote_id: '1234567890' + end - # Not a real file, so can't determine size itself - params['file.size'] = 1.byte + context 'with a path and a remote id' do + let(:params) do + { + 'file.path' => temp_file.path, + 'file.name' => 'dir/my file&.txt', + 'file.sha256' => 'sha256', + 'file.remote_url' => 'http://localhost/file', + 'file.remote_id' => '1234567890', + 'file.etag' => 'etag1234567890', + 'file.size' => '123456' + } + end + + it { is_expected.not_to be_nil } + + it_behaves_like 'using the remote id', + filename: 'my_file_.txt', + content_type: 'application/octet-stream', + sha256: 'sha256', + size: 123456, + remote_id: '1234567890' + end - expect { described_class.from_params(params, :file, upload_path) } - .not_to raise_error + context 'with a path override and a remote id' do + include_context 'filepath override' + + let(:params) do + { + 'file.name' => 'dir/my file&.txt', + 'file.sha256' => 'sha256', + 'file.remote_url' => 'http://localhost/file', + 'file.remote_id' => '1234567890', + 'file.etag' => 'etag1234567890', + 'file.size' => '123456' + } + end + + it { is_expected.not_to be_nil } + + it_behaves_like 'using the remote id', + filename: 'my_file_.txt', + content_type: 'application/octet-stream', + sha256: 'sha256', + size: 123456, + remote_id: '1234567890' end end end |