From 45e43b0b895d5c31215c08bde8530ffc73da5a1a Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Thu, 15 Apr 2021 15:13:01 +0530 Subject: [PATCH] feat: Contactable Inboxes API (#2101) - Add endpoint which lists inboxes through which a contact can be contacted - Conversation creation API auto-creates contact_inbox for specific channels [ Twilio, email, api] - Ability to send the initial message payload along with the conversation creation - Fixes #1678 ( issue saving additional attributes for conversation ) --- app/builders/contact_inbox_builder.rb | 41 ++++ .../api/v1/accounts/contacts_controller.rb | 6 +- .../conversations/messages_controller.rb | 2 +- .../v1/accounts/conversations_controller.rb | 20 +- app/models/contact.rb | 4 +- app/models/contact_inbox.rb | 8 +- app/policies/contact_policy.rb | 4 + .../contacts/contactable_inboxes_service.rb | 52 ++++ .../contactable_inboxes.json.jbuilder | 8 + config/routes.rb | 3 + spec/builders/contact_inbox_builder_spec.rb | 231 ++++++++++++++++++ .../v1/accounts/contacts_controller_spec.rb | 28 +++ .../accounts/conversations_controller_spec.rb | 31 ++- .../contactable_inboxes_service_spec.rb | 66 +++++ 14 files changed, 494 insertions(+), 10 deletions(-) create mode 100644 app/builders/contact_inbox_builder.rb create mode 100644 app/services/contacts/contactable_inboxes_service.rb create mode 100644 app/views/api/v1/accounts/contacts/contactable_inboxes.json.jbuilder create mode 100644 spec/builders/contact_inbox_builder_spec.rb create mode 100644 spec/services/contacts/contactable_inboxes_service_spec.rb diff --git a/app/builders/contact_inbox_builder.rb b/app/builders/contact_inbox_builder.rb new file mode 100644 index 000000000..73380190d --- /dev/null +++ b/app/builders/contact_inbox_builder.rb @@ -0,0 +1,41 @@ +class ContactInboxBuilder + pattr_initialize [:contact_id!, :inbox_id!, :source_id] + + def perform + @contact = Contact.find(contact_id) + @inbox = @contact.account.inboxes.find(inbox_id) + return unless ['Channel::TwilioSms', 'Channel::Email', 'Channel::Api'].include? @inbox.channel_type + + source_id = @source_id || generate_source_id + create_contact_inbox(source_id) if source_id.present? + end + + private + + def generate_source_id + return twilio_source_id if @inbox.channel_type == 'Channel::TwilioSms' + return @contact.email if @inbox.channel_type == 'Channel::Email' + return SecureRandom.uuid if @inbox.channel_type == 'Channel::Api' + + nil + end + + def twilio_source_id + return unless @contact.phone_number + + case @inbox.channel.medium + when 'sms' + @contact.phone_number + when 'whatsapp' + "whatsapp:#{@contact.phone_number}" + end + end + + def create_contact_inbox(source_id) + ::ContactInbox.find_or_create_by!( + contact_id: @contact.id, + inbox_id: @inbox.id, + source_id: source_id + ) + end +end diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index 37a583f05..375eb9313 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -4,7 +4,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController before_action :check_authorization before_action :set_current_page, only: [:index, :active, :search] - before_action :fetch_contact, only: [:show, :update] + before_action :fetch_contact, only: [:show, :update, :contactable_inboxes] def index @contacts_count = resolved_contacts.count @@ -40,6 +40,10 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController def show; end + def contactable_inboxes + @contactable_inboxes = Contacts::ContactableInboxesService.new(contact: @contact).get + end + def create ActiveRecord::Base.transaction do @contact = Current.account.contacts.new(contact_params) diff --git a/app/controllers/api/v1/accounts/conversations/messages_controller.rb b/app/controllers/api/v1/accounts/conversations/messages_controller.rb index 8cfea20ba..ffd00461b 100644 --- a/app/controllers/api/v1/accounts/conversations/messages_controller.rb +++ b/app/controllers/api/v1/accounts/conversations/messages_controller.rb @@ -4,7 +4,7 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts:: end def create - user = current_user || @resource + user = Current.user || @resource mb = Messages::MessageBuilder.new(user, @conversation, params) @message = mb.perform rescue StandardError => e diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 5dddf8036..dccb56a95 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -22,7 +22,10 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro end def create - @conversation = ::Conversation.create!(conversation_params) + ActiveRecord::Base.transaction do + @conversation = ::Conversation.create!(conversation_params) + Messages::MessageBuilder.new(Current.user, @conversation, params[:message]).perform if params[:message].present? + end end def show; end @@ -78,16 +81,29 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro end def contact_inbox + @contact_inbox = build_contact_inbox + @contact_inbox ||= ::ContactInbox.find_by!(source_id: params[:source_id]) end + def build_contact_inbox + return if params[:contact_id].blank? || params[:inbox_id].blank? + + ContactInboxBuilder.new( + contact_id: params[:contact_id], + inbox_id: params[:inbox_id], + source_id: params[:source_id] + ).perform + end + def conversation_params + additional_attributes = params[:additional_attributes]&.permit! || {} { account_id: Current.account.id, inbox_id: @contact_inbox.inbox_id, contact_id: @contact_inbox.contact_id, contact_inbox_id: @contact_inbox.id, - additional_attributes: params[:additional_attributes] + additional_attributes: additional_attributes } end diff --git a/app/models/contact.rb b/app/models/contact.rb index 3d7eedf9a..47eb5352d 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -31,7 +31,9 @@ class Contact < ApplicationRecord validates :account_id, presence: true validates :email, allow_blank: true, uniqueness: { scope: [:account_id], case_sensitive: false } validates :identifier, allow_blank: true, uniqueness: { scope: [:account_id] } - validates :phone_number, allow_blank: true, uniqueness: { scope: [:account_id] } + validates :phone_number, + allow_blank: true, uniqueness: { scope: [:account_id] }, + format: { with: /\+[1-9]\d{1,14}\z/, message: 'should be in e164 format' } belongs_to :account has_many :conversations, dependent: :destroy diff --git a/app/models/contact_inbox.rb b/app/models/contact_inbox.rb index 9d7d3d5ad..fe268b605 100644 --- a/app/models/contact_inbox.rb +++ b/app/models/contact_inbox.rb @@ -53,10 +53,10 @@ class ContactInbox < ApplicationRecord def validate_twilio_source_id # https://www.twilio.com/docs/glossary/what-e164#regex-matching-for-e164 - if inbox.channel.medium == 'sms' && !/^\+[1-9]\d{1,14}$/.match?(source_id) - errors.add(:source_id, 'invalid source id for twilio sms inbox. valid Regex /^\+[1-9]\d{1,14}$/') - elsif inbox.channel.medium == 'whatsapp' && !/^whatsapp:\+[1-9]\d{1,14}$/.match?(source_id) - errors.add(:source_id, 'invalid source id for twilio whatsapp inbox. valid Regex /^whatsapp:\+[1-9]\d{1,14}$/') + if inbox.channel.medium == 'sms' && !/\+[1-9]\d{1,14}\z/.match?(source_id) + errors.add(:source_id, 'invalid source id for twilio sms inbox. valid Regex /\+[1-9]\d{1,14}\z/') + elsif inbox.channel.medium == 'whatsapp' && !/whatsapp:\+[1-9]\d{1,14}\z/.match?(source_id) + errors.add(:source_id, 'invalid source id for twilio whatsapp inbox. valid Regex /whatsapp:\+[1-9]\d{1,14}\z/') end end diff --git a/app/policies/contact_policy.rb b/app/policies/contact_policy.rb index b5ec2f78c..fb4cd4009 100644 --- a/app/policies/contact_policy.rb +++ b/app/policies/contact_policy.rb @@ -19,6 +19,10 @@ class ContactPolicy < ApplicationPolicy true end + def contactable_inboxes? + true + end + def show? true end diff --git a/app/services/contacts/contactable_inboxes_service.rb b/app/services/contacts/contactable_inboxes_service.rb new file mode 100644 index 000000000..fccd88f8d --- /dev/null +++ b/app/services/contacts/contactable_inboxes_service.rb @@ -0,0 +1,52 @@ +class Contacts::ContactableInboxesService + pattr_initialize [:contact!] + + def get + account = contact.account + account.inboxes.map { |inbox| get_contactable_inbox(inbox) }.compact + end + + private + + def get_contactable_inbox(inbox) + return twilio_contactable_inbox(inbox) if inbox.channel_type == 'Channel::TwilioSms' + return email_contactable_inbox(inbox) if inbox.channel_type == 'Channel::Email' + return api_contactable_inbox(inbox) if inbox.channel_type == 'Channel::Api' + return website_contactable_inbox(inbox) if inbox.channel_type == 'Channel::WebWidget' + + nil + end + + def website_contactable_inbox(inbox) + latest_contact_inbox = inbox.contact_inboxes.where(contact: @contact).last + return unless latest_contact_inbox + # FIXME : change this when multiple conversations comes in + return if latest_contact_inbox.conversations.present? + + { source_id: latest_contact_inbox.source_id, inbox: inbox } + end + + def api_contactable_inbox(inbox) + latest_contact_inbox = inbox.contact_inboxes.where(contact: @contact).last + source_id = latest_contact_inbox&.source_id || SecureRandom.uuid + + { source_id: source_id, inbox: inbox } + end + + def email_contactable_inbox(inbox) + return unless @contact.email + + { source_id: @contact.email, inbox: inbox } + end + + def twilio_contactable_inbox(inbox) + return unless @contact.phone_number + + case inbox.channel.medium + when 'sms' + { source_id: @contact.phone_number, inbox: inbox } + when 'whatsapp' + { source_id: "whatsapp:#{@contact.phone_number}", inbox: inbox } + end + end +end diff --git a/app/views/api/v1/accounts/contacts/contactable_inboxes.json.jbuilder b/app/views/api/v1/accounts/contacts/contactable_inboxes.json.jbuilder new file mode 100644 index 000000000..0e8b27d08 --- /dev/null +++ b/app/views/api/v1/accounts/contacts/contactable_inboxes.json.jbuilder @@ -0,0 +1,8 @@ +json.payload do + json.array! @contactable_inboxes do |contactable_inbox| + json.inbox do + json.partial! 'api/v1/models/inbox.json.jbuilder', resource: contactable_inbox[:inbox] + end + json.source_id contactable_inbox[:source_id] + end +end diff --git a/config/routes.rb b/config/routes.rb index cedb58a12..46a5aee9c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,6 +69,9 @@ Rails.application.routes.draw do get :search post :import end + member do + get :contactable_inboxes + end scope module: :contacts do resources :conversations, only: [:index] resources :contact_inboxes, only: [:create] diff --git a/spec/builders/contact_inbox_builder_spec.rb b/spec/builders/contact_inbox_builder_spec.rb new file mode 100644 index 000000000..fead65f36 --- /dev/null +++ b/spec/builders/contact_inbox_builder_spec.rb @@ -0,0 +1,231 @@ +require 'rails_helper' + +describe ::ContactInboxBuilder do + let(:account) { create(:account) } + let(:contact) { create(:contact, account: account) } + + describe '#perform' do + describe 'twilio sms inbox' do + let!(:twilio_sms) { create(:channel_twilio_sms, account: account) } + let!(:twilio_inbox) { create(:inbox, channel: twilio_sms, account: account) } + + 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: twilio_inbox, source_id: contact.phone_number) + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: twilio_inbox.id, + source_id: contact.phone_number + ).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: twilio_inbox, source_id: contact.phone_number) + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: twilio_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: twilio_inbox, source_id: contact.phone_number) + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: twilio_inbox.id, + source_id: '+224213223422' + ).perform + + expect(contact_inbox.id).not_to be(existing_contact_inbox.id) + expect(contact_inbox.source_id).not_to be('+224213223422') + 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: twilio_inbox.id + ).perform + + expect(contact_inbox.source_id).not_to be(contact.phone_number) + end + end + + describe 'twilio whatsapp inbox' do + let!(:twilio_whatsapp) { create(:channel_twilio_sms, medium: :whatsapp, account: account) } + let!(:twilio_inbox) { create(:inbox, channel: twilio_whatsapp, account: account) } + + 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: twilio_inbox, source_id: "whatsapp:#{contact.phone_number}") + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: twilio_inbox.id, + source_id: "whatsapp:#{contact.phone_number}" + ).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: twilio_inbox, source_id: "whatsapp:#{contact.phone_number}") + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: twilio_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: twilio_inbox, source_id: "whatsapp:#{contact.phone_number}") + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: twilio_inbox.id, + source_id: 'whatsapp:+555555' + ).perform + + expect(contact_inbox.id).not_to be(existing_contact_inbox.id) + expect(contact_inbox.source_id).not_to be('whatsapp:+55555') + 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: twilio_inbox.id + ).perform + + expect(contact_inbox.source_id).not_to be("whatsapp:#{contact.phone_number}") + end + end + + describe 'email inbox' do + let!(:email_channel) { create(:channel_email, account: account) } + let!(:email_inbox) { create(:inbox, channel: email_channel, account: account) } + + 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: email_inbox, source_id: contact.email) + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: email_inbox.id, + source_id: contact.email + ).perform + + expect(contact_inbox.id).to be(existing_contact_inbox.id) + end + + it 'does not create contact inbox when contact inbox already exists with email and source id is not provided' do + existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: email_inbox, source_id: contact.email) + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: email_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: email_inbox, source_id: contact.email) + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: email_inbox.id, + source_id: 'xyc@xyc.com' + ).perform + + expect(contact_inbox.id).not_to be(existing_contact_inbox.id) + expect(contact_inbox.source_id).not_to be('xyc@xyc.com') + end + + it 'creates a contact inbox with contact email when source id not provided and no contact inbox exists' do + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: email_inbox.id + ).perform + + expect(contact_inbox.source_id).not_to be(contact.email) + end + end + + describe 'api inbox' do + let!(:api_channel) { create(:channel_api, account: account) } + let!(:api_inbox) { create(:inbox, channel: api_channel, account: account) } + + 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: api_inbox, source_id: 'test') + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: api_inbox.id, + source_id: 'test' + ).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: api_inbox, source_id: SecureRandom.uuid) + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: api_inbox.id, + source_id: 'test' + ).perform + + expect(contact_inbox.id).not_to be(existing_contact_inbox.id) + expect(contact_inbox.source_id).not_to be('test') + end + + it 'creates a contact inbox with SecureRandom.uuid when source id not provided and no contact inbox exists' do + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: api_inbox.id + ).perform + + expect(contact_inbox.source_id).not_to be(nil) + end + end + + describe 'web widget' do + let!(:website_channel) { create(:channel_widget, account: account) } + let!(:website_inbox) { create(:inbox, channel: website_channel, account: account) } + + it 'does not create contact inbox' do + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: website_inbox.id, + source_id: 'test' + ).perform + + expect(contact_inbox).to be(nil) + end + end + + describe 'facebook inbox' do + let!(:facebook_channel) { create(:channel_facebook_page, account: account) } + let!(:facebook_inbox) { create(:inbox, channel: facebook_channel, account: account) } + + it 'does not create contact inbox' do + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: facebook_inbox.id, + source_id: 'test' + ).perform + + expect(contact_inbox).to be(nil) + end + end + + describe 'twitter inbox' do + let!(:twitter_channel) { create(:channel_twitter_profile, account: account) } + let!(:twitter_inbox) { create(:inbox, channel: twitter_channel, account: account) } + + it 'does not create contact inbox' do + contact_inbox = described_class.new( + contact_id: contact.id, + inbox_id: twitter_inbox.id, + source_id: 'test' + ).perform + + expect(contact_inbox).to be(nil) + end + end + end +end diff --git a/spec/controllers/api/v1/accounts/contacts_controller_spec.rb b/spec/controllers/api/v1/accounts/contacts_controller_spec.rb index 549614b26..8bc4e5724 100644 --- a/spec/controllers/api/v1/accounts/contacts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/contacts_controller_spec.rb @@ -189,6 +189,34 @@ RSpec.describe 'Contacts API', type: :request do end end + describe 'GET /api/v1/accounts/{account.id}/contacts/:id/contactable_inboxes' do + let!(:contact) { create(:contact, account: account) } + + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + get "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/contactable_inboxes" + + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated user' do + let(:admin) { create(:user, account: account, role: :administrator) } + + it 'shows the contact' do + inbox_service = double + allow(Contacts::ContactableInboxesService).to receive(:new).and_return(inbox_service) + allow(inbox_service).to receive(:get).and_return({}) + expect(inbox_service).to receive(:get).and_return({}) + get "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/contactable_inboxes", + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + end + end + end + describe 'POST /api/v1/accounts/{account.id}/contacts' do let(:custom_attributes) { { test: 'test', test1: 'test1' } } let(:valid_params) { { contact: { name: 'test', custom_attributes: custom_attributes } } } diff --git a/spec/controllers/api/v1/accounts/conversations_controller_spec.rb b/spec/controllers/api/v1/accounts/conversations_controller_spec.rb index e2e025e82..8d4b3de20 100644 --- a/spec/controllers/api/v1/accounts/conversations_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/conversations_controller_spec.rb @@ -151,14 +151,43 @@ RSpec.describe 'Conversations API', type: :request do it 'creates a new conversation' do allow(Rails.configuration.dispatcher).to receive(:dispatch) + additional_attributes = { test: 'test' } post "/api/v1/accounts/#{account.id}/conversations", headers: agent.create_new_auth_token, - params: { source_id: contact_inbox.source_id }, + params: { source_id: contact_inbox.source_id, additional_attributes: additional_attributes }, + as: :json + + expect(response).to have_http_status(:success) + response_data = JSON.parse(response.body, symbolize_names: true) + expect(response_data[:additional_attributes]).to eq(additional_attributes) + end + + it 'creates a new conversation with message when message is passed' do + allow(Rails.configuration.dispatcher).to receive(:dispatch) + post "/api/v1/accounts/#{account.id}/conversations", + headers: agent.create_new_auth_token, + params: { source_id: contact_inbox.source_id, message: { content: 'hi' } }, as: :json expect(response).to have_http_status(:success) response_data = JSON.parse(response.body, symbolize_names: true) expect(response_data[:additional_attributes]).to eq({}) + expect(account.conversations.find_by(display_id: response_data[:id]).messages.first.content).to eq 'hi' + end + + it 'calls contact inbox builder if contact_id and inbox_id is present' do + builder = double + contact = create(:contact, account: account) + inbox = create(:inbox, account: account) + allow(Rails.configuration.dispatcher).to receive(:dispatch) + allow(ContactInboxBuilder).to receive(:new).and_return(builder) + allow(builder).to receive(:perform) + expect(builder).to receive(:perform) + + post "/api/v1/accounts/#{account.id}/conversations", + headers: agent.create_new_auth_token, + params: { contact_id: contact.id, inbox_id: inbox.id }, + as: :json end end end diff --git a/spec/services/contacts/contactable_inboxes_service_spec.rb b/spec/services/contacts/contactable_inboxes_service_spec.rb new file mode 100644 index 000000000..03a4981e7 --- /dev/null +++ b/spec/services/contacts/contactable_inboxes_service_spec.rb @@ -0,0 +1,66 @@ +require 'rails_helper' + +describe Contacts::ContactableInboxesService do + let(:account) { create(:account) } + let(:contact) { create(:contact, account: account) } + let!(:twilio_sms) { create(:channel_twilio_sms, account: account) } + let!(:twilio_sms_inbox) { create(:inbox, channel: twilio_sms, account: account) } + let!(:twilio_whatsapp) { create(:channel_twilio_sms, medium: :whatsapp, account: account) } + let!(:twilio_whatsapp_inbox) { create(:inbox, channel: twilio_whatsapp, account: account) } + let!(:email_channel) { create(:channel_email, account: account) } + let!(:email_inbox) { create(:inbox, channel: email_channel, account: account) } + let!(:api_channel) { create(:channel_api, account: account) } + let!(:api_inbox) { create(:inbox, channel: api_channel, account: account) } + let!(:website_channel) { create(:channel_widget, account: account) } + let!(:website_inbox) { create(:inbox, channel: website_channel, account: account) } + + describe '#get' do + it 'returns the contactable inboxes for the contact' do + contactable_inboxes = described_class.new(contact: contact).get + + expect(contactable_inboxes).to include({ source_id: contact.phone_number, inbox: twilio_sms_inbox }) + expect(contactable_inboxes).to include({ source_id: "whatsapp:#{contact.phone_number}", inbox: twilio_whatsapp_inbox }) + expect(contactable_inboxes).to include({ source_id: contact.email, inbox: email_inbox }) + expect(contactable_inboxes.pluck(:inbox)).to include(api_inbox) + end + + it 'doest not return the non contactable inboxes for the contact' do + facebook_channel = create(:channel_facebook_page, account: account) + facebook_inbox = create(:inbox, channel: facebook_channel, account: account) + twitter_channel = create(:channel_twitter_profile, account: account) + twitter_inbox = create(:inbox, channel: twitter_channel, account: account) + + contactable_inboxes = described_class.new(contact: contact).get + + expect(contactable_inboxes.pluck(:inbox)).not_to include(website_inbox) + expect(contactable_inboxes.pluck(:inbox)).not_to include(facebook_inbox) + expect(contactable_inboxes.pluck(:inbox)).not_to include(twitter_inbox) + end + + context 'when api inbox is available' do + it 'returns existing source id if contact inbox exists' do + contact_inbox = create(:contact_inbox, inbox: api_inbox, contact: contact) + + contactable_inboxes = described_class.new(contact: contact).get + expect(contactable_inboxes).to include({ source_id: contact_inbox.source_id, inbox: api_inbox }) + end + end + + context 'when website inbox is available' do + it 'returns existing source id if contact inbox exists without any conversations' do + contact_inbox = create(:contact_inbox, inbox: website_inbox, contact: contact) + + contactable_inboxes = described_class.new(contact: contact).get + expect(contactable_inboxes).to include({ source_id: contact_inbox.source_id, inbox: website_inbox }) + end + + it 'does not return existing source id if contact inbox exists with conversations' do + contact_inbox = create(:contact_inbox, inbox: website_inbox, contact: contact) + create(:conversation, contact: contact, inbox: website_inbox, contact_inbox: contact_inbox) + + contactable_inboxes = described_class.new(contact: contact).get + expect(contactable_inboxes.pluck(:inbox)).not_to include(website_inbox) + end + end + end +end