fix: "wa_source_id" function return value (#5451)

- Fix contact inbox builder returning invalid WhatsApp source id
- Add specs to cover source id validations

Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
OMAR.A 2022-09-29 19:34:55 +02:00 committed by GitHub
parent 74e03f9beb
commit 1819041f5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 143 additions and 28 deletions

View file

@ -31,7 +31,7 @@ class ContactInboxBuilder
return unless @contact.phone_number return unless @contact.phone_number
# whatsapp doesn't want the + in e164 format # whatsapp doesn't want the + in e164 format
"#{@contact.phone_number}.delete('+')" @contact.phone_number.delete('+').to_s
end end
def twilio_source_id def twilio_source_id

View file

@ -22,6 +22,7 @@
class ContactInbox < ApplicationRecord class ContactInbox < ApplicationRecord
include Pubsubable include Pubsubable
include RegexHelper
validates :inbox_id, presence: true validates :inbox_id, presence: true
validates :contact_id, presence: true validates :contact_id, presence: true
validates :source_id, presence: true validates :source_id, presence: true
@ -51,10 +52,10 @@ class ContactInbox < ApplicationRecord
def validate_twilio_source_id def validate_twilio_source_id
# https://www.twilio.com/docs/glossary/what-e164#regex-matching-for-e164 # https://www.twilio.com/docs/glossary/what-e164#regex-matching-for-e164
if inbox.channel.medium == 'sms' && !/\+[1-9]\d{1,14}\z/.match?(source_id) if inbox.channel.medium == 'sms' && !TWILIO_CHANNEL_SMS_REGEX.match?(source_id)
errors.add(:source_id, 'invalid source id for twilio sms inbox. valid Regex /\+[1-9]\d{1,14}\z/') errors.add(:source_id, "invalid source id for twilio sms inbox. valid Regex #{TWILIO_CHANNEL_SMS_REGEX}")
elsif inbox.channel.medium == 'whatsapp' && !/whatsapp:\+[1-9]\d{1,14}\z/.match?(source_id) elsif inbox.channel.medium == 'whatsapp' && !TWILIO_CHANNEL_WHATSAPP_REGEX.match?(source_id)
errors.add(:source_id, 'invalid source id for twilio whatsapp inbox. valid Regex /whatsapp:\+[1-9]\d{1,14}\z/') errors.add(:source_id, "invalid source id for twilio whatsapp inbox. valid Regex #{TWILIO_CHANNEL_WHATSAPP_REGEX}")
end end
end end
@ -62,8 +63,15 @@ class ContactInbox < ApplicationRecord
errors.add(:source_id, "invalid source id for Email inbox. valid Regex #{Devise.email_regexp}") unless Devise.email_regexp.match?(source_id) errors.add(:source_id, "invalid source id for Email inbox. valid Regex #{Devise.email_regexp}") unless Devise.email_regexp.match?(source_id)
end end
def validate_whatsapp_source_id
return if WHATSAPP_CHANNEL_REGEX.match?(source_id)
errors.add(:source_id, "invalid source id for whatsapp inbox. valid Regex #{WHATSAPP_CHANNEL_REGEX}")
end
def valid_source_id_format? def valid_source_id_format?
validate_twilio_source_id if inbox.channel_type == 'Channel::TwilioSms' validate_twilio_source_id if inbox.channel_type == 'Channel::TwilioSms'
validate_email_source_id if inbox.channel_type == 'Channel::Email' validate_email_source_id if inbox.channel_type == 'Channel::Email'
validate_whatsapp_source_id if inbox.channel_type == 'Channel::Whatsapp'
end end
end end

View file

@ -6,4 +6,8 @@ module RegexHelper
# shouldn't start with a underscore or hyphen # shouldn't start with a underscore or hyphen
UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE = Regexp.new('\A[\p{L}\p{N}]+[\p{L}\p{N}_-]+\Z') UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE = Regexp.new('\A[\p{L}\p{N}]+[\p{L}\p{N}_-]+\Z')
MENTION_REGEX = Regexp.new('\[(@[\w_. ]+)\]\(mention://(?:user|team)/\d+/(.*?)+\)') MENTION_REGEX = Regexp.new('\[(@[\w_. ]+)\]\(mention://(?:user|team)/\d+/(.*?)+\)')
TWILIO_CHANNEL_SMS_REGEX = Regexp.new('^\+\d{1,14}\z')
TWILIO_CHANNEL_WHATSAPP_REGEX = Regexp.new('^whatsapp:\+\d{1,14}\z')
WHATSAPP_CHANNEL_REGEX = Regexp.new('^\d{1,14}\z')
end end

View file

@ -17,7 +17,7 @@ describe ::ContactInboxBuilder do
source_id: contact.phone_number source_id: contact.phone_number
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do
@ -27,7 +27,7 @@ describe ::ContactInboxBuilder do
inbox_id: twilio_inbox.id inbox_id: twilio_inbox.id
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'creates a new contact inbox when different source id is provided' do it 'creates a new contact inbox when different source id is provided' do
@ -38,8 +38,8 @@ describe ::ContactInboxBuilder do
source_id: '+224213223422' source_id: '+224213223422'
).perform ).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id) expect(contact_inbox.id).not_to eq(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('+224213223422') expect(contact_inbox.source_id).to eq('+224213223422')
end end
it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do
@ -48,7 +48,7 @@ describe ::ContactInboxBuilder do
inbox_id: twilio_inbox.id inbox_id: twilio_inbox.id
).perform ).perform
expect(contact_inbox.source_id).not_to be(contact.phone_number) expect(contact_inbox.source_id).to eq(contact.phone_number)
end end
end end
@ -64,7 +64,7 @@ describe ::ContactInboxBuilder do
source_id: "whatsapp:#{contact.phone_number}" source_id: "whatsapp:#{contact.phone_number}"
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do
@ -74,7 +74,7 @@ describe ::ContactInboxBuilder do
inbox_id: twilio_inbox.id inbox_id: twilio_inbox.id
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'creates a new contact inbox when different source id is provided' do it 'creates a new contact inbox when different source id is provided' do
@ -85,8 +85,8 @@ describe ::ContactInboxBuilder do
source_id: 'whatsapp:+555555' source_id: 'whatsapp:+555555'
).perform ).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id) expect(contact_inbox.id).not_to eq(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('whatsapp:+55555') expect(contact_inbox.source_id).to eq('whatsapp:+555555')
end end
it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do
@ -95,7 +95,53 @@ describe ::ContactInboxBuilder do
inbox_id: twilio_inbox.id inbox_id: twilio_inbox.id
).perform ).perform
expect(contact_inbox.source_id).not_to be("whatsapp:#{contact.phone_number}") expect(contact_inbox.source_id).to eq("whatsapp:#{contact.phone_number}")
end
end
describe 'whatsapp inbox' do
let(:whatsapp_inbox) { create(:channel_whatsapp, account: account, sync_templates: false, validate_provider_config: false).inbox }
it 'does not create contact inbox when contact inbox already exists with the source id provided' do
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: contact.phone_number&.delete('+'))
contact_inbox = described_class.new(
contact_id: contact.id,
inbox_id: whatsapp_inbox.id,
source_id: contact.phone_number&.delete('+')
).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id)
end
it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: contact.phone_number&.delete('+'))
contact_inbox = described_class.new(
contact_id: contact.id,
inbox_id: whatsapp_inbox.id
).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id)
end
it 'creates a new contact inbox when different source id is provided' do
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: contact.phone_number&.delete('+'))
contact_inbox = described_class.new(
contact_id: contact.id,
inbox_id: whatsapp_inbox.id,
source_id: '555555'
).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('555555')
end
it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do
contact_inbox = described_class.new(
contact_id: contact.id,
inbox_id: whatsapp_inbox.id
).perform
expect(contact_inbox.source_id).to eq(contact.phone_number&.delete('+'))
end end
end end
@ -111,7 +157,7 @@ describe ::ContactInboxBuilder do
source_id: contact.phone_number source_id: contact.phone_number
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do
@ -121,7 +167,7 @@ describe ::ContactInboxBuilder do
inbox_id: sms_inbox.id inbox_id: sms_inbox.id
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'creates a new contact inbox when different source id is provided' do it 'creates a new contact inbox when different source id is provided' do
@ -132,8 +178,8 @@ describe ::ContactInboxBuilder do
source_id: '+224213223422' source_id: '+224213223422'
).perform ).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id) expect(contact_inbox.id).not_to eq(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('+224213223422') expect(contact_inbox.source_id).to eq('+224213223422')
end end
it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do
@ -142,7 +188,7 @@ describe ::ContactInboxBuilder do
inbox_id: sms_inbox.id inbox_id: sms_inbox.id
).perform ).perform
expect(contact_inbox.source_id).not_to be(contact.phone_number) expect(contact_inbox.source_id).to eq(contact.phone_number)
end end
end end
@ -158,7 +204,7 @@ describe ::ContactInboxBuilder do
source_id: contact.email source_id: contact.email
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'does not create contact inbox when contact inbox already exists with email and source id is not provided' do it 'does not create contact inbox when contact inbox already exists with email and source id is not provided' do
@ -168,7 +214,7 @@ describe ::ContactInboxBuilder do
inbox_id: email_inbox.id inbox_id: email_inbox.id
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'creates a new contact inbox when different source id is provided' do it 'creates a new contact inbox when different source id is provided' do
@ -179,8 +225,8 @@ describe ::ContactInboxBuilder do
source_id: 'xyc@xyc.com' source_id: 'xyc@xyc.com'
).perform ).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id) expect(contact_inbox.id).not_to eq(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('xyc@xyc.com') expect(contact_inbox.source_id).to eq('xyc@xyc.com')
end end
it 'creates a contact inbox with contact email when source id not provided and no contact inbox exists' do it 'creates a contact inbox with contact email when source id not provided and no contact inbox exists' do
@ -189,7 +235,7 @@ describe ::ContactInboxBuilder do
inbox_id: email_inbox.id inbox_id: email_inbox.id
).perform ).perform
expect(contact_inbox.source_id).not_to be(contact.email) expect(contact_inbox.source_id).to eq(contact.email)
end end
end end
@ -205,7 +251,7 @@ describe ::ContactInboxBuilder do
source_id: 'test' source_id: 'test'
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'creates a new contact inbox when different source id is provided' do it 'creates a new contact inbox when different source id is provided' do
@ -216,8 +262,8 @@ describe ::ContactInboxBuilder do
source_id: 'test' source_id: 'test'
).perform ).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id) expect(contact_inbox.id).not_to eq(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('test') expect(contact_inbox.source_id).to eq('test')
end end
it 'creates a contact inbox with SecureRandom.uuid when source id not provided and no contact inbox exists' do it 'creates a contact inbox with SecureRandom.uuid when source id not provided and no contact inbox exists' do

View file

@ -15,6 +15,8 @@ def generate_source_id(contact_inbox)
contact_inbox.inbox.channel.medium == 'sms' ? Faker::PhoneNumber.cell_phone_in_e164 : "whatsapp:#{Faker::PhoneNumber.cell_phone_in_e164}" contact_inbox.inbox.channel.medium == 'sms' ? Faker::PhoneNumber.cell_phone_in_e164 : "whatsapp:#{Faker::PhoneNumber.cell_phone_in_e164}"
when 'Channel::Email' when 'Channel::Email'
"#{SecureRandom.uuid}@acme.inc" "#{SecureRandom.uuid}@acme.inc"
when 'Channel::Whatsapp'
Faker::PhoneNumber.cell_phone_in_e164.delete('+')
else else
SecureRandom.uuid SecureRandom.uuid
end end

View file

@ -37,4 +37,59 @@ RSpec.describe ContactInbox do
expect(obj.pubsub_token).to eq(new_token) expect(obj.pubsub_token).to eq(new_token)
end end
end end
describe 'validations' do
context 'when source_id' do
it 'validates whatsapp channel source_id' do
whatsapp_inbox = create(:channel_whatsapp, sync_templates: false, validate_provider_config: false).inbox
contact = create(:contact)
valid_source_id = build(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: '1234567890')
ci_character_in_source_id = build(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: '1234567890aaa')
ci_plus_in_source_id = build(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: '+1234567890')
expect(valid_source_id.valid?).to be(true)
expect(ci_character_in_source_id.valid?).to be(false)
expect(ci_character_in_source_id.errors.full_messages).to eq(
['Source invalid source id for whatsapp inbox. valid Regex (?-mix:^\\d{1,14}\\z)']
)
expect(ci_plus_in_source_id.valid?).to be(false)
expect(ci_plus_in_source_id.errors.full_messages).to eq(
['Source invalid source id for whatsapp inbox. valid Regex (?-mix:^\\d{1,14}\\z)']
)
end
it 'validates twilio sms channel source_id' do
twilio_sms_inbox = create(:channel_twilio_sms).inbox
contact = create(:contact)
valid_source_id = build(:contact_inbox, contact: contact, inbox: twilio_sms_inbox, source_id: '+1234567890')
ci_character_in_source_id = build(:contact_inbox, contact: contact, inbox: twilio_sms_inbox, source_id: '+1234567890aaa')
ci_without_plus_in_source_id = build(:contact_inbox, contact: contact, inbox: twilio_sms_inbox, source_id: '1234567890')
expect(valid_source_id.valid?).to be(true)
expect(ci_character_in_source_id.valid?).to be(false)
expect(ci_character_in_source_id.errors.full_messages).to eq(
['Source invalid source id for twilio sms inbox. valid Regex (?-mix:^\\+\\d{1,14}\\z)']
)
expect(ci_without_plus_in_source_id.valid?).to be(false)
expect(ci_without_plus_in_source_id.errors.full_messages).to eq(
['Source invalid source id for twilio sms inbox. valid Regex (?-mix:^\\+\\d{1,14}\\z)']
)
end
it 'validates twilio whatsapp channel source_id' do
twilio_whatsapp_inbox = create(:channel_twilio_sms, medium: :whatsapp).inbox
contact = create(:contact)
valid_source_id = build(:contact_inbox, contact: contact, inbox: twilio_whatsapp_inbox, source_id: 'whatsapp:+1234567890')
ci_character_in_source_id = build(:contact_inbox, contact: contact, inbox: twilio_whatsapp_inbox, source_id: 'whatsapp:+1234567890aaa')
ci_without_plus_in_source_id = build(:contact_inbox, contact: contact, inbox: twilio_whatsapp_inbox, source_id: 'whatsapp:1234567890')
expect(valid_source_id.valid?).to be(true)
expect(ci_character_in_source_id.valid?).to be(false)
expect(ci_character_in_source_id.errors.full_messages).to eq(
['Source invalid source id for twilio whatsapp inbox. valid Regex (?-mix:^whatsapp:\\+\\d{1,14}\\z)']
)
expect(ci_without_plus_in_source_id.valid?).to be(false)
expect(ci_without_plus_in_source_id.errors.full_messages).to eq(
['Source invalid source id for twilio whatsapp inbox. valid Regex (?-mix:^whatsapp:\\+\\d{1,14}\\z)']
)
end
end
end
end end