diff --git a/spec/builders/messages/instagram/message_builder_spec.rb b/spec/builders/messages/instagram/message_builder_spec.rb index 8e863823a..386087fbe 100644 --- a/spec/builders/messages/instagram/message_builder_spec.rb +++ b/spec/builders/messages/instagram/message_builder_spec.rb @@ -17,40 +17,34 @@ describe Messages::Instagram::MessageBuilder do let!(:shared_reel_params) { build(:instagram_shared_reel_event).with_indifferent_access } let!(:instagram_story_reply_event) { build(:instagram_story_reply_event).with_indifferent_access } let!(:instagram_message_reply_event) { build(:instagram_message_reply_event).with_indifferent_access } - let!(:contact) { create(:contact, id: 'Sender-id-1', name: 'Jane Dae') } - let!(:contact_inbox) { create(:contact_inbox, contact_id: contact.id, inbox_id: instagram_inbox.id, source_id: 'Sender-id-1') } - let(:conversation) do - create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id, contact_id: contact.id) - end - let(:message) do - create(:message, account_id: account.id, inbox_id: instagram_inbox.id, conversation_id: conversation.id, message_type: 'outgoing', - source_id: 'message-id-1') - end describe '#perform' do before do instagram_channel.update(access_token: 'valid_instagram_token') - stub_request(:get, %r{https://graph\.instagram\.com/.*?/Sender-id-1\?.*}) + stub_request(:get, %r{https://graph\.instagram\.com/.*?/Sender-id-.*?\?.*}) .to_return( status: 200, - body: { - name: 'Jane', - username: 'some_user_name', - profile_pic: 'https://chatwoot-assets.local/sample.png', - id: 'Sender-id-1', - follower_count: 100, - is_user_follow_business: true, - is_business_follow_user: true, - is_verified_user: false - }.to_json, + body: proc { |request| + sender_id = request.uri.path.split('/').last.split('?').first + { + name: 'Jane', + username: 'some_user_name', + profile_pic: 'https://chatwoot-assets.local/sample.png', + id: sender_id, + follower_count: 100, + is_user_follow_business: true, + is_business_follow_user: true, + is_verified_user: false + }.to_json + }, headers: { 'Content-Type' => 'application/json' } ) end it 'creates contact and message for the instagram direct inbox' do messaging = dm_params[:entry][0]['messaging'][0] - contact_inbox + create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) described_class.new(messaging, instagram_inbox).perform instagram_inbox.reload @@ -63,13 +57,15 @@ describe Messages::Instagram::MessageBuilder do end it 'discard echo message already sent by chatwoot' do - conversation - message + messaging = dm_params[:entry][0]['messaging'][0] + contact = create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) + conversation = create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id, contact_id: contact.id) + create(:message, account_id: account.id, inbox_id: instagram_inbox.id, conversation_id: conversation.id, message_type: 'outgoing', + source_id: 'message-id-1') expect(instagram_inbox.conversations.count).to be 1 expect(instagram_inbox.messages.count).to be 1 - messaging = dm_params[:entry][0]['messaging'][0] messaging[:message][:mid] = 'message-id-1' # Set same source_id as the existing message described_class.new(messaging, instagram_inbox, outgoing_echo: true).perform @@ -81,6 +77,7 @@ describe Messages::Instagram::MessageBuilder do it 'discards duplicate messages from webhook events with the same message_id' do messaging = dm_params[:entry][0]['messaging'][0] + create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) described_class.new(messaging, instagram_inbox).perform initial_message_count = instagram_inbox.messages.count @@ -93,6 +90,7 @@ describe Messages::Instagram::MessageBuilder do it 'creates message for shared reel' do messaging = shared_reel_params[:entry][0]['messaging'][0] + create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) described_class.new(messaging, instagram_inbox).perform message = instagram_inbox.messages.first @@ -103,7 +101,9 @@ describe Messages::Instagram::MessageBuilder do end it 'creates message with story id' do - story_source_id = instagram_story_reply_event[:entry][0]['messaging'][0]['message']['mid'] + messaging = instagram_story_reply_event[:entry][0]['messaging'][0] + create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) + story_source_id = messaging['message']['mid'] stub_request(:get, %r{https://graph\.instagram\.com/.*?/#{story_source_id}\?.*}) .to_return( @@ -121,7 +121,6 @@ describe Messages::Instagram::MessageBuilder do headers: { 'Content-Type' => 'application/json' } ) - messaging = instagram_story_reply_event[:entry][0]['messaging'][0] described_class.new(messaging, instagram_inbox).perform message = instagram_inbox.messages.first @@ -134,10 +133,13 @@ describe Messages::Instagram::MessageBuilder do it 'creates message with reply to mid' do # Create first message to ensure reply to is valid first_messaging = dm_params[:entry][0]['messaging'][0] + sender_id = first_messaging['sender']['id'] + create_instagram_contact_for_sender(sender_id, instagram_inbox) described_class.new(first_messaging, instagram_inbox).perform - # Create second message with reply to mid + # Create second message with reply to mid, using same sender_id messaging = instagram_message_reply_event[:entry][0]['messaging'][0] + messaging['sender']['id'] = sender_id described_class.new(messaging, instagram_inbox).perform first_message = instagram_inbox.messages.first @@ -148,12 +150,13 @@ describe Messages::Instagram::MessageBuilder do end it 'handles deleted story' do - story_source_id = story_mention_params[:entry][0][:messaging][0]['message']['mid'] + messaging = story_mention_params[:entry][0][:messaging][0] + create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) + story_source_id = messaging['message']['mid'] stub_request(:get, %r{https://graph\.instagram\.com/.*?/#{story_source_id}\?.*}) .to_return(status: 404, body: { error: { message: 'Story not found', code: 1_609_005 } }.to_json) - messaging = story_mention_params[:entry][0][:messaging][0] described_class.new(messaging, instagram_inbox).perform message = instagram_inbox.messages.first @@ -163,11 +166,12 @@ describe Messages::Instagram::MessageBuilder do end it 'does not create message for unsupported file type' do - conversation + messaging = story_mention_params[:entry][0][:messaging][0] + contact = create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) + create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id, contact_id: contact.id) # try to create a message with unsupported file type - story_mention_params[:entry][0][:messaging][0]['message']['attachments'][0]['type'] = 'unsupported_type' - messaging = story_mention_params[:entry][0][:messaging][0] + messaging['message']['attachments'][0]['type'] = 'unsupported_type' described_class.new(messaging, instagram_inbox, outgoing_echo: false).perform @@ -177,7 +181,11 @@ describe Messages::Instagram::MessageBuilder do end it 'does not create message if the message is already exists' do - message + messaging = dm_params[:entry][0]['messaging'][0] + contact = create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) + conversation = create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id, contact_id: contact.id) + create(:message, account_id: account.id, inbox_id: instagram_inbox.id, conversation_id: conversation.id, message_type: 'outgoing', + source_id: 'message-id-1') expect(instagram_inbox.conversations.count).to be 1 expect(instagram_inbox.messages.count).to be 1 @@ -194,7 +202,7 @@ describe Messages::Instagram::MessageBuilder do instagram_channel.update(access_token: 'invalid_token') # Stub the request to return authorization error status - stub_request(:get, %r{https://graph\.instagram\.com/.*?/Sender-id-1\?.*}) + stub_request(:get, %r{https://graph\.instagram\.com/.*?/Sender-id-.*?\?.*}) .to_return( status: 401, body: { error: { message: 'unauthorized access token', code: 190 } }.to_json, @@ -218,6 +226,7 @@ describe Messages::Instagram::MessageBuilder do it 'creates a new conversation if existing conversation is not present' do initial_count = Conversation.count messaging = dm_params[:entry][0]['messaging'][0] + create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) described_class.new(messaging, instagram_inbox).perform @@ -226,21 +235,23 @@ describe Messages::Instagram::MessageBuilder do end it 'will not create a new conversation if last conversation is not resolved' do + messaging = dm_params[:entry][0]['messaging'][0] + contact = create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) existing_conversation = create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id, contact_id: contact.id, status: :open) - messaging = dm_params[:entry][0]['messaging'][0] described_class.new(messaging, instagram_inbox).perform expect(instagram_inbox.conversations.last.id).to eq(existing_conversation.id) end it 'creates a new conversation if last conversation is resolved' do + messaging = dm_params[:entry][0]['messaging'][0] + contact = create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) existing_conversation = create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id, contact_id: contact.id, status: :resolved) initial_count = Conversation.count - messaging = dm_params[:entry][0]['messaging'][0] described_class.new(messaging, instagram_inbox).perform @@ -257,6 +268,7 @@ describe Messages::Instagram::MessageBuilder do it 'creates a new conversation if existing conversation is not present' do initial_count = Conversation.count messaging = dm_params[:entry][0]['messaging'][0] + create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) described_class.new(messaging, instagram_inbox).perform @@ -265,6 +277,8 @@ describe Messages::Instagram::MessageBuilder do end it 'reopens last conversation if last conversation is resolved' do + messaging = dm_params[:entry][0]['messaging'][0] + contact = create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) existing_conversation = create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id, contact_id: contact.id, status: :resolved) @@ -307,6 +321,7 @@ describe Messages::Instagram::MessageBuilder do it 'saves story information when story mention is processed' do messaging = story_mention_params[:entry][0][:messaging][0] + create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) described_class.new(messaging, instagram_inbox).perform message = instagram_inbox.messages.first @@ -328,6 +343,7 @@ describe Messages::Instagram::MessageBuilder do ) messaging = story_mention_params[:entry][0][:messaging][0] + create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox) described_class.new(messaging, instagram_inbox).perform message = instagram_inbox.messages.first diff --git a/spec/builders/messages/instagram/messenger/message_builder_spec.rb b/spec/builders/messages/instagram/messenger/message_builder_spec.rb index 03194673a..593dfb946 100644 --- a/spec/builders/messages/instagram/messenger/message_builder_spec.rb +++ b/spec/builders/messages/instagram/messenger/message_builder_spec.rb @@ -17,30 +17,23 @@ describe Messages::Instagram::Messenger::MessageBuilder do let!(:instagram_story_reply_event) { build(:instagram_story_reply_event).with_indifferent_access } let!(:instagram_message_reply_event) { build(:instagram_message_reply_event).with_indifferent_access } let(:fb_object) { double } - let(:contact) { create(:contact, id: 'Sender-id-1', name: 'Jane Dae') } - let(:contact_inbox) { create(:contact_inbox, contact_id: contact.id, inbox_id: instagram_messenger_inbox.id, source_id: 'Sender-id-1') } - let(:conversation) do - create(:conversation, account_id: account.id, inbox_id: instagram_messenger_inbox.id, contact_id: contact.id, - additional_attributes: { type: 'instagram_direct_message', conversation_language: 'en' }) - end - let(:message) do - create(:message, account_id: account.id, inbox_id: instagram_messenger_inbox.id, conversation_id: conversation.id, message_type: 'outgoing', - source_id: 'message-id-1') - end describe '#perform' do it 'creates contact and message for the facebook inbox' do + messaging = dm_params[:entry][0]['messaging'][0] + sender_id = messaging['sender']['id'] + allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) allow(fb_object).to receive(:get_object).and_return( { name: 'Jane', - id: 'Sender-id-1', + id: sender_id, account_id: instagram_messenger_inbox.account_id, profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access ) - messaging = dm_params[:entry][0]['messaging'][0] - contact_inbox + + create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) described_class.new(messaging, instagram_messenger_inbox).perform instagram_messenger_inbox.reload @@ -56,7 +49,13 @@ describe Messages::Instagram::Messenger::MessageBuilder do end it 'discard echo message already sent by chatwoot' do - message + messaging = dm_params[:entry][0]['messaging'][0] + sender_id = messaging['sender']['id'] + contact = create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) + conversation = create(:conversation, account_id: account.id, inbox_id: instagram_messenger_inbox.id, contact_id: contact.id, + additional_attributes: { type: 'instagram_direct_message', conversation_language: 'en' }) + create(:message, account_id: account.id, inbox_id: instagram_messenger_inbox.id, conversation_id: conversation.id, message_type: 'outgoing', + source_id: 'message-id-1') expect(instagram_messenger_inbox.conversations.count).to be 1 expect(instagram_messenger_inbox.messages.count).to be 1 @@ -65,13 +64,11 @@ describe Messages::Instagram::Messenger::MessageBuilder do allow(fb_object).to receive(:get_object).and_return( { name: 'Jane', - id: 'Sender-id-1', + id: sender_id, account_id: instagram_messenger_inbox.account_id, profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access ) - messaging = dm_params[:entry][0]['messaging'][0] - contact_inbox described_class.new(messaging, instagram_messenger_inbox, outgoing_echo: true).perform instagram_messenger_inbox.reload @@ -81,17 +78,20 @@ describe Messages::Instagram::Messenger::MessageBuilder do end it 'creates message for shared reel' do + messaging = shared_reel_params[:entry][0]['messaging'][0] + sender_id = messaging['sender']['id'] + allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) allow(fb_object).to receive(:get_object).and_return( { name: 'Jane', - id: 'Sender-id-1', + id: sender_id, account_id: instagram_messenger_inbox.account_id, profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access ) - messaging = shared_reel_params[:entry][0]['messaging'][0] - contact_inbox + + create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) described_class.new(messaging, instagram_messenger_inbox).perform message = instagram_messenger_channel.inbox.messages.first @@ -102,18 +102,20 @@ describe Messages::Instagram::Messenger::MessageBuilder do end it 'creates message with for reply with story id' do + messaging = instagram_story_reply_event[:entry][0]['messaging'][0] + sender_id = messaging['sender']['id'] + allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) allow(fb_object).to receive(:get_object).and_return( { name: 'Jane', - id: 'Sender-id-1', + id: sender_id, account_id: instagram_messenger_inbox.account_id, profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access ) - messaging = instagram_story_reply_event[:entry][0]['messaging'][0] - contact_inbox + create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) described_class.new(messaging, instagram_messenger_inbox).perform message = instagram_messenger_channel.inbox.messages.first @@ -125,24 +127,26 @@ describe Messages::Instagram::Messenger::MessageBuilder do end it 'creates message with for reply with mid' do + # create first message to ensure reply to is valid + first_message_data = dm_params[:entry][0]['messaging'][0] + sender_id = first_message_data['sender']['id'] + allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) allow(fb_object).to receive(:get_object).and_return( { name: 'Jane', - id: 'Sender-id-1', + id: sender_id, account_id: instagram_messenger_inbox.account_id, profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access ) - # create first message to ensure reply to is valid - first_message = dm_params[:entry][0]['messaging'][0] - contact_inbox - described_class.new(first_message, instagram_messenger_inbox).perform - # create the second message with the reply to mid set + create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) + described_class.new(first_message_data, instagram_messenger_inbox).perform + + # create the second message with the reply to mid set, ensure same sender_id messaging = instagram_message_reply_event[:entry][0]['messaging'][0] - contact_inbox - + messaging['sender']['id'] = sender_id # Use the same sender_id described_class.new(messaging, instagram_messenger_inbox).perform first_message = instagram_messenger_channel.inbox.messages.first message = instagram_messenger_channel.inbox.messages.last @@ -153,14 +157,16 @@ describe Messages::Instagram::Messenger::MessageBuilder do end it 'raises exception on deleted story' do + messaging = story_mention_params[:entry][0][:messaging][0] + sender_id = messaging['sender']['id'] + allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) allow(fb_object).to receive(:get_object).and_raise(Koala::Facebook::ClientError.new( 190, 'This Message has been deleted by the user or the business.' )) - messaging = story_mention_params[:entry][0][:messaging][0] - contact_inbox + create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) described_class.new(messaging, instagram_messenger_inbox, outgoing_echo: false).perform instagram_messenger_inbox.reload @@ -180,22 +186,24 @@ describe Messages::Instagram::Messenger::MessageBuilder do end it 'does not create message for unsupported file type' do + # create a message with unsupported file type + story_mention_params[:entry][0][:messaging][0]['message']['attachments'][0]['type'] = 'unsupported_type' + messaging = story_mention_params[:entry][0][:messaging][0] + sender_id = messaging['sender']['id'] + allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) allow(fb_object).to receive(:get_object).and_return( { name: 'Jane', - id: 'Sender-id-1', + id: sender_id, account_id: instagram_messenger_inbox.account_id, profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access ) - conversation - - # create a message with unsupported file type - story_mention_params[:entry][0][:messaging][0]['message']['attachments'][0]['type'] = 'unsupported_type' - messaging = story_mention_params[:entry][0][:messaging][0] - + contact = create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) + create(:conversation, account_id: account.id, inbox_id: instagram_messenger_inbox.id, contact_id: contact.id, + additional_attributes: { type: 'instagram_direct_message', conversation_language: 'en' }) described_class.new(messaging, instagram_messenger_inbox, outgoing_echo: false).perform instagram_messenger_inbox.reload @@ -218,18 +226,22 @@ describe Messages::Instagram::Messenger::MessageBuilder do it 'creates a new conversation if existing conversation is not present' do inital_count = Conversation.count message = dm_params[:entry][0]['messaging'][0] - contact_inbox + sender_id = message['sender']['id'] + create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) described_class.new(message, instagram_messenger_inbox).perform instagram_messenger_inbox.reload - contact_inbox.reload expect(instagram_messenger_inbox.conversations.count).to eq(1) expect(Conversation.count).to eq(inital_count + 1) end it 'will not create a new conversation if last conversation is not resolved' do + message = dm_params[:entry][0]['messaging'][0] + sender_id = message['sender']['id'] + contact = create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) + existing_conversation = create( :conversation, account_id: account.id, @@ -239,18 +251,18 @@ describe Messages::Instagram::Messenger::MessageBuilder do additional_attributes: { type: 'instagram_direct_message', conversation_language: 'en' } ) - message = dm_params[:entry][0]['messaging'][0] - contact_inbox - described_class.new(message, instagram_messenger_inbox).perform instagram_messenger_inbox.reload - contact_inbox.reload expect(instagram_messenger_inbox.conversations.last.id).to eq(existing_conversation.id) end it 'creates a new conversation if last conversation is resolved' do + message = dm_params[:entry][0]['messaging'][0] + sender_id = message['sender']['id'] + contact = create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) + existing_conversation = create( :conversation, account_id: account.id, @@ -261,13 +273,9 @@ describe Messages::Instagram::Messenger::MessageBuilder do ) inital_count = Conversation.count - message = dm_params[:entry][0]['messaging'][0] - contact_inbox - described_class.new(message, instagram_messenger_inbox).perform instagram_messenger_inbox.reload - contact_inbox.reload expect(instagram_messenger_inbox.conversations.last.id).not_to eq(existing_conversation.id) expect(Conversation.count).to eq(inital_count + 1) @@ -283,18 +291,22 @@ describe Messages::Instagram::Messenger::MessageBuilder do it 'creates a new conversation if existing conversation is not present' do inital_count = Conversation.count message = dm_params[:entry][0]['messaging'][0] - contact_inbox + sender_id = message['sender']['id'] + create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) described_class.new(message, instagram_messenger_inbox).perform instagram_messenger_inbox.reload - contact_inbox.reload expect(instagram_messenger_inbox.conversations.count).to eq(1) expect(Conversation.count).to eq(inital_count + 1) end it 'reopens last conversation if last conversation is resolved' do + message = dm_params[:entry][0]['messaging'][0] + sender_id = message['sender']['id'] + contact = create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) + existing_conversation = create( :conversation, account_id: account.id, @@ -306,13 +318,9 @@ describe Messages::Instagram::Messenger::MessageBuilder do inital_count = Conversation.count - message = dm_params[:entry][0]['messaging'][0] - contact_inbox - described_class.new(message, instagram_messenger_inbox).perform instagram_messenger_inbox.reload - contact_inbox.reload expect(instagram_messenger_inbox.conversations.last.id).to eq(existing_conversation.id) expect(Conversation.count).to eq(inital_count) @@ -344,7 +352,9 @@ describe Messages::Instagram::Messenger::MessageBuilder do allow(fb_object).to receive(:get_object).and_return(story_data) messaging = story_mention_params[:entry][0][:messaging][0] - contact_inbox + sender_id = messaging['sender']['id'] + + create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) builder = described_class.new(messaging, instagram_messenger_inbox) builder.perform @@ -358,18 +368,20 @@ describe Messages::Instagram::Messenger::MessageBuilder do end it 'handles story mentions specifically in the Instagram builder' do + messaging = story_mention_params[:entry][0][:messaging][0] + sender_id = messaging['sender']['id'] + # First allow contact info fetch allow(fb_object).to receive(:get_object).and_return({ name: 'Jane', - id: 'Sender-id-1' + id: sender_id }.with_indifferent_access) # Then allow story data fetch allow(fb_object).to receive(:get_object).with(anything, fields: %w[story from]) .and_return(story_data) - messaging = story_mention_params[:entry][0][:messaging][0] - contact_inbox + create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox) described_class.new(messaging, instagram_messenger_inbox).perform message = instagram_messenger_inbox.messages.first diff --git a/spec/factories/instagram/instagram_message_create_event.rb b/spec/factories/instagram/instagram_message_create_event.rb index 66d5e8a81..a5de8e7d3 100644 --- a/spec/factories/instagram/instagram_message_create_event.rb +++ b/spec/factories/instagram/instagram_message_create_event.rb @@ -1,14 +1,18 @@ FactoryBot.define do factory :instagram_message_create_event, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + sender_id { "Sender-id-#{SecureRandom.hex(4)}" } + end entry do [ { - 'id': 'instagram-message-id-123', + 'id': ig_entry_id, 'time': '2021-09-08T06:34:04+0000', 'messaging': [ { 'sender': { - 'id': 'Sender-id-1' + 'id': sender_id }, 'recipient': { 'id': 'chatwoot-app-user-id-1' @@ -27,15 +31,19 @@ FactoryBot.define do end factory :instagram_message_standby_event, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + sender_id { "Sender-id-#{SecureRandom.hex(4)}" } + end entry do [ { 'time': '2021-09-08T06:34:04+0000', - 'id': 'instagram-message-id-123', + 'id': ig_entry_id, 'standby': [ { 'sender': { - 'id': 'Sender-id-1' + 'id': sender_id }, 'recipient': { 'id': 'chatwoot-app-user-id-1' @@ -54,15 +62,19 @@ FactoryBot.define do end factory :instagram_story_reply_event, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + sender_id { "Sender-id-#{SecureRandom.hex(4)}" } + end entry do [ { - 'id': 'instagram-message-id-123', + 'id': ig_entry_id, 'time': '2021-09-08T06:34:04+0000', 'messaging': [ { 'sender': { - 'id': 'Sender-id-1' + 'id': sender_id }, 'recipient': { 'id': 'chatwoot-app-user-id-1' @@ -87,15 +99,19 @@ FactoryBot.define do end factory :instagram_message_reply_event, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + sender_id { "Sender-id-#{SecureRandom.hex(4)}" } + end entry do [ { - 'id': 'instagram-message-id-123', + 'id': ig_entry_id, 'time': '2021-09-08T06:35:04+0000', 'messaging': [ { 'sender': { - 'id': 'Sender-id-1' + 'id': sender_id }, 'recipient': { 'id': 'chatwoot-app-user-id-1' @@ -117,11 +133,14 @@ FactoryBot.define do end factory :instagram_test_text_event, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + end entry do [ { 'time' => 1_661_141_837_537, - 'id' => '0', + 'id' => ig_entry_id, 'messaging' => [ { 'sender' => { @@ -144,15 +163,19 @@ FactoryBot.define do end factory :instagram_message_unsend_event, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + sender_id { "Sender-id-#{SecureRandom.hex(4)}" } + end entry do [ { - 'id': 'instagram-message-id-123', + 'id': ig_entry_id, 'time': '2021-09-08T06:34:04+0000', 'messaging': [ { 'sender': { - 'id': 'Sender-id-1' + 'id': sender_id }, 'recipient': { 'id': 'chatwoot-app-user-id-1' @@ -171,15 +194,19 @@ FactoryBot.define do end factory :instagram_message_attachment_event, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + sender_id { "Sender-id-#{SecureRandom.hex(4)}" } + end entry do [ { - 'id': 'instagram-message-id-1234', + 'id': ig_entry_id, 'time': '2021-09-08T06:34:04+0000', 'messaging': [ { 'sender': { - 'id': 'Sender-id-1' + 'id': sender_id }, 'recipient': { 'id': 'chatwoot-app-user-id-1' @@ -205,15 +232,19 @@ FactoryBot.define do end factory :instagram_shared_reel_event, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + sender_id { "Sender-id-#{SecureRandom.hex(4)}" } + end entry do [ { - 'id': 'instagram-message-id-1234', + 'id': ig_entry_id, 'time': '2021-09-08T06:34:04+0000', 'messaging': [ { 'sender': { - 'id': 'Sender-id-1' + 'id': sender_id }, 'recipient': { 'id': 'chatwoot-app-user-id-1' @@ -241,15 +272,19 @@ FactoryBot.define do end factory :instagram_story_mention_event, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + sender_id { "Sender-id-#{SecureRandom.hex(4)}" } + end entry do [ { - 'id': 'instagram-message-id-1234', + 'id': ig_entry_id, 'time': '2021-09-08T06:34:04+0000', 'messaging': [ { 'sender': { - 'id': 'Sender-id-1' + 'id': sender_id }, 'recipient': { 'id': 'chatwoot-app-user-id-1' @@ -275,15 +310,19 @@ FactoryBot.define do end factory :instagram_story_mention_event_with_echo, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + sender_id { "Sender-id-#{SecureRandom.hex(4)}" } + end entry do [ { - 'id': 'instagram-message-id-1234', + 'id': ig_entry_id, 'time': '2021-09-08T06:34:04+0000', 'messaging': [ { 'sender': { - 'id': 'Sender-id-1' + 'id': sender_id }, 'recipient': { 'id': 'chatwoot-app-user-id-1' @@ -310,15 +349,19 @@ FactoryBot.define do end factory :instagram_message_unsupported_event, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + sender_id { "Sender-id-#{SecureRandom.hex(4)}" } + end entry do [ { - 'id': 'instagram-message-unsupported-id-123', + 'id': ig_entry_id, 'time': '2021-09-08T06:34:04+0000', 'messaging': [ { 'sender': { - 'id': 'Sender-id-1' + 'id': sender_id }, 'recipient': { 'id': 'chatwoot-app-user-id-1' @@ -337,15 +380,19 @@ FactoryBot.define do end factory :messaging_seen_event, class: Hash do + transient do + ig_entry_id { SecureRandom.uuid } + sender_id { "Sender-id-#{SecureRandom.hex(4)}" } + end entry do [ { - 'id': 'instagram-message-id-123', + 'id': ig_entry_id, 'time': '2021-09-08T06:34:04+0000', 'messaging': [ { 'sender': { - 'id': 'Sender-id-1' + 'id': sender_id }, 'recipient': { 'id': 'chatwoot-app-user-id-1' diff --git a/spec/jobs/webhooks/instagram_events_job_spec.rb b/spec/jobs/webhooks/instagram_events_job_spec.rb index 1962c78a5..9edd9a34d 100644 --- a/spec/jobs/webhooks/instagram_events_job_spec.rb +++ b/spec/jobs/webhooks/instagram_events_job_spec.rb @@ -10,19 +10,10 @@ describe Webhooks::InstagramEventsJob do end let!(:account) { create(:account) } - let(:return_object) do - { name: 'Jane', - id: 'Sender-id-1', - account_id: instagram_messenger_inbox.account_id, - profile_pic: 'https://chatwoot-assets.local/sample.png', - username: 'some_user_name' } - end let!(:instagram_messenger_channel) { create(:channel_instagram_fb_page, account: account, instagram_id: 'chatwoot-app-user-id-1') } let!(:instagram_messenger_inbox) { create(:inbox, channel: instagram_messenger_channel, account: account, greeting_enabled: false) } - let!(:instagram_channel) { create(:channel_instagram, account: account, instagram_id: 'chatwoot-app-user-id-1') } let!(:instagram_inbox) { create(:inbox, channel: instagram_channel, account: account, greeting_enabled: false) } - # Combined message events into one helper let(:message_events) do { @@ -37,6 +28,14 @@ describe Webhooks::InstagramEventsJob do } end + def return_object_for(sender_id) + { name: 'Jane', + id: sender_id, + account_id: instagram_messenger_inbox.account_id, + profile_pic: 'https://chatwoot-assets.local/sample.png', + username: 'some_user_name' } + end + describe '#perform' do context 'when handling messaging events for Instagram via Facebook page' do let(:fb_object) { double } @@ -47,8 +46,9 @@ describe Webhooks::InstagramEventsJob do it 'creates incoming message in the instagram inbox' do allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) + sender_id = message_events[:dm][:entry][0][:messaging][0][:sender][:id] allow(fb_object).to receive(:get_object).and_return( - return_object.with_indifferent_access + return_object_for(sender_id).with_indifferent_access ) instagram_webhook.perform_now(message_events[:dm][:entry]) @@ -63,8 +63,9 @@ describe Webhooks::InstagramEventsJob do it 'creates standby message in the instagram inbox' do allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) + sender_id = message_events[:standby][:entry][0][:standby][0][:sender][:id] allow(fb_object).to receive(:get_object).and_return( - return_object.with_indifferent_access + return_object_for(sender_id).with_indifferent_access ) instagram_webhook.perform_now(message_events[:standby][:entry]) @@ -82,10 +83,11 @@ describe Webhooks::InstagramEventsJob do it 'handle instagram unsend message event' do message = create(:message, inbox_id: instagram_messenger_inbox.id, source_id: 'message-id-to-delete') allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) + sender_id = message_events[:unsend][:entry][0][:messaging][0][:sender][:id] allow(fb_object).to receive(:get_object).and_return( { name: 'Jane', - id: 'Sender-id-1', + id: sender_id, account_id: instagram_messenger_inbox.account_id, profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access @@ -104,8 +106,9 @@ describe Webhooks::InstagramEventsJob do it 'creates incoming message with attachments in the instagram inbox' do allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) + sender_id = message_events[:attachment][:entry][0][:messaging][0][:sender][:id] allow(fb_object).to receive(:get_object).and_return( - return_object.with_indifferent_access + return_object_for(sender_id).with_indifferent_access ) instagram_webhook.perform_now(message_events[:attachment][:entry]) @@ -118,8 +121,9 @@ describe Webhooks::InstagramEventsJob do it 'creates incoming message with attachments in the instagram inbox for story mention' do allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) + sender_id = message_events[:story_mention][:entry][0][:messaging][0][:sender][:id] allow(fb_object).to receive(:get_object).and_return( - return_object.with_indifferent_access, + return_object_for(sender_id).with_indifferent_access, { story: { mention: { @@ -165,8 +169,9 @@ describe Webhooks::InstagramEventsJob do it 'handles unsupported message' do allow(Koala::Facebook::API).to receive(:new).and_return(fb_object) + sender_id = message_events[:unsupported][:entry][0][:messaging][0][:sender][:id] allow(fb_object).to receive(:get_object).and_return( - return_object.with_indifferent_access + return_object_for(sender_id).with_indifferent_access ) instagram_webhook.perform_now(message_events[:unsupported][:entry]) @@ -184,19 +189,22 @@ describe Webhooks::InstagramEventsJob do before do instagram_channel.update(access_token: 'valid_instagram_token') - stub_request(:get, %r{https://graph\.instagram\.com/v22\.0/Sender-id-1\?.*}) + stub_request(:get, %r{https://graph\.instagram\.com/v22\.0/Sender-id-.*\?.*}) .to_return( status: 200, - body: { - name: 'Jane', - username: 'some_user_name', - profile_pic: 'https://chatwoot-assets.local/sample.png', - id: 'Sender-id-1', - follower_count: 100, - is_user_follow_business: true, - is_business_follow_user: true, - is_verified_user: false - }.to_json, + body: proc { |request| + sender_id = request.uri.path.split('/').last.split('?').first + { + name: 'Jane', + username: 'some_user_name', + profile_pic: 'https://chatwoot-assets.local/sample.png', + id: sender_id, + follower_count: 100, + is_user_follow_business: true, + is_business_follow_user: true, + is_verified_user: false + }.to_json + }, headers: { 'Content-Type' => 'application/json' } ) end @@ -289,14 +297,15 @@ describe Webhooks::InstagramEventsJob do stub_request(:get, %r{https://graph\.instagram\.com/v22\.0/.*\?.*}) .to_return(status: 401, body: { error: { message: 'No matching Instagram user', code: 9010 } }.to_json) + sender_id = message_events[:dm][:entry][0][:messaging][0][:sender][:id] instagram_webhook.perform_now(message_events[:dm][:entry]) instagram_inbox.reload expect(instagram_inbox.contacts.count).to be 1 - expect(instagram_inbox.contacts.last.name).to eq 'Unknown (IG: Sender-id-1)' + expect(instagram_inbox.contacts.last.name).to eq "Unknown (IG: #{sender_id})" expect(instagram_inbox.contacts.last.contact_inboxes.count).to be 1 - expect(instagram_inbox.contacts.last.contact_inboxes.first.source_id).to eq 'Sender-id-1' + expect(instagram_inbox.contacts.last.contact_inboxes.first.source_id).to eq sender_id expect(instagram_inbox.conversations.count).to eq 1 expect(instagram_inbox.messages.count).to eq 1 diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 616d62d28..bb8ea89e1 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -70,6 +70,7 @@ RSpec.configure do |config| config.include SlackStubs config.include FileUploadHelpers config.include CsvSpecHelpers + config.include InstagramSpecHelpers config.include Devise::Test::IntegrationHelpers, type: :request config.include ActiveSupport::Testing::TimeHelpers config.include ActionCable::TestHelper diff --git a/spec/support/instagram_spec_helpers.rb b/spec/support/instagram_spec_helpers.rb new file mode 100644 index 000000000..651bff585 --- /dev/null +++ b/spec/support/instagram_spec_helpers.rb @@ -0,0 +1,20 @@ +module InstagramSpecHelpers + def create_instagram_contact_for_sender(sender_id, inbox) + contact = Contact.find_by(identifier: sender_id) + if contact.nil? + contact = create(:contact, identifier: sender_id, name: 'Jane Dae') + create(:contact_inbox, contact_id: contact.id, inbox_id: inbox.id, source_id: sender_id) + end + contact + end + + def instagram_user_response_object_for(sender_id, account_id) + { + name: 'Jane', + id: sender_id, + account_id: account_id, + profile_pic: 'https://chatwoot-assets.local/sample.png', + username: 'some_user_name' + } + end +end