mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	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 )
This commit is contained in:
		
							
								
								
									
										41
									
								
								app/builders/contact_inbox_builder.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								app/builders/contact_inbox_builder.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -19,6 +19,10 @@ class ContactPolicy < ApplicationPolicy | ||||
|     true | ||||
|   end | ||||
|  | ||||
|   def contactable_inboxes? | ||||
|     true | ||||
|   end | ||||
|  | ||||
|   def show? | ||||
|     true | ||||
|   end | ||||
|   | ||||
							
								
								
									
										52
									
								
								app/services/contacts/contactable_inboxes_service.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								app/services/contacts/contactable_inboxes_service.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -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 | ||||
| @@ -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] | ||||
|   | ||||
							
								
								
									
										231
									
								
								spec/builders/contact_inbox_builder_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								spec/builders/contact_inbox_builder_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -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 } } } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										66
									
								
								spec/services/contacts/contactable_inboxes_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								spec/services/contacts/contactable_inboxes_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
		Reference in New Issue
	
	Block a user
	 Sojan Jose
					Sojan Jose