Navigation Menu

Skip to content

Commit

Permalink
chore: Fix contact model silently discarding invalid attributes (#4994)
Browse files Browse the repository at this point in the history
fixes: #4775
  • Loading branch information
sojan-official committed Jul 8, 2022
1 parent bca3471 commit e4b159d
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 24 deletions.
7 changes: 4 additions & 3 deletions app/actions/contact_identify_action.rb
Expand Up @@ -6,7 +6,7 @@
# We don't want to update the name of the identified original contact.

class ContactIdentifyAction
pattr_initialize [:contact!, :params!, { retain_original_contact_name: false }]
pattr_initialize [:contact!, :params!, { retain_original_contact_name: false, discard_invalid_attrs: false }]

def perform
@attributes_to_update = [:identifier, :name, :email, :phone_number]
Expand Down Expand Up @@ -97,12 +97,13 @@ def mergable_phone_contact?
end

def update_contact
params_to_update = params.slice(*@attributes_to_update).reject do |_k, v|
@contact.attributes = params.slice(*@attributes_to_update).reject do |_k, v|
v.blank?
end.merge({ custom_attributes: custom_attributes, additional_attributes: additional_attributes })
# blank identifier or email will throw unique index error
# TODO: replace reject { |_k, v| v.blank? } with compact_blank when rails is upgraded
@contact.update!(params_to_update)
@contact.discard_invalid_attrs if discard_invalid_attrs
@contact.save!
ContactAvatarJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present?
end

Expand Down
3 changes: 2 additions & 1 deletion app/controllers/api/v1/widget/contacts_controller.rb
Expand Up @@ -36,7 +36,8 @@ def destroy_custom_attributes
def identify_contact(contact)
contact_identify_action = ContactIdentifyAction.new(
contact: contact,
params: permitted_params.to_h.deep_symbolize_keys
params: permitted_params.to_h.deep_symbolize_keys,
discard_invalid_attrs: true
)
@contact = contact_identify_action.perform
end
Expand Down
12 changes: 9 additions & 3 deletions app/models/contact.rb
Expand Up @@ -28,10 +28,12 @@ class Contact < ApplicationRecord
include Labelable

validates :account_id, presence: true
validates :email, allow_blank: true, uniqueness: { scope: [:account_id], case_sensitive: false }
validates :email, allow_blank: true, uniqueness: { scope: [:account_id], case_sensitive: false },
format: { with: Devise.email_regexp, message: 'Invalid email' }
validates :identifier, allow_blank: true, uniqueness: { scope: [:account_id] }
validates :phone_number,
allow_blank: true, uniqueness: { scope: [:account_id] }
allow_blank: true, uniqueness: { scope: [:account_id] },
format: { with: /\+[1-9]\d{1,14}\z/, message: 'Should be in e164 format' }
validates :name, length: { maximum: 255 }

belongs_to :account
Expand All @@ -42,7 +44,6 @@ class Contact < ApplicationRecord
has_many :messages, as: :sender, dependent: :destroy_async
has_many :notes, dependent: :destroy_async
before_validation :prepare_contact_attributes
before_save :phone_number_format, :email_format
after_create_commit :dispatch_create_event, :ip_lookup
after_update_commit :dispatch_update_event
after_destroy_commit :dispatch_destroy_event
Expand Down Expand Up @@ -134,6 +135,11 @@ def self.resolved_contacts
).or(Current.account.contacts.where.not(identifier: [nil, '']))
end

def discard_invalid_attrs
phone_number_format
email_format
end

private

def ip_lookup
Expand Down
18 changes: 18 additions & 0 deletions spec/actions/contact_identify_action_spec.rb
Expand Up @@ -115,5 +115,23 @@
expect { contact.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end

context 'when discard_invalid_attrs is set to false' do
it 'will not update the name of the existing contact' do
params = { email: 'blah blah blah', name: 'new name' }
expect do
described_class.new(contact: contact, params: params, retain_original_contact_name: true).perform
end.to raise_error(ActiveRecord::RecordInvalid)
end
end

context 'when discard_invalid_attrs is set to true' do
it 'will not update the name of the existing contact' do
params = { phone_number: 'blahblah blah', name: 'new name' }
described_class.new(contact: contact, params: params, discard_invalid_attrs: true).perform
expect(contact.reload.name).to eq 'new name'
expect(contact.phone_number).to eq nil
end
end
end
end
2 changes: 1 addition & 1 deletion spec/controllers/api/v1/widget/contacts_controller_spec.rb
Expand Up @@ -33,7 +33,7 @@
as: :json

expect(response).to have_http_status(:success)
expected_params = { contact: contact, params: params }
expected_params = { contact: contact, params: params, discard_invalid_attrs: true }
expect(ContactIdentifyAction).to have_received(:new).with(expected_params)
expect(identify_action).to have_received(:perform)
end
Expand Down
20 changes: 4 additions & 16 deletions spec/models/contact_spec.rb
Expand Up @@ -44,41 +44,29 @@
end
end

# rubocop:disable Rails/SkipsModelValidations
context 'when phone number format' do
it 'will not throw error for existing invalid phone number' do
it 'will throw error for existing invalid phone number' do
contact = create(:contact)
contact.update_column(:phone_number, '344234')
contact.reload
expect(contact.update!(name: 'test')).to eq true
expect(contact.phone_number).to eq '344234'
expect { contact.update!(phone_number: '123456789') }.to raise_error(ActiveRecord::RecordInvalid)
end

it 'updates phone number when adding valid phone number' do
contact = create(:contact)
contact.update_column(:phone_number, '344234')
contact.reload
expect(contact.update!(phone_number: '+12312312321')).to eq true
expect(contact.phone_number).to eq '+12312312321'
end
end

context 'when email format' do
it 'will not throw error for existing invalid email' do
it 'will throw error for existing invalid email' do
contact = create(:contact)
contact.update_column(:email, 'ssfdasdf <test@test')
contact.reload
expect(contact.update!(name: 'test')).to eq true
expect(contact.email).to eq 'ssfdasdf <test@test'
expect { contact.update!(email: '<2324234234') }.to raise_error(ActiveRecord::RecordInvalid)
end

it 'updates email when adding valid email' do
contact = create(:contact)
contact.update_column(:email, 'ssfdasdf <test@test')
contact.reload
expect(contact.update!(email: 'test@test.com')).to eq true
expect(contact.email).to eq 'test@test.com'
end
end
# rubocop:enable Rails/SkipsModelValidations
end

0 comments on commit e4b159d

Please sign in to comment.