FEATURE: Use path from existing URL of uploads and optimized images (#13177)

Discourse shouldn't dynamically calculate the path of uploads and optimized images after a file has been stored on disk or S3. Otherwise it might calculate the wrong path if the SHA1 or extension stored in the database doesn't match the actual file path.
This commit is contained in:
Gerhard Schlager 2021-05-27 17:42:25 +02:00 committed by GitHub
parent bcbb5b4dae
commit 157f10db4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 189 additions and 56 deletions

View File

@ -3,13 +3,17 @@
module FileStore module FileStore
class BaseStore class BaseStore
UPLOAD_PATH_REGEX = %r|/(original/\d+X/.*)|
OPTIMIZED_IMAGE_PATH_REGEX = %r|/(optimized/\d+X/.*)|
def store_upload(file, upload, content_type = nil) def store_upload(file, upload, content_type = nil)
upload.url = nil
path = get_path_for_upload(upload) path = get_path_for_upload(upload)
store_file(file, path) store_file(file, path)
end end
def store_optimized_image(file, optimized_image, content_type = nil, secure: false) def store_optimized_image(file, optimized_image, content_type = nil, secure: false)
optimized_image.url = nil
path = get_path_for_optimized_image(optimized_image) path = get_path_for_optimized_image(optimized_image)
store_file(file, path) store_file(file, path)
end end
@ -116,6 +120,12 @@ module FileStore
end end
def get_path_for_upload(upload) def get_path_for_upload(upload)
# try to extract the path from the URL instead of calculating it,
# because the calculated path might differ from the actual path
if upload.url.present? && (path = upload.url[UPLOAD_PATH_REGEX, 1])
return prefix_path(path)
end
extension = extension =
if upload.extension if upload.extension
".#{upload.extension}" ".#{upload.extension}"
@ -128,6 +138,12 @@ module FileStore
end end
def get_path_for_optimized_image(optimized_image) def get_path_for_optimized_image(optimized_image)
# try to extract the path from the URL instead of calculating it,
# because the calculated path might differ from the actual path
if optimized_image.url.present? && (path = optimized_image.url[OPTIMIZED_IMAGE_PATH_REGEX, 1])
return prefix_path(path)
end
upload = optimized_image.upload upload = optimized_image.upload
version = optimized_image.version || 1 version = optimized_image.version || 1
extension = "_#{version}_#{optimized_image.width}x#{optimized_image.height}#{optimized_image.extension}" extension = "_#{version}_#{optimized_image.width}x#{optimized_image.height}#{optimized_image.extension}"
@ -178,6 +194,9 @@ module FileStore
depths.max depths.max
end end
def prefix_path(path)
path
end
end end
end end

View File

@ -66,7 +66,7 @@ module FileStore
end end
def get_path_for(type, upload_id, sha, extension) def get_path_for(type, upload_id, sha, extension)
File.join("/", upload_path, super(type, upload_id, sha, extension)) prefix_path(super(type, upload_id, sha, extension))
end end
def copy_file(file, path) def copy_file(file, path)
@ -134,5 +134,8 @@ module FileStore
puts "#{count} of #{model.count} #{model.name.underscore.pluralize} are missing" if count > 0 puts "#{count} of #{model.count} #{model.name.underscore.pluralize} are missing" if count > 0
end end
def prefix_path(path)
File.join("/", upload_path, path)
end
end end
end end

View File

@ -22,6 +22,7 @@ module FileStore
end end
def store_upload(file, upload, content_type = nil) def store_upload(file, upload, content_type = nil)
upload.url = nil
path = get_path_for_upload(upload) path = get_path_for_upload(upload)
url, upload.etag = store_file( url, upload.etag = store_file(
file, file,
@ -35,6 +36,7 @@ module FileStore
end end
def store_optimized_image(file, optimized_image, content_type = nil, secure: false) def store_optimized_image(file, optimized_image, content_type = nil, secure: false)
optimized_image.url = nil
path = get_path_for_optimized_image(optimized_image) path = get_path_for_optimized_image(optimized_image)
url, optimized_image.etag = store_file(file, path, content_type: content_type, private_acl: secure) url, optimized_image.etag = store_file(file, path, content_type: content_type, private_acl: secure)
url url

View File

@ -67,7 +67,7 @@ class ShrinkUploadedImage
log "base62: #{original_upload.base62_sha1} -> #{Upload.base62_sha1(sha1)}" log "base62: #{original_upload.base62_sha1} -> #{Upload.base62_sha1(sha1)}"
log "sha: #{original_upload.sha1} -> #{sha1}" log "sha: #{original_upload.sha1} -> #{sha1}"
log "(an exisiting upload)" if existing_upload log "(an existing upload)" if existing_upload
success = true success = true
posts = Post.unscoped.joins(:post_uploads).where(post_uploads: { upload_id: original_upload.id }).uniq.sort_by(&:created_at) posts = Post.unscoped.joins(:post_uploads).where(post_uploads: { upload_id: original_upload.id }).uniq.sort_by(&:created_at)

View File

@ -489,8 +489,8 @@ describe Email::Sender do
let!(:optimized_image_file) { file_from_fixtures("smallest.png", "images") } let!(:optimized_image_file) { file_from_fixtures("smallest.png", "images") }
before do before do
Discourse.store.store_optimized_image(optimized_image_file, optimized) url = Discourse.store.store_optimized_image(optimized_image_file, optimized)
optimized.update(url: Discourse.store.absolute_base_url + '/' + optimized.url) optimized.update(url: Discourse.store.absolute_base_url + '/' + url)
Discourse.store.cache_file(optimized_image_file, File.basename("#{optimized.sha1}.png")) Discourse.store.cache_file(optimized_image_file, File.basename("#{optimized.sha1}.png"))
end end

View File

@ -6,34 +6,86 @@ RSpec.describe FileStore::BaseStore do
fab!(:upload) { Fabricate(:upload, id: 9999, sha1: Digest::SHA1.hexdigest('9999')) } fab!(:upload) { Fabricate(:upload, id: 9999, sha1: Digest::SHA1.hexdigest('9999')) }
describe '#get_path_for_upload' do describe '#get_path_for_upload' do
def expect_correct_path(expected_path)
expect(described_class.new.get_path_for_upload(upload)).to eq(expected_path)
end
context "empty URL" do
before do
upload.update!(url: "")
end
it 'should return the right path' do it 'should return the right path' do
expect(FileStore::BaseStore.new.get_path_for_upload(upload)) expect_correct_path('original/2X/4/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png')
.to eq('original/2X/4/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png')
end end
describe 'when Upload#extension has not been set' do describe 'when Upload#extension has not been set' do
it 'should return the right path' do it 'should return the right path' do
upload.update!(extension: nil) upload.update!(extension: nil)
expect_correct_path('original/2X/4/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png')
expect(FileStore::BaseStore.new.get_path_for_upload(upload))
.to eq('original/2X/4/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png')
end end
end end
describe 'when id is negative' do describe 'when id is negative' do
it 'should return the right depth' do it 'should return the right depth' do
upload.update!(id: -999) upload.update!(id: -999)
expect_correct_path('original/1X/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png')
end
end
end
expect(FileStore::BaseStore.new.get_path_for_upload(upload)) context "existing URL" do
.to eq('original/1X/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png') context "regular site" do
it "returns the correct path for files stored on local storage" do
upload.update!(url: "/uploads/default/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
upload.update!(url: "/uploads/default/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
expect_correct_path("original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
end
it "returns the correct path for files stored on S3" do
upload.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
upload.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
expect_correct_path("original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
end
end
context "multisite" do
it "returns the correct path for files stored on local storage" do
upload.update!(url: "/uploads/foo/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
upload.update!(url: "/uploads/foo/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
expect_correct_path("original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
end
it "returns the correct path for files stored on S3" do
upload.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/foo/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
upload.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/foo/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
expect_correct_path("original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
end
it "returns the correct path when the site name is 'original'" do
upload.update!(url: "/uploads/original/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
upload.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/original/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg")
end
end end
end end
end end
describe '#get_path_for_optimized_image' do describe '#get_path_for_optimized_image' do
let(:upload) { Fabricate.build(:upload, id: 100) } let!(:upload) { Fabricate.build(:upload, id: 100) }
let(:optimized_path) { "optimized/1X/#{upload.sha1}_1_100x200.png" } let!(:optimized_path) { "optimized/1X/#{upload.sha1}_1_100x200.png" }
context "empty URL" do
it 'should return the right path' do it 'should return the right path' do
optimized = Fabricate.build(:optimized_image, upload: upload, version: 1) optimized = Fabricate.build(:optimized_image, upload: upload, version: 1)
expect(FileStore::BaseStore.new.get_path_for_optimized_image(optimized)).to eq(optimized_path) expect(FileStore::BaseStore.new.get_path_for_optimized_image(optimized)).to eq(optimized_path)
@ -45,6 +97,48 @@ RSpec.describe FileStore::BaseStore do
end end
end end
context "existing URL" do
let!(:optimized) { Fabricate.build(:optimized_image, upload: upload, version: 1) }
let!(:optimized_path) { "optimized/1X/#{upload.sha1}_1_100x200.jpg" }
def expect_correct_optimized_path
expect(described_class.new.get_path_for_optimized_image(optimized)).to eq(optimized_path)
end
context "regular site" do
it "returns the correct path for files stored on local storage" do
optimized.update!(url: "/uploads/default/optimized/1X/#{upload.sha1}_1_100x200.jpg")
expect_correct_optimized_path
end
it "returns the correct path for files stored on S3" do
optimized.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/optimized/1X/#{upload.sha1}_1_100x200.jpg")
expect_correct_optimized_path
end
end
context "multisite" do
it "returns the correct path for files stored on local storage" do
optimized.update!(url: "/uploads/foo/optimized/1X/#{upload.sha1}_1_100x200.jpg")
expect_correct_optimized_path
end
it "returns the correct path for files stored on S3" do
optimized.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/foo/optimized/1X/#{upload.sha1}_1_100x200.jpg")
expect_correct_optimized_path
end
it "returns the correct path when the site name is 'optimized'" do
optimized.update!(url: "/uploads/optimized/optimized/1X/#{upload.sha1}_1_100x200.jpg")
expect_correct_optimized_path
optimized.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/optimized/optimized/1X/#{upload.sha1}_1_100x200.jpg")
expect_correct_optimized_path
end
end
end
end
describe '#download' do describe '#download' do
before do before do
setup_s3 setup_s3

View File

@ -146,4 +146,24 @@ describe FileStore::LocalStore do
expect(store.external?).to eq(false) expect(store.external?).to eq(false)
end end
describe "#get_path_for" do
it "returns the correct path" do
expect(store.get_path_for("original", upload.id, upload.sha1, ".#{upload.extension}"))
.to match(%r|/#{upload_path}/original/.+#{upload.sha1}\.png|)
end
end
describe "#get_path_for_upload" do
it "returns the correct path" do
expect(store.get_path_for_upload(upload))
.to match(%r|/#{upload_path}/original/.+#{upload.sha1}\.png|)
end
end
describe "#get_path_for_optimized_image" do
it "returns the correct path" do
expect(store.get_path_for_optimized_image(optimized_image))
.to match(%r|/#{upload_path}/optimized/.+#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200\.png|)
end
end
end end

View File

@ -29,7 +29,6 @@ describe FileStore::S3Store do
describe "#store_upload" do describe "#store_upload" do
it "returns an absolute schemaless url" do it "returns an absolute schemaless url" do
store.expects(:get_depth_for).with(upload.id).returns(0)
s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once
s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.png").returns(s3_object) s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.png").returns(s3_object)
s3_object.expects(:put).with( s3_object.expects(:put).with(
@ -51,7 +50,6 @@ describe FileStore::S3Store do
end end
it "returns an absolute schemaless url" do it "returns an absolute schemaless url" do
store.expects(:get_depth_for).with(upload.id).returns(0)
s3_helper.expects(:s3_bucket).returns(s3_bucket) s3_helper.expects(:s3_bucket).returns(s3_bucket)
s3_bucket.expects(:object).with("discourse-uploads/original/1X/#{upload.sha1}.png").returns(s3_object) s3_bucket.expects(:object).with("discourse-uploads/original/1X/#{upload.sha1}.png").returns(s3_object)
@ -67,7 +65,7 @@ describe FileStore::S3Store do
it "saves secure attachment using private ACL" do it "saves secure attachment using private ACL" do
SiteSetting.prevent_anons_from_downloading_files = true SiteSetting.prevent_anons_from_downloading_files = true
SiteSetting.authorized_extensions = "pdf|png|jpg|gif" SiteSetting.authorized_extensions = "pdf|png|jpg|gif"
upload.update!(original_filename: "small.pdf", extension: "pdf", secure: true) upload = Fabricate(:upload, original_filename: "small.pdf", extension: "pdf", secure: true)
s3_helper.expects(:s3_bucket).returns(s3_bucket) s3_helper.expects(:s3_bucket).returns(s3_bucket)
s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.pdf").returns(s3_object) s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.pdf").returns(s3_object)
@ -109,7 +107,6 @@ describe FileStore::S3Store do
end end
it "returns an absolute schemaless url" do it "returns an absolute schemaless url" do
store.expects(:get_depth_for).with(optimized_image.upload.id).returns(0)
s3_helper.expects(:s3_bucket).returns(s3_bucket) s3_helper.expects(:s3_bucket).returns(s3_bucket)
path = "optimized/1X/#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200.png" path = "optimized/1X/#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200.png"
@ -127,7 +124,6 @@ describe FileStore::S3Store do
end end
it "returns an absolute schemaless url" do it "returns an absolute schemaless url" do
store.expects(:get_depth_for).with(optimized_image.upload.id).returns(0)
s3_helper.expects(:s3_bucket).returns(s3_bucket) s3_helper.expects(:s3_bucket).returns(s3_bucket)
path = "discourse-uploads/optimized/1X/#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200.png" path = "discourse-uploads/optimized/1X/#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200.png"
@ -170,7 +166,6 @@ describe FileStore::S3Store do
context 'removal from s3' do context 'removal from s3' do
describe "#remove_upload" do describe "#remove_upload" do
it "removes the file from s3 with the right paths" do it "removes the file from s3 with the right paths" do
store.expects(:get_depth_for).with(upload.id).returns(0)
s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once
upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/original/1X/#{upload.sha1}.png") upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/original/1X/#{upload.sha1}.png")
s3_object = stub s3_object = stub
@ -188,7 +183,6 @@ describe FileStore::S3Store do
upload = optimized.upload upload = optimized.upload
path = "optimized/1X/#{upload.sha1}_#{optimized.version}_#{optimized.width}x#{optimized.height}.png" path = "optimized/1X/#{upload.sha1}_#{optimized.version}_#{optimized.width}x#{optimized.height}.png"
store.expects(:get_depth_for).with(upload.id).returns(0)
s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once
optimized.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{path}") optimized.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{path}")
s3_object = stub s3_object = stub
@ -207,7 +201,6 @@ describe FileStore::S3Store do
end end
it "removes the file from s3 with the right paths" do it "removes the file from s3 with the right paths" do
store.expects(:get_depth_for).with(upload.id).returns(0)
s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once
upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/discourse-uploads/original/1X/#{upload.sha1}.png") upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/discourse-uploads/original/1X/#{upload.sha1}.png")
s3_object = stub s3_object = stub
@ -360,8 +353,9 @@ describe FileStore::S3Store do
end end
describe ".update_upload_ACL" do describe ".update_upload_ACL" do
let(:upload) { Fabricate(:upload, original_filename: "small.pdf", extension: "pdf") }
it "sets acl to public by default" do it "sets acl to public by default" do
upload.update!(original_filename: "small.pdf", extension: "pdf")
s3_helper.expects(:s3_bucket).returns(s3_bucket) s3_helper.expects(:s3_bucket).returns(s3_bucket)
s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.pdf").returns(s3_object) s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.pdf").returns(s3_object)
s3_object.expects(:acl).returns(s3_object) s3_object.expects(:acl).returns(s3_object)
@ -371,7 +365,7 @@ describe FileStore::S3Store do
end end
it "sets acl to private when upload is marked secure" do it "sets acl to private when upload is marked secure" do
upload.update!(original_filename: "small.pdf", extension: "pdf", secure: true) upload.update!(secure: true)
s3_helper.expects(:s3_bucket).returns(s3_bucket) s3_helper.expects(:s3_bucket).returns(s3_bucket)
s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.pdf").returns(s3_object) s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.pdf").returns(s3_object)
s3_object.expects(:acl).returns(s3_object) s3_object.expects(:acl).returns(s3_object)

View File

@ -78,7 +78,7 @@ Fabricator(:s3_image_upload, from: :upload_s3) do
file = Tempfile.new(['fabricated', '.png']) file = Tempfile.new(['fabricated', '.png'])
`convert -size #{upload.width}x#{upload.height} xc:white "#{file.path}"` `convert -size #{upload.width}x#{upload.height} xc:white "#{file.path}"`
Discourse.store.store_upload(file, upload) upload.url = Discourse.store.store_upload(file, upload)
upload.sha1 = Upload.generate_digest(file.path) upload.sha1 = Upload.generate_digest(file.path)
WebMock WebMock

View File

@ -363,9 +363,15 @@ describe Upload do
expect(upload.secure).to eq(false) expect(upload.secure).to eq(false)
end end
it 'marks a local attachment as secure if secure media enabled' do context "local attachment" do
before do
SiteSetting.authorized_extensions = "pdf" SiteSetting.authorized_extensions = "pdf"
upload.update!(original_filename: "small.pdf", extension: "pdf", secure: false, access_control_post: Fabricate(:private_message_post)) end
let(:upload) { Fabricate(:upload, original_filename: "small.pdf", extension: "pdf", secure: true) }
it 'marks a local attachment as secure if secure media enabled' do
upload.update!(secure: false, access_control_post: Fabricate(:private_message_post))
enable_secure_media enable_secure_media
expect { upload.update_secure_status } expect { upload.update_secure_status }
@ -375,14 +381,12 @@ describe Upload do
end end
it 'marks a local attachment as not secure if secure media enabled' do it 'marks a local attachment as not secure if secure media enabled' do
SiteSetting.authorized_extensions = "pdf"
upload.update!(original_filename: "small.pdf", extension: "pdf", secure: true)
expect { upload.update_secure_status } expect { upload.update_secure_status }
.to change { upload.secure } .to change { upload.secure }
expect(upload.secure).to eq(false) expect(upload.secure).to eq(false)
end end
end
it 'does not change secure status of a non-attachment when prevent_anons_from_downloading_files is enabled by itself' do it 'does not change secure status of a non-attachment when prevent_anons_from_downloading_files is enabled by itself' do
SiteSetting.prevent_anons_from_downloading_files = true SiteSetting.prevent_anons_from_downloading_files = true

View File

@ -108,7 +108,6 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do
it "removes the file from s3 on multisite" do it "removes the file from s3 on multisite" do
test_multisite_connection('default') do test_multisite_connection('default') do
upload = build_upload upload = build_upload
store.expects(:get_depth_for).with(upload.id).returns(0)
s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once
upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{upload_path}/original/1X/#{upload.sha1}.png") upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{upload_path}/original/1X/#{upload.sha1}.png")
s3_object = stub s3_object = stub
@ -125,7 +124,6 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do
it "removes the file from s3 on another multisite db" do it "removes the file from s3 on another multisite db" do
test_multisite_connection('second') do test_multisite_connection('second') do
upload = build_upload upload = build_upload
store.expects(:get_depth_for).with(upload.id).returns(0)
s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once
upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{upload_path}/original/1X/#{upload.sha1}.png") upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{upload_path}/original/1X/#{upload.sha1}.png")
s3_object = stub s3_object = stub
@ -147,7 +145,6 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do
it "removes the file from s3 on multisite" do it "removes the file from s3 on multisite" do
test_multisite_connection('default') do test_multisite_connection('default') do
upload = build_upload upload = build_upload
store.expects(:get_depth_for).with(upload.id).returns(0)
s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once
upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/discourse-uploads/#{upload_path}/original/1X/#{upload.sha1}.png") upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/discourse-uploads/#{upload_path}/original/1X/#{upload.sha1}.png")
s3_object = stub s3_object = stub
@ -185,14 +182,14 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do
describe "when secure attachments are enabled" do describe "when secure attachments are enabled" do
it "returns signed URL with correct path" do it "returns signed URL with correct path" do
test_multisite_connection('default') do test_multisite_connection('default') do
upload = build_upload upload = Fabricate(:upload, original_filename: "small.pdf", extension: "pdf", secure: true)
upload.update!(original_filename: "small.pdf", extension: "pdf", secure: true)
s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once
s3_bucket.expects(:object).with("#{upload_path}/original/1X/#{upload.sha1}.pdf").returns(s3_object).at_least_once s3_bucket.expects(:object).with("#{upload_path}/original/1X/#{upload.sha1}.pdf").returns(s3_object).at_least_once
s3_object.expects(:presigned_url).with(:get, expires_in: S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS) s3_object.expects(:presigned_url).with(:get, expires_in: S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS)
expect(store.store_upload(uploaded_file, upload)).to eq( upload.url = store.store_upload(uploaded_file, upload)
expect(upload.url).to eq(
"//some-really-cool-bucket.s3.dualstack.us-west-1.amazonaws.com/#{upload_path}/original/1X/#{upload.sha1}.pdf" "//some-really-cool-bucket.s3.dualstack.us-west-1.amazonaws.com/#{upload_path}/original/1X/#{upload.sha1}.pdf"
) )