discourse/spec/models/user_avatar_spec.rb
Kelv 04ba5baec0
DEV: ensure rebaking works even when some users have inconsistent data (#30261)
* DEV: add db consistency check for UserEmail

* DEV: add db consistency check for UserAvatar

* DEV: ignore inconsistent data related to user avatars when deciding whether to rebake old posts


Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>

---------

Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
2024-12-16 19:48:25 +08:00

250 lines
7.7 KiB
Ruby

# frozen_string_literal: true
RSpec.describe UserAvatar do
fab!(:user)
let(:avatar) { user.create_user_avatar! }
describe "#update_gravatar!" do
let(:temp) { Tempfile.new("test") }
fab!(:upload) { Fabricate(:upload, user: user) }
describe "when working" do
before do
temp.binmode
# tiny valid png
temp.write(
Base64.decode64(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==",
),
)
temp.rewind
FileHelper.expects(:download).returns(temp)
end
after { temp.unlink }
it "can update gravatars" do
freeze_time
expect { avatar.update_gravatar! }.to change { Upload.count }.by(1)
expect(avatar.gravatar_upload).to eq(Upload.last)
expect(avatar.last_gravatar_download_attempt).to eq(Time.now)
expect(user.reload.uploaded_avatar).to eq(nil)
expect do avatar.destroy end.to_not change { Upload.count }
end
it "updates gravatars even if uploads have been disabled" do
SiteSetting.authorized_extensions = ""
expect { avatar.update_gravatar! }.to change { Upload.count }.by(1)
expect(avatar.gravatar_upload).to eq(Upload.last)
end
describe "when user has an existing custom upload" do
it "should not change the user's uploaded avatar" do
user.update!(uploaded_avatar: upload)
avatar.update!(custom_upload: upload, gravatar_upload: Fabricate(:upload, user: user))
avatar.update_gravatar!
expect(upload.reload).to eq(upload)
expect(user.reload.uploaded_avatar).to eq(upload)
expect(avatar.reload.custom_upload).to eq(upload)
expect(avatar.gravatar_upload).to eq(Upload.last)
end
end
describe "when user has an existing gravatar" do
it "should update the user's uploaded avatar correctly" do
user.update!(uploaded_avatar: upload)
avatar.update!(gravatar_upload: upload)
avatar.update_gravatar!
# old upload to be cleaned up via clean_up_uploads
expect(Upload.find_by(id: upload.id)).not_to eq(nil)
new_upload = Upload.last
expect(user.reload.uploaded_avatar).to eq(new_upload)
expect(avatar.reload.gravatar_upload).to eq(new_upload)
end
end
end
describe "when failing" do
it "always update 'last_gravatar_download_attempt'" do
freeze_time
FileHelper.expects(:download).raises(SocketError)
expect do expect { avatar.update_gravatar! }.to raise_error(SocketError) end.to_not change {
Upload.count
}
expect(avatar.last_gravatar_download_attempt).to eq(Time.now)
end
end
describe "404 should be silent, nothing to do really" do
it "does nothing when avatar is 404" do
SecureRandom.stubs(:urlsafe_base64).returns("5555")
freeze_time
stub_request(
:get,
"https://www.gravatar.com/avatar/#{avatar.user.email_hash}.png?d=404&reset_cache=5555&s=#{Discourse.avatar_sizes.max}",
).to_return(status: 404, body: "", headers: {})
expect do avatar.update_gravatar! end.to_not change { Upload.count }
expect(avatar.last_gravatar_download_attempt).to eq(Time.now)
end
end
it "should not raise an error when there's no primary_email" do
avatar.user.primary_email.destroy
avatar.user.reload
# If raises an error, test fails
avatar.update_gravatar!
end
end
describe ".import_url_for_user" do
it "creates user_avatar record if missing" do
user = Fabricate(:user)
user.user_avatar.destroy
user.reload
FileHelper.stubs(:download).returns(file_from_fixtures("logo.png"))
UserAvatar.import_url_for_user("logo.png", user)
user.reload
expect(user.uploaded_avatar_id).not_to eq(nil)
expect(user.user_avatar.custom_upload_id).to eq(user.uploaded_avatar_id)
end
it "can leave gravatar alone" do
upload = Fabricate(:upload)
user = Fabricate(:user, uploaded_avatar_id: upload.id)
user.user_avatar.update_columns(gravatar_upload_id: upload.id)
stub_request(:get, "http://thisfakesomething.something.com/").to_return(
status: 200,
body: file_from_fixtures("logo.png"),
headers: {
},
)
url = "http://thisfakesomething.something.com/"
expect do UserAvatar.import_url_for_user(url, user, override_gravatar: false) end.to change {
Upload.count
}.by(1)
user.reload
expect(user.uploaded_avatar_id).to eq(upload.id)
last_id = Upload.last.id
expect(last_id).not_to eq(upload.id)
expect(user.user_avatar.custom_upload_id).to eq(last_id)
end
describe "when avatar url returns an invalid status code" do
it "should not do anything" do
stub_request(:get, "http://thisfakesomething.something.com/").to_return(
status: 500,
body: "",
headers: {
},
)
url = "http://thisfakesomething.something.com/"
expect do UserAvatar.import_url_for_user(url, user) end.to_not change { Upload.count }
user.reload
expect(user.uploaded_avatar_id).to eq(nil)
expect(user.user_avatar.custom_upload_id).to eq(nil)
end
end
it "doesn't error out if the remote request fails" do
FileHelper.stubs(:download).raises(FinalDestination::SSRFDetector::LookupFailedError.new)
expect { UserAvatar.import_url_for_user(anything, user) }.not_to raise_error
end
end
describe "ensure_consistency!" do
it "cleans up incorrectly sized avatars" do
SiteSetting.avatar_sizes = "10|20|30"
upload = Fabricate(:upload)
user_avatar = Fabricate(:user).user_avatar
user_avatar.update_columns(custom_upload_id: upload.id)
Fabricate(:optimized_image, upload: upload, width: 10, height: 10)
Fabricate(:optimized_image, upload: upload, width: 15, height: 15)
Fabricate(:optimized_image, upload: upload, width: 20, height: 20)
UserAvatar.ensure_consistency!
expect(OptimizedImage.where(upload_id: upload.id).pluck(:width, :height).sort).to eq(
[[10, 10], [20, 20]],
)
# will not clean up if referenced
Fabricate(:optimized_image, upload: upload, width: 15, height: 15)
UploadReference.create!(upload: upload, target: Fabricate(:post))
UserAvatar.ensure_consistency!
expect(OptimizedImage.where(upload_id: upload.id).pluck(:width, :height).sort).to eq(
[[10, 10], [15, 15], [20, 20]],
)
end
it "will clean up dangling avatars" do
upload1 = Fabricate(:upload)
upload2 = Fabricate(:upload)
user_avatar = Fabricate(:user).user_avatar
user_avatar.update_columns(gravatar_upload_id: upload1.id, custom_upload_id: upload2.id)
upload1.destroy!
upload2.destroy!
user_avatar.reload
expect(user_avatar.gravatar_upload_id).to eq(nil)
expect(user_avatar.custom_upload_id).to eq(nil)
user_avatar.update_columns(gravatar_upload_id: upload1.id, custom_upload_id: upload2.id)
UserAvatar.ensure_consistency!
user_avatar.reload
expect(user_avatar.gravatar_upload_id).to eq(nil)
expect(user_avatar.custom_upload_id).to eq(nil)
end
it "deletes avatars without users and does not remove avatars with users" do
user_avatar_with_user = Fabricate(:user_avatar)
user_avatar_without_user = Fabricate(:user_avatar)
user_avatar_without_user.user.delete
UserAvatar.ensure_consistency!
expect(UserAvatar.exists?(user_avatar_with_user.id)).to eq true
expect(UserAvatar.exists?(user_avatar_without_user.id)).to eq false
end
end
end