244ed8adb3
Because we were passing query strings as symbols, the fog-aws storage driver was omitting the uploadId and partNumber query strings from the signature in multipart uploads. This fix makes AWS and Ceph work with v2 signatures. See: https://github.com/fog/fog-aws/blob/v3.0.0/lib/fog/aws/storage.rb#L733 Closes https://gitlab.com/gitlab-org/gitlab-workhorse/issues/181
180 lines
5.3 KiB
Ruby
180 lines
5.3 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe ObjectStorage::DirectUpload do
|
|
let(:credentials) do
|
|
{
|
|
provider: 'AWS',
|
|
aws_access_key_id: 'AWS_ACCESS_KEY_ID',
|
|
aws_secret_access_key: 'AWS_SECRET_ACCESS_KEY'
|
|
}
|
|
end
|
|
|
|
let(:storage_url) { 'https://uploads.s3.amazonaws.com/' }
|
|
|
|
let(:bucket_name) { 'uploads' }
|
|
let(:object_name) { 'tmp/uploads/my-file' }
|
|
let(:maximum_size) { 1.gigabyte }
|
|
|
|
let(:direct_upload) { described_class.new(credentials, bucket_name, object_name, has_length: has_length, maximum_size: maximum_size) }
|
|
|
|
before do
|
|
Fog.unmock!
|
|
end
|
|
|
|
describe '#has_length' do
|
|
context 'is known' do
|
|
let(:has_length) { true }
|
|
let(:maximum_size) { nil }
|
|
|
|
it "maximum size is not required" do
|
|
expect { direct_upload }.not_to raise_error
|
|
end
|
|
end
|
|
|
|
context 'is unknown' do
|
|
let(:has_length) { false }
|
|
|
|
context 'and maximum size is specified' do
|
|
let(:maximum_size) { 1.gigabyte }
|
|
|
|
it "does not raise an error" do
|
|
expect { direct_upload }.not_to raise_error
|
|
end
|
|
end
|
|
|
|
context 'and maximum size is not specified' do
|
|
let(:maximum_size) { nil }
|
|
|
|
it "raises an error" do
|
|
expect { direct_upload }.to raise_error /maximum_size has to be specified if length is unknown/
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#to_hash' do
|
|
subject { direct_upload.to_hash }
|
|
|
|
shared_examples 'a valid upload' do
|
|
it "returns valid structure" do
|
|
expect(subject).to have_key(:Timeout)
|
|
expect(subject[:GetURL]).to start_with(storage_url)
|
|
expect(subject[:StoreURL]).to start_with(storage_url)
|
|
expect(subject[:DeleteURL]).to start_with(storage_url)
|
|
expect(subject[:CustomPutHeaders]).to be_truthy
|
|
expect(subject[:PutHeaders]).to eq({})
|
|
end
|
|
end
|
|
|
|
shared_examples 'a valid upload with multipart data' do
|
|
before do
|
|
stub_object_storage_multipart_init(storage_url, "myUpload")
|
|
end
|
|
|
|
it_behaves_like 'a valid upload'
|
|
|
|
it "returns valid structure" do
|
|
expect(subject).to have_key(:MultipartUpload)
|
|
expect(subject[:MultipartUpload]).to have_key(:PartSize)
|
|
expect(subject[:MultipartUpload][:PartURLs]).to all(start_with(storage_url))
|
|
expect(subject[:MultipartUpload][:PartURLs]).to all(include('uploadId=myUpload'))
|
|
expect(subject[:MultipartUpload][:CompleteURL]).to start_with(storage_url)
|
|
expect(subject[:MultipartUpload][:CompleteURL]).to include('uploadId=myUpload')
|
|
expect(subject[:MultipartUpload][:AbortURL]).to start_with(storage_url)
|
|
expect(subject[:MultipartUpload][:AbortURL]).to include('uploadId=myUpload')
|
|
end
|
|
|
|
it 'uses only strings in query parameters' do
|
|
expect(direct_upload.send(:connection)).to receive(:signed_url).at_least(:once) do |params|
|
|
if params[:query]
|
|
expect(params[:query].keys.all? { |key| key.is_a?(String) }).to be_truthy
|
|
end
|
|
end
|
|
|
|
subject
|
|
end
|
|
end
|
|
|
|
shared_examples 'a valid upload without multipart data' do
|
|
it_behaves_like 'a valid upload'
|
|
|
|
it "returns valid structure" do
|
|
expect(subject).not_to have_key(:MultipartUpload)
|
|
end
|
|
end
|
|
|
|
context 'when AWS is used' do
|
|
context 'when length is known' do
|
|
let(:has_length) { true }
|
|
|
|
it_behaves_like 'a valid upload without multipart data'
|
|
end
|
|
|
|
context 'when length is unknown' do
|
|
let(:has_length) { false }
|
|
|
|
it_behaves_like 'a valid upload with multipart data' do
|
|
context 'when maximum upload size is 10MB' do
|
|
let(:maximum_size) { 10.megabyte }
|
|
|
|
it 'returns only 2 parts' do
|
|
expect(subject[:MultipartUpload][:PartURLs].length).to eq(2)
|
|
end
|
|
|
|
it 'part size is mimimum, 5MB' do
|
|
expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte)
|
|
end
|
|
end
|
|
|
|
context 'when maximum upload size is 12MB' do
|
|
let(:maximum_size) { 12.megabyte }
|
|
|
|
it 'returns only 3 parts' do
|
|
expect(subject[:MultipartUpload][:PartURLs].length).to eq(3)
|
|
end
|
|
|
|
it 'part size is rounded-up to 5MB' do
|
|
expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte)
|
|
end
|
|
end
|
|
|
|
context 'when maximum upload size is 49GB' do
|
|
let(:maximum_size) { 49.gigabyte }
|
|
|
|
it 'returns maximum, 100 parts' do
|
|
expect(subject[:MultipartUpload][:PartURLs].length).to eq(100)
|
|
end
|
|
|
|
it 'part size is rounded-up to 5MB' do
|
|
expect(subject[:MultipartUpload][:PartSize]).to eq(505.megabyte)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when Google is used' do
|
|
let(:credentials) do
|
|
{
|
|
provider: 'Google',
|
|
google_storage_access_key_id: 'GOOGLE_ACCESS_KEY_ID',
|
|
google_storage_secret_access_key: 'GOOGLE_SECRET_ACCESS_KEY'
|
|
}
|
|
end
|
|
|
|
let(:storage_url) { 'https://storage.googleapis.com/uploads/' }
|
|
|
|
context 'when length is known' do
|
|
let(:has_length) { true }
|
|
|
|
it_behaves_like 'a valid upload without multipart data'
|
|
end
|
|
|
|
context 'when length is unknown' do
|
|
let(:has_length) { false }
|
|
|
|
it_behaves_like 'a valid upload without multipart data'
|
|
end
|
|
end
|
|
end
|
|
end
|