chore: Ability to Disable Gravatars (#5027)

fixes: #3853

- Introduced DISABLE_GRAVATAR Global Config, which will stop chatwoot from making API requests to gravatar
- Cleaned up avatar-related logic and centralized it into the avatarable concern
- Added specs for the missing cases
- Added migration for existing installations to move the avatar to attachment, rather than making the API that results in 404.
This commit is contained in:
Sojan Jose
2022-07-21 19:27:12 +02:00
committed by GitHub
parent 6105567238
commit 6a6a37a67b
25 changed files with 225 additions and 83 deletions

View File

@@ -13,7 +13,7 @@ describe ::ContactIdentifyAction do
describe '#perform' do
it 'updates the contact' do
expect(ContactAvatarJob).not_to receive(:perform_later).with(contact, params[:avatar_url])
expect(Avatar::AvatarFromUrlJob).not_to receive(:perform_later).with(contact, params[:avatar_url])
contact_identify
expect(contact.reload.name).to eq 'test'
# custom attributes are merged properly without overwriting existing ones
@@ -32,7 +32,7 @@ describe ::ContactIdentifyAction do
it 'enques avatar job when avatar url parameter is passed' do
params = { name: 'test', avatar_url: 'https://chatwoot-assets.local/sample.png' }
expect(ContactAvatarJob).to receive(:perform_later).with(contact, params[:avatar_url]).once
expect(Avatar::AvatarFromUrlJob).to receive(:perform_later).with(contact, params[:avatar_url]).once
described_class.new(contact: contact, params: params).perform
end

View File

@@ -1,6 +1,7 @@
require 'rails_helper'
describe ::V2::ReportBuilder do
include ActiveJob::TestHelper
let!(:account) { create(:account) }
let!(:user) { create(:user, account: account) }
let!(:inbox) { create(:inbox, account: account) }
@@ -8,49 +9,44 @@ describe ::V2::ReportBuilder do
let!(:label_1) { create(:label, title: 'Label_1', account: account) }
let!(:label_2) { create(:label, title: 'Label_2', account: account) }
# Running jobs inline to calculate the exact metrics
around do |test|
current_adapter = ActiveJob::Base.queue_adapter
ActiveJob::Base.queue_adapter = :inline
test.run
ensure
ActiveJob::Base.queue_adapter = current_adapter
end
describe '#timeseries' do
before do
10.times do
conversation = create(:conversation, account: account,
inbox: inbox, assignee: user,
created_at: Time.zone.today)
create_list(:message, 5, message_type: 'outgoing',
account: account, inbox: inbox,
conversation: conversation, created_at: Time.zone.today + 2.hours)
create_list(:message, 2, message_type: 'incoming',
account: account, inbox: inbox,
conversation: conversation,
created_at: Time.zone.today + 3.hours)
conversation.update_labels('label_1')
conversation.label_list
conversation.save!
end
gravatar_url = 'https://www.gravatar.com'
stub_request(:get, /#{gravatar_url}.*/).to_return(status: 404)
5.times do
conversation = create(:conversation, account: account,
inbox: inbox, assignee: user,
created_at: (Time.zone.today - 2.days))
create_list(:message, 3, message_type: 'outgoing',
account: account, inbox: inbox,
conversation: conversation,
created_at: (Time.zone.today - 2.days))
create_list(:message, 1, message_type: 'incoming',
account: account, inbox: inbox,
conversation: conversation,
created_at: (Time.zone.today - 2.days))
conversation.update_labels('label_2')
conversation.label_list
conversation.save!
perform_enqueued_jobs do
10.times do
conversation = create(:conversation, account: account,
inbox: inbox, assignee: user,
created_at: Time.zone.today)
create_list(:message, 5, message_type: 'outgoing',
account: account, inbox: inbox,
conversation: conversation, created_at: Time.zone.today + 2.hours)
create_list(:message, 2, message_type: 'incoming',
account: account, inbox: inbox,
conversation: conversation,
created_at: Time.zone.today + 3.hours)
conversation.update_labels('label_1')
conversation.label_list
conversation.save!
end
5.times do
conversation = create(:conversation, account: account,
inbox: inbox, assignee: user,
created_at: (Time.zone.today - 2.days))
create_list(:message, 3, message_type: 'outgoing',
account: account, inbox: inbox,
conversation: conversation,
created_at: (Time.zone.today - 2.days))
create_list(:message, 1, message_type: 'incoming',
account: account, inbox: inbox,
conversation: conversation,
created_at: (Time.zone.today - 2.days))
conversation.update_labels('label_2')
conversation.label_list
conversation.save!
end
end
end

View File

@@ -15,7 +15,7 @@ RSpec.describe 'Api::V1::Accounts::BulkActionsController', type: :request do
describe 'POST /api/v1/accounts/{account.id}/bulk_action' do
context 'when it is an unauthenticated user' do
let(:agent) { create(:user) }
let!(:agent) { create(:user) }
it 'returns unauthorized' do
post "/api/v1/accounts/#{account.id}/bulk_actions",
@@ -27,7 +27,7 @@ RSpec.describe 'Api::V1::Accounts::BulkActionsController', type: :request do
end
context 'when it is an authenticated user' do
let(:agent) { create(:user, account: account, role: :agent) }
let!(:agent) { create(:user, account: account, role: :agent) }
it 'Ignores bulk_actions for wrong type' do
post "/api/v1/accounts/#{account.id}/bulk_actions",
@@ -117,7 +117,7 @@ RSpec.describe 'Api::V1::Accounts::BulkActionsController', type: :request do
describe 'POST /api/v1/accounts/{account.id}/bulk_actions' do
context 'when it is an authenticated user' do
let(:agent) { create(:user, account: account, role: :agent) }
let!(:agent) { create(:user, account: account, role: :agent) }
it 'Bulk delete conversation labels' do
Conversation.first.add_labels(%w[support priority_customer])

View File

@@ -487,6 +487,14 @@ RSpec.describe 'Contacts API', type: :request do
contact.reload
expect(contact.avatar.attached?).to be(true)
end
it 'updated avatar with avatar_url' do
patch "/api/v1/accounts/#{account.id}/contacts/#{contact.id}",
params: valid_params.merge(avatar_url: 'http://example.com/avatar.png'),
headers: admin.create_new_auth_token
expect(response).to have_http_status(:success)
expect(Avatar::AvatarFromUrlJob).to have_been_enqueued.with(contact, 'http://example.com/avatar.png')
end
end
end

View File

@@ -2,7 +2,7 @@
FactoryBot.define do
factory :inbox_member do
user
user { create(:user, :with_avatar) }
inbox
end
end

View File

@@ -0,0 +1,29 @@
require 'rails_helper'
RSpec.describe Avatar::AvatarFromGravatarJob, type: :job do
let(:avatarable) { create(:contact) }
let(:email) { 'test@test.com' }
let(:gravatar_url) { "https://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(email)}?d=404" }
it 'enqueues the job' do
expect { described_class.perform_later(avatarable, email) }.to have_enqueued_job(described_class)
.on_queue('low')
end
it 'will call AvatarFromUrlJob with gravatar url' do
expect(Avatar::AvatarFromUrlJob).to receive(:perform_later).with(avatarable, gravatar_url)
described_class.perform_now(avatarable, email)
end
it 'will not call AvatarFromUrlJob if DISABLE_GRAVATAR is configured' do
with_modified_env DISABLE_GRAVATAR: 'true' do
expect(Avatar::AvatarFromUrlJob).not_to receive(:perform_later).with(avatarable, gravatar_url)
described_class.perform_now(avatarable, '')
end
end
it 'will not call AvatarFromUrlJob if email is blank' do
expect(Avatar::AvatarFromUrlJob).not_to receive(:perform_later).with(avatarable, gravatar_url)
described_class.perform_now(avatarable, '')
end
end

View File

@@ -0,0 +1,20 @@
require 'rails_helper'
RSpec.describe Avatar::AvatarFromUrlJob, type: :job do
let(:avatarable) { create(:contact) }
let(:avatar_url) { 'https://example.com/avatar.png' }
it 'enqueues the job' do
expect { described_class.perform_later(avatarable, avatar_url) }.to have_enqueued_job(described_class)
.on_queue('default')
end
it 'will attach avatar from url' do
expect(avatarable.avatar).not_to be_attached
expect(Down).to receive(:download).with(avatar_url,
max_size: 15 * 1024 * 1024).and_return(fixture_file_upload(Rails.root.join('spec/assets/avatar.png'),
'image/png'))
described_class.perform_now(avatarable, avatar_url)
expect(avatarable.avatar).to be_attached
end
end

View File

@@ -1,5 +1,6 @@
require 'rails_helper'
require Rails.root.join 'spec/models/concerns/access_tokenable_shared.rb'
require Rails.root.join 'spec/models/concerns/avatarable_shared.rb'
RSpec.describe AgentBot, type: :model do
describe 'associations' do
@@ -9,5 +10,6 @@ RSpec.describe AgentBot, type: :model do
describe 'concerns' do
it_behaves_like 'access_tokenable'
it_behaves_like 'avatarable'
end
end

View File

@@ -0,0 +1,41 @@
require 'rails_helper'
shared_examples_for 'avatarable' do
let(:avatarable) { create(described_class.to_s.underscore) }
it { is_expected.to have_one_attached(:avatar) }
it 'add avatar_url method' do
expect(avatarable.respond_to?(:avatar_url)).to be true
end
context 'when avatarable has an email attribute' do
it 'enques job when email is changed on avatarable create' do
avatarable = build(described_class.to_s.underscore, account: create(:account))
if avatarable.respond_to?(:email)
avatarable.email = 'test@test.com'
avatarable.skip_reconfirmation! if avatarable.is_a? User
expect(Avatar::AvatarFromGravatarJob).to receive(:set).with(wait: 30.seconds).and_call_original
end
avatarable.save!
expect(Avatar::AvatarFromGravatarJob).to have_been_enqueued.with(avatarable, avatarable.email) if avatarable.respond_to?(:email)
end
it 'enques job when email is changes on avatarable update' do
if avatarable.respond_to?(:email)
avatarable.email = 'xyc@test.com'
avatarable.skip_reconfirmation! if avatarable.is_a? User
expect(Avatar::AvatarFromGravatarJob).to receive(:set).with(wait: 30.seconds).and_call_original
end
avatarable.save!
expect(Avatar::AvatarFromGravatarJob).to have_been_enqueued.with(avatarable, avatarable.email) if avatarable.respond_to?(:email)
end
it 'will not enqueu when email is not changed on avatarable update' do
avatarable.updated_at = Time.now.utc
expect do
avatarable.save!
end.not_to have_enqueued_job(Avatar::AvatarFromGravatarJob)
end
end
end

View File

@@ -2,6 +2,8 @@
require 'rails_helper'
require Rails.root.join 'spec/models/concerns/avatarable_shared.rb'
RSpec.describe Contact do
context 'validations' do
it { is_expected.to validate_presence_of(:account_id) }
@@ -12,6 +14,10 @@ RSpec.describe Contact do
it { is_expected.to have_many(:conversations).dependent(:destroy_async) }
end
describe 'concerns' do
it_behaves_like 'avatarable'
end
context 'prepare contact attributes before validation' do
it 'sets email to lowercase' do
contact = create(:contact, email: 'Test@test.com')

View File

@@ -2,6 +2,7 @@
require 'rails_helper'
require Rails.root.join 'spec/models/concerns/out_of_offisable_shared.rb'
require Rails.root.join 'spec/models/concerns/avatarable_shared.rb'
RSpec.describe Inbox do
describe 'validations' do
@@ -37,6 +38,7 @@ RSpec.describe Inbox do
describe 'concerns' do
it_behaves_like 'out_of_offisable'
it_behaves_like 'avatarable'
end
describe '#add_member' do

View File

@@ -2,6 +2,7 @@
require 'rails_helper'
require Rails.root.join 'spec/models/concerns/access_tokenable_shared.rb'
require Rails.root.join 'spec/models/concerns/avatarable_shared.rb'
RSpec.describe User do
let!(:user) { create(:user) }
@@ -25,6 +26,7 @@ RSpec.describe User do
describe 'concerns' do
it_behaves_like 'access_tokenable'
it_behaves_like 'avatarable'
end
describe 'pubsub_token' do