mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-30 10:42:38 +00:00
fix: resolve mutex conflicts in Instagram webhook specs (#12154)
This PR fixes flaky test failures in the Instagram webhook specs that were caused by Redis mutex lock conflicts when tests ran in parallel. ### The Problem: The InstagramEventsJob uses a Redis mutex with a key based on sender_id and ig_account_id to prevent race conditions. However, all test factories were using the same hardcoded sender_id: 'Sender-id-1', causing multiple test instances to compete for the same mutex lock when running in parallel. ### The Solution: - Updated all Instagram event factories to generate unique sender IDs using SecureRandom.hex(4) - Modified test stubs and expectations to work with dynamic sender IDs instead of hardcoded values - Ensured each test instance gets its own unique mutex key, eliminating lock contention
This commit is contained in:
@@ -17,40 +17,34 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
let!(:shared_reel_params) { build(:instagram_shared_reel_event).with_indifferent_access }
|
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_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!(: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
|
describe '#perform' do
|
||||||
before do
|
before do
|
||||||
instagram_channel.update(access_token: 'valid_instagram_token')
|
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(
|
.to_return(
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: proc { |request|
|
||||||
name: 'Jane',
|
sender_id = request.uri.path.split('/').last.split('?').first
|
||||||
username: 'some_user_name',
|
{
|
||||||
profile_pic: 'https://chatwoot-assets.local/sample.png',
|
name: 'Jane',
|
||||||
id: 'Sender-id-1',
|
username: 'some_user_name',
|
||||||
follower_count: 100,
|
profile_pic: 'https://chatwoot-assets.local/sample.png',
|
||||||
is_user_follow_business: true,
|
id: sender_id,
|
||||||
is_business_follow_user: true,
|
follower_count: 100,
|
||||||
is_verified_user: false
|
is_user_follow_business: true,
|
||||||
}.to_json,
|
is_business_follow_user: true,
|
||||||
|
is_verified_user: false
|
||||||
|
}.to_json
|
||||||
|
},
|
||||||
headers: { 'Content-Type' => 'application/json' }
|
headers: { 'Content-Type' => 'application/json' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates contact and message for the instagram direct inbox' do
|
it 'creates contact and message for the instagram direct inbox' do
|
||||||
messaging = dm_params[:entry][0]['messaging'][0]
|
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
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
instagram_inbox.reload
|
instagram_inbox.reload
|
||||||
@@ -63,13 +57,15 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'discard echo message already sent by chatwoot' do
|
it 'discard echo message already sent by chatwoot' do
|
||||||
conversation
|
messaging = dm_params[:entry][0]['messaging'][0]
|
||||||
message
|
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.conversations.count).to be 1
|
||||||
expect(instagram_inbox.messages.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
|
messaging[:message][:mid] = 'message-id-1' # Set same source_id as the existing message
|
||||||
described_class.new(messaging, instagram_inbox, outgoing_echo: true).perform
|
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
|
it 'discards duplicate messages from webhook events with the same message_id' do
|
||||||
messaging = dm_params[:entry][0]['messaging'][0]
|
messaging = dm_params[:entry][0]['messaging'][0]
|
||||||
|
create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox)
|
||||||
described_class.new(messaging, instagram_inbox).perform
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
initial_message_count = instagram_inbox.messages.count
|
initial_message_count = instagram_inbox.messages.count
|
||||||
@@ -93,6 +90,7 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
|
|
||||||
it 'creates message for shared reel' do
|
it 'creates message for shared reel' do
|
||||||
messaging = shared_reel_params[:entry][0]['messaging'][0]
|
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
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
message = instagram_inbox.messages.first
|
message = instagram_inbox.messages.first
|
||||||
@@ -103,7 +101,9 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'creates message with story id' do
|
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}\?.*})
|
stub_request(:get, %r{https://graph\.instagram\.com/.*?/#{story_source_id}\?.*})
|
||||||
.to_return(
|
.to_return(
|
||||||
@@ -121,7 +121,6 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
headers: { 'Content-Type' => 'application/json' }
|
headers: { 'Content-Type' => 'application/json' }
|
||||||
)
|
)
|
||||||
|
|
||||||
messaging = instagram_story_reply_event[:entry][0]['messaging'][0]
|
|
||||||
described_class.new(messaging, instagram_inbox).perform
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
message = instagram_inbox.messages.first
|
message = instagram_inbox.messages.first
|
||||||
@@ -134,10 +133,13 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
it 'creates message with reply to mid' do
|
it 'creates message with reply to mid' do
|
||||||
# Create first message to ensure reply to is valid
|
# Create first message to ensure reply to is valid
|
||||||
first_messaging = dm_params[:entry][0]['messaging'][0]
|
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
|
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 = instagram_message_reply_event[:entry][0]['messaging'][0]
|
||||||
|
messaging['sender']['id'] = sender_id
|
||||||
described_class.new(messaging, instagram_inbox).perform
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
first_message = instagram_inbox.messages.first
|
first_message = instagram_inbox.messages.first
|
||||||
@@ -148,12 +150,13 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'handles deleted story' do
|
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}\?.*})
|
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)
|
.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
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
message = instagram_inbox.messages.first
|
message = instagram_inbox.messages.first
|
||||||
@@ -163,11 +166,12 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'does not create message for unsupported file type' do
|
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
|
# try to create a message with unsupported file type
|
||||||
story_mention_params[:entry][0][:messaging][0]['message']['attachments'][0]['type'] = 'unsupported_type'
|
messaging['message']['attachments'][0]['type'] = 'unsupported_type'
|
||||||
messaging = story_mention_params[:entry][0][:messaging][0]
|
|
||||||
|
|
||||||
described_class.new(messaging, instagram_inbox, outgoing_echo: false).perform
|
described_class.new(messaging, instagram_inbox, outgoing_echo: false).perform
|
||||||
|
|
||||||
@@ -177,7 +181,11 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'does not create message if the message is already exists' do
|
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.conversations.count).to be 1
|
||||||
expect(instagram_inbox.messages.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')
|
instagram_channel.update(access_token: 'invalid_token')
|
||||||
|
|
||||||
# Stub the request to return authorization error status
|
# 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(
|
.to_return(
|
||||||
status: 401,
|
status: 401,
|
||||||
body: { error: { message: 'unauthorized access token', code: 190 } }.to_json,
|
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
|
it 'creates a new conversation if existing conversation is not present' do
|
||||||
initial_count = Conversation.count
|
initial_count = Conversation.count
|
||||||
messaging = dm_params[:entry][0]['messaging'][0]
|
messaging = dm_params[:entry][0]['messaging'][0]
|
||||||
|
create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox)
|
||||||
|
|
||||||
described_class.new(messaging, instagram_inbox).perform
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
@@ -226,21 +235,23 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'will not create a new conversation if last conversation is not resolved' do
|
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,
|
existing_conversation = create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id,
|
||||||
contact_id: contact.id, status: :open)
|
contact_id: contact.id, status: :open)
|
||||||
|
|
||||||
messaging = dm_params[:entry][0]['messaging'][0]
|
|
||||||
described_class.new(messaging, instagram_inbox).perform
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
expect(instagram_inbox.conversations.last.id).to eq(existing_conversation.id)
|
expect(instagram_inbox.conversations.last.id).to eq(existing_conversation.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a new conversation if last conversation is resolved' do
|
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,
|
existing_conversation = create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id,
|
||||||
contact_id: contact.id, status: :resolved)
|
contact_id: contact.id, status: :resolved)
|
||||||
|
|
||||||
initial_count = Conversation.count
|
initial_count = Conversation.count
|
||||||
messaging = dm_params[:entry][0]['messaging'][0]
|
|
||||||
|
|
||||||
described_class.new(messaging, instagram_inbox).perform
|
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
|
it 'creates a new conversation if existing conversation is not present' do
|
||||||
initial_count = Conversation.count
|
initial_count = Conversation.count
|
||||||
messaging = dm_params[:entry][0]['messaging'][0]
|
messaging = dm_params[:entry][0]['messaging'][0]
|
||||||
|
create_instagram_contact_for_sender(messaging['sender']['id'], instagram_inbox)
|
||||||
|
|
||||||
described_class.new(messaging, instagram_inbox).perform
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
@@ -265,6 +277,8 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'reopens last conversation if last conversation is resolved' do
|
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,
|
existing_conversation = create(:conversation, account_id: account.id, inbox_id: instagram_inbox.id,
|
||||||
contact_id: contact.id, status: :resolved)
|
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
|
it 'saves story information when story mention is processed' do
|
||||||
messaging = story_mention_params[:entry][0][:messaging][0]
|
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
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
message = instagram_inbox.messages.first
|
message = instagram_inbox.messages.first
|
||||||
@@ -328,6 +343,7 @@ describe Messages::Instagram::MessageBuilder do
|
|||||||
)
|
)
|
||||||
|
|
||||||
messaging = story_mention_params[:entry][0][:messaging][0]
|
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
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
message = instagram_inbox.messages.first
|
message = instagram_inbox.messages.first
|
||||||
|
|||||||
@@ -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_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!(:instagram_message_reply_event) { build(:instagram_message_reply_event).with_indifferent_access }
|
||||||
let(:fb_object) { double }
|
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
|
describe '#perform' do
|
||||||
it 'creates contact and message for the facebook inbox' 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(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||||
allow(fb_object).to receive(:get_object).and_return(
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
{
|
{
|
||||||
name: 'Jane',
|
name: 'Jane',
|
||||||
id: 'Sender-id-1',
|
id: sender_id,
|
||||||
account_id: instagram_messenger_inbox.account_id,
|
account_id: instagram_messenger_inbox.account_id,
|
||||||
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
||||||
}.with_indifferent_access
|
}.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
|
described_class.new(messaging, instagram_messenger_inbox).perform
|
||||||
|
|
||||||
instagram_messenger_inbox.reload
|
instagram_messenger_inbox.reload
|
||||||
@@ -56,7 +49,13 @@ describe Messages::Instagram::Messenger::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'discard echo message already sent by chatwoot' do
|
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.conversations.count).to be 1
|
||||||
expect(instagram_messenger_inbox.messages.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(
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
{
|
{
|
||||||
name: 'Jane',
|
name: 'Jane',
|
||||||
id: 'Sender-id-1',
|
id: sender_id,
|
||||||
account_id: instagram_messenger_inbox.account_id,
|
account_id: instagram_messenger_inbox.account_id,
|
||||||
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
||||||
}.with_indifferent_access
|
}.with_indifferent_access
|
||||||
)
|
)
|
||||||
messaging = dm_params[:entry][0]['messaging'][0]
|
|
||||||
contact_inbox
|
|
||||||
described_class.new(messaging, instagram_messenger_inbox, outgoing_echo: true).perform
|
described_class.new(messaging, instagram_messenger_inbox, outgoing_echo: true).perform
|
||||||
|
|
||||||
instagram_messenger_inbox.reload
|
instagram_messenger_inbox.reload
|
||||||
@@ -81,17 +78,20 @@ describe Messages::Instagram::Messenger::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'creates message for shared reel' do
|
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(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||||
allow(fb_object).to receive(:get_object).and_return(
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
{
|
{
|
||||||
name: 'Jane',
|
name: 'Jane',
|
||||||
id: 'Sender-id-1',
|
id: sender_id,
|
||||||
account_id: instagram_messenger_inbox.account_id,
|
account_id: instagram_messenger_inbox.account_id,
|
||||||
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
||||||
}.with_indifferent_access
|
}.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
|
described_class.new(messaging, instagram_messenger_inbox).perform
|
||||||
|
|
||||||
message = instagram_messenger_channel.inbox.messages.first
|
message = instagram_messenger_channel.inbox.messages.first
|
||||||
@@ -102,18 +102,20 @@ describe Messages::Instagram::Messenger::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'creates message with for reply with story id' do
|
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(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||||
allow(fb_object).to receive(:get_object).and_return(
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
{
|
{
|
||||||
name: 'Jane',
|
name: 'Jane',
|
||||||
id: 'Sender-id-1',
|
id: sender_id,
|
||||||
account_id: instagram_messenger_inbox.account_id,
|
account_id: instagram_messenger_inbox.account_id,
|
||||||
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
||||||
}.with_indifferent_access
|
}.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
|
described_class.new(messaging, instagram_messenger_inbox).perform
|
||||||
|
|
||||||
message = instagram_messenger_channel.inbox.messages.first
|
message = instagram_messenger_channel.inbox.messages.first
|
||||||
@@ -125,24 +127,26 @@ describe Messages::Instagram::Messenger::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'creates message with for reply with mid' do
|
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(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||||
allow(fb_object).to receive(:get_object).and_return(
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
{
|
{
|
||||||
name: 'Jane',
|
name: 'Jane',
|
||||||
id: 'Sender-id-1',
|
id: sender_id,
|
||||||
account_id: instagram_messenger_inbox.account_id,
|
account_id: instagram_messenger_inbox.account_id,
|
||||||
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
||||||
}.with_indifferent_access
|
}.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]
|
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
|
described_class.new(messaging, instagram_messenger_inbox).perform
|
||||||
first_message = instagram_messenger_channel.inbox.messages.first
|
first_message = instagram_messenger_channel.inbox.messages.first
|
||||||
message = instagram_messenger_channel.inbox.messages.last
|
message = instagram_messenger_channel.inbox.messages.last
|
||||||
@@ -153,14 +157,16 @@ describe Messages::Instagram::Messenger::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'raises exception on deleted story' do
|
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(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||||
allow(fb_object).to receive(:get_object).and_raise(Koala::Facebook::ClientError.new(
|
allow(fb_object).to receive(:get_object).and_raise(Koala::Facebook::ClientError.new(
|
||||||
190,
|
190,
|
||||||
'This Message has been deleted by the user or the business.'
|
'This Message has been deleted by the user or the business.'
|
||||||
))
|
))
|
||||||
|
|
||||||
messaging = story_mention_params[:entry][0][:messaging][0]
|
create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox)
|
||||||
contact_inbox
|
|
||||||
described_class.new(messaging, instagram_messenger_inbox, outgoing_echo: false).perform
|
described_class.new(messaging, instagram_messenger_inbox, outgoing_echo: false).perform
|
||||||
|
|
||||||
instagram_messenger_inbox.reload
|
instagram_messenger_inbox.reload
|
||||||
@@ -180,22 +186,24 @@ describe Messages::Instagram::Messenger::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'does not create message for unsupported file type' do
|
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(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||||
allow(fb_object).to receive(:get_object).and_return(
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
{
|
{
|
||||||
name: 'Jane',
|
name: 'Jane',
|
||||||
id: 'Sender-id-1',
|
id: sender_id,
|
||||||
account_id: instagram_messenger_inbox.account_id,
|
account_id: instagram_messenger_inbox.account_id,
|
||||||
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
||||||
}.with_indifferent_access
|
}.with_indifferent_access
|
||||||
)
|
)
|
||||||
|
|
||||||
conversation
|
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,
|
||||||
# create a message with unsupported file type
|
additional_attributes: { type: 'instagram_direct_message', conversation_language: 'en' })
|
||||||
story_mention_params[:entry][0][:messaging][0]['message']['attachments'][0]['type'] = 'unsupported_type'
|
|
||||||
messaging = story_mention_params[:entry][0][:messaging][0]
|
|
||||||
|
|
||||||
described_class.new(messaging, instagram_messenger_inbox, outgoing_echo: false).perform
|
described_class.new(messaging, instagram_messenger_inbox, outgoing_echo: false).perform
|
||||||
|
|
||||||
instagram_messenger_inbox.reload
|
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
|
it 'creates a new conversation if existing conversation is not present' do
|
||||||
inital_count = Conversation.count
|
inital_count = Conversation.count
|
||||||
message = dm_params[:entry][0]['messaging'][0]
|
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
|
described_class.new(message, instagram_messenger_inbox).perform
|
||||||
|
|
||||||
instagram_messenger_inbox.reload
|
instagram_messenger_inbox.reload
|
||||||
contact_inbox.reload
|
|
||||||
|
|
||||||
expect(instagram_messenger_inbox.conversations.count).to eq(1)
|
expect(instagram_messenger_inbox.conversations.count).to eq(1)
|
||||||
expect(Conversation.count).to eq(inital_count + 1)
|
expect(Conversation.count).to eq(inital_count + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'will not create a new conversation if last conversation is not resolved' do
|
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(
|
existing_conversation = create(
|
||||||
:conversation,
|
:conversation,
|
||||||
account_id: account.id,
|
account_id: account.id,
|
||||||
@@ -239,18 +251,18 @@ describe Messages::Instagram::Messenger::MessageBuilder do
|
|||||||
additional_attributes: { type: 'instagram_direct_message', conversation_language: 'en' }
|
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
|
described_class.new(message, instagram_messenger_inbox).perform
|
||||||
|
|
||||||
instagram_messenger_inbox.reload
|
instagram_messenger_inbox.reload
|
||||||
contact_inbox.reload
|
|
||||||
|
|
||||||
expect(instagram_messenger_inbox.conversations.last.id).to eq(existing_conversation.id)
|
expect(instagram_messenger_inbox.conversations.last.id).to eq(existing_conversation.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a new conversation if last conversation is resolved' do
|
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(
|
existing_conversation = create(
|
||||||
:conversation,
|
:conversation,
|
||||||
account_id: account.id,
|
account_id: account.id,
|
||||||
@@ -261,13 +273,9 @@ describe Messages::Instagram::Messenger::MessageBuilder do
|
|||||||
)
|
)
|
||||||
|
|
||||||
inital_count = Conversation.count
|
inital_count = Conversation.count
|
||||||
message = dm_params[:entry][0]['messaging'][0]
|
|
||||||
contact_inbox
|
|
||||||
|
|
||||||
described_class.new(message, instagram_messenger_inbox).perform
|
described_class.new(message, instagram_messenger_inbox).perform
|
||||||
|
|
||||||
instagram_messenger_inbox.reload
|
instagram_messenger_inbox.reload
|
||||||
contact_inbox.reload
|
|
||||||
|
|
||||||
expect(instagram_messenger_inbox.conversations.last.id).not_to eq(existing_conversation.id)
|
expect(instagram_messenger_inbox.conversations.last.id).not_to eq(existing_conversation.id)
|
||||||
expect(Conversation.count).to eq(inital_count + 1)
|
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
|
it 'creates a new conversation if existing conversation is not present' do
|
||||||
inital_count = Conversation.count
|
inital_count = Conversation.count
|
||||||
message = dm_params[:entry][0]['messaging'][0]
|
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
|
described_class.new(message, instagram_messenger_inbox).perform
|
||||||
|
|
||||||
instagram_messenger_inbox.reload
|
instagram_messenger_inbox.reload
|
||||||
contact_inbox.reload
|
|
||||||
|
|
||||||
expect(instagram_messenger_inbox.conversations.count).to eq(1)
|
expect(instagram_messenger_inbox.conversations.count).to eq(1)
|
||||||
expect(Conversation.count).to eq(inital_count + 1)
|
expect(Conversation.count).to eq(inital_count + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'reopens last conversation if last conversation is resolved' do
|
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(
|
existing_conversation = create(
|
||||||
:conversation,
|
:conversation,
|
||||||
account_id: account.id,
|
account_id: account.id,
|
||||||
@@ -306,13 +318,9 @@ describe Messages::Instagram::Messenger::MessageBuilder do
|
|||||||
|
|
||||||
inital_count = Conversation.count
|
inital_count = Conversation.count
|
||||||
|
|
||||||
message = dm_params[:entry][0]['messaging'][0]
|
|
||||||
contact_inbox
|
|
||||||
|
|
||||||
described_class.new(message, instagram_messenger_inbox).perform
|
described_class.new(message, instagram_messenger_inbox).perform
|
||||||
|
|
||||||
instagram_messenger_inbox.reload
|
instagram_messenger_inbox.reload
|
||||||
contact_inbox.reload
|
|
||||||
|
|
||||||
expect(instagram_messenger_inbox.conversations.last.id).to eq(existing_conversation.id)
|
expect(instagram_messenger_inbox.conversations.last.id).to eq(existing_conversation.id)
|
||||||
expect(Conversation.count).to eq(inital_count)
|
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)
|
allow(fb_object).to receive(:get_object).and_return(story_data)
|
||||||
|
|
||||||
messaging = story_mention_params[:entry][0][:messaging][0]
|
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 = described_class.new(messaging, instagram_messenger_inbox)
|
||||||
builder.perform
|
builder.perform
|
||||||
|
|
||||||
@@ -358,18 +368,20 @@ describe Messages::Instagram::Messenger::MessageBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'handles story mentions specifically in the Instagram builder' do
|
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
|
# First allow contact info fetch
|
||||||
allow(fb_object).to receive(:get_object).and_return({
|
allow(fb_object).to receive(:get_object).and_return({
|
||||||
name: 'Jane',
|
name: 'Jane',
|
||||||
id: 'Sender-id-1'
|
id: sender_id
|
||||||
}.with_indifferent_access)
|
}.with_indifferent_access)
|
||||||
|
|
||||||
# Then allow story data fetch
|
# Then allow story data fetch
|
||||||
allow(fb_object).to receive(:get_object).with(anything, fields: %w[story from])
|
allow(fb_object).to receive(:get_object).with(anything, fields: %w[story from])
|
||||||
.and_return(story_data)
|
.and_return(story_data)
|
||||||
|
|
||||||
messaging = story_mention_params[:entry][0][:messaging][0]
|
create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox)
|
||||||
contact_inbox
|
|
||||||
described_class.new(messaging, instagram_messenger_inbox).perform
|
described_class.new(messaging, instagram_messenger_inbox).perform
|
||||||
|
|
||||||
message = instagram_messenger_inbox.messages.first
|
message = instagram_messenger_inbox.messages.first
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :instagram_message_create_event, class: Hash 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
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'id': 'instagram-message-id-123',
|
'id': ig_entry_id,
|
||||||
'time': '2021-09-08T06:34:04+0000',
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
'messaging': [
|
'messaging': [
|
||||||
{
|
{
|
||||||
'sender': {
|
'sender': {
|
||||||
'id': 'Sender-id-1'
|
'id': sender_id
|
||||||
},
|
},
|
||||||
'recipient': {
|
'recipient': {
|
||||||
'id': 'chatwoot-app-user-id-1'
|
'id': 'chatwoot-app-user-id-1'
|
||||||
@@ -27,15 +31,19 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
|
|
||||||
factory :instagram_message_standby_event, class: Hash do
|
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
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'time': '2021-09-08T06:34:04+0000',
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
'id': 'instagram-message-id-123',
|
'id': ig_entry_id,
|
||||||
'standby': [
|
'standby': [
|
||||||
{
|
{
|
||||||
'sender': {
|
'sender': {
|
||||||
'id': 'Sender-id-1'
|
'id': sender_id
|
||||||
},
|
},
|
||||||
'recipient': {
|
'recipient': {
|
||||||
'id': 'chatwoot-app-user-id-1'
|
'id': 'chatwoot-app-user-id-1'
|
||||||
@@ -54,15 +62,19 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
|
|
||||||
factory :instagram_story_reply_event, class: Hash do
|
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
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'id': 'instagram-message-id-123',
|
'id': ig_entry_id,
|
||||||
'time': '2021-09-08T06:34:04+0000',
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
'messaging': [
|
'messaging': [
|
||||||
{
|
{
|
||||||
'sender': {
|
'sender': {
|
||||||
'id': 'Sender-id-1'
|
'id': sender_id
|
||||||
},
|
},
|
||||||
'recipient': {
|
'recipient': {
|
||||||
'id': 'chatwoot-app-user-id-1'
|
'id': 'chatwoot-app-user-id-1'
|
||||||
@@ -87,15 +99,19 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
|
|
||||||
factory :instagram_message_reply_event, class: Hash do
|
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
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'id': 'instagram-message-id-123',
|
'id': ig_entry_id,
|
||||||
'time': '2021-09-08T06:35:04+0000',
|
'time': '2021-09-08T06:35:04+0000',
|
||||||
'messaging': [
|
'messaging': [
|
||||||
{
|
{
|
||||||
'sender': {
|
'sender': {
|
||||||
'id': 'Sender-id-1'
|
'id': sender_id
|
||||||
},
|
},
|
||||||
'recipient': {
|
'recipient': {
|
||||||
'id': 'chatwoot-app-user-id-1'
|
'id': 'chatwoot-app-user-id-1'
|
||||||
@@ -117,11 +133,14 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
|
|
||||||
factory :instagram_test_text_event, class: Hash do
|
factory :instagram_test_text_event, class: Hash do
|
||||||
|
transient do
|
||||||
|
ig_entry_id { SecureRandom.uuid }
|
||||||
|
end
|
||||||
entry do
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'time' => 1_661_141_837_537,
|
'time' => 1_661_141_837_537,
|
||||||
'id' => '0',
|
'id' => ig_entry_id,
|
||||||
'messaging' => [
|
'messaging' => [
|
||||||
{
|
{
|
||||||
'sender' => {
|
'sender' => {
|
||||||
@@ -144,15 +163,19 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
|
|
||||||
factory :instagram_message_unsend_event, class: Hash do
|
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
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'id': 'instagram-message-id-123',
|
'id': ig_entry_id,
|
||||||
'time': '2021-09-08T06:34:04+0000',
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
'messaging': [
|
'messaging': [
|
||||||
{
|
{
|
||||||
'sender': {
|
'sender': {
|
||||||
'id': 'Sender-id-1'
|
'id': sender_id
|
||||||
},
|
},
|
||||||
'recipient': {
|
'recipient': {
|
||||||
'id': 'chatwoot-app-user-id-1'
|
'id': 'chatwoot-app-user-id-1'
|
||||||
@@ -171,15 +194,19 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
|
|
||||||
factory :instagram_message_attachment_event, class: Hash do
|
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
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'id': 'instagram-message-id-1234',
|
'id': ig_entry_id,
|
||||||
'time': '2021-09-08T06:34:04+0000',
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
'messaging': [
|
'messaging': [
|
||||||
{
|
{
|
||||||
'sender': {
|
'sender': {
|
||||||
'id': 'Sender-id-1'
|
'id': sender_id
|
||||||
},
|
},
|
||||||
'recipient': {
|
'recipient': {
|
||||||
'id': 'chatwoot-app-user-id-1'
|
'id': 'chatwoot-app-user-id-1'
|
||||||
@@ -205,15 +232,19 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
|
|
||||||
factory :instagram_shared_reel_event, class: Hash do
|
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
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'id': 'instagram-message-id-1234',
|
'id': ig_entry_id,
|
||||||
'time': '2021-09-08T06:34:04+0000',
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
'messaging': [
|
'messaging': [
|
||||||
{
|
{
|
||||||
'sender': {
|
'sender': {
|
||||||
'id': 'Sender-id-1'
|
'id': sender_id
|
||||||
},
|
},
|
||||||
'recipient': {
|
'recipient': {
|
||||||
'id': 'chatwoot-app-user-id-1'
|
'id': 'chatwoot-app-user-id-1'
|
||||||
@@ -241,15 +272,19 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
|
|
||||||
factory :instagram_story_mention_event, class: Hash do
|
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
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'id': 'instagram-message-id-1234',
|
'id': ig_entry_id,
|
||||||
'time': '2021-09-08T06:34:04+0000',
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
'messaging': [
|
'messaging': [
|
||||||
{
|
{
|
||||||
'sender': {
|
'sender': {
|
||||||
'id': 'Sender-id-1'
|
'id': sender_id
|
||||||
},
|
},
|
||||||
'recipient': {
|
'recipient': {
|
||||||
'id': 'chatwoot-app-user-id-1'
|
'id': 'chatwoot-app-user-id-1'
|
||||||
@@ -275,15 +310,19 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
|
|
||||||
factory :instagram_story_mention_event_with_echo, class: Hash do
|
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
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'id': 'instagram-message-id-1234',
|
'id': ig_entry_id,
|
||||||
'time': '2021-09-08T06:34:04+0000',
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
'messaging': [
|
'messaging': [
|
||||||
{
|
{
|
||||||
'sender': {
|
'sender': {
|
||||||
'id': 'Sender-id-1'
|
'id': sender_id
|
||||||
},
|
},
|
||||||
'recipient': {
|
'recipient': {
|
||||||
'id': 'chatwoot-app-user-id-1'
|
'id': 'chatwoot-app-user-id-1'
|
||||||
@@ -310,15 +349,19 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
|
|
||||||
factory :instagram_message_unsupported_event, class: Hash do
|
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
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'id': 'instagram-message-unsupported-id-123',
|
'id': ig_entry_id,
|
||||||
'time': '2021-09-08T06:34:04+0000',
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
'messaging': [
|
'messaging': [
|
||||||
{
|
{
|
||||||
'sender': {
|
'sender': {
|
||||||
'id': 'Sender-id-1'
|
'id': sender_id
|
||||||
},
|
},
|
||||||
'recipient': {
|
'recipient': {
|
||||||
'id': 'chatwoot-app-user-id-1'
|
'id': 'chatwoot-app-user-id-1'
|
||||||
@@ -337,15 +380,19 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
|
|
||||||
factory :messaging_seen_event, class: Hash do
|
factory :messaging_seen_event, class: Hash do
|
||||||
|
transient do
|
||||||
|
ig_entry_id { SecureRandom.uuid }
|
||||||
|
sender_id { "Sender-id-#{SecureRandom.hex(4)}" }
|
||||||
|
end
|
||||||
entry do
|
entry do
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'id': 'instagram-message-id-123',
|
'id': ig_entry_id,
|
||||||
'time': '2021-09-08T06:34:04+0000',
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
'messaging': [
|
'messaging': [
|
||||||
{
|
{
|
||||||
'sender': {
|
'sender': {
|
||||||
'id': 'Sender-id-1'
|
'id': sender_id
|
||||||
},
|
},
|
||||||
'recipient': {
|
'recipient': {
|
||||||
'id': 'chatwoot-app-user-id-1'
|
'id': 'chatwoot-app-user-id-1'
|
||||||
|
|||||||
@@ -10,19 +10,10 @@ describe Webhooks::InstagramEventsJob do
|
|||||||
end
|
end
|
||||||
|
|
||||||
let!(:account) { create(:account) }
|
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_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_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_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) }
|
let!(:instagram_inbox) { create(:inbox, channel: instagram_channel, account: account, greeting_enabled: false) }
|
||||||
|
|
||||||
# Combined message events into one helper
|
# Combined message events into one helper
|
||||||
let(:message_events) do
|
let(:message_events) do
|
||||||
{
|
{
|
||||||
@@ -37,6 +28,14 @@ describe Webhooks::InstagramEventsJob do
|
|||||||
}
|
}
|
||||||
end
|
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
|
describe '#perform' do
|
||||||
context 'when handling messaging events for Instagram via Facebook page' do
|
context 'when handling messaging events for Instagram via Facebook page' do
|
||||||
let(:fb_object) { double }
|
let(:fb_object) { double }
|
||||||
@@ -47,8 +46,9 @@ describe Webhooks::InstagramEventsJob do
|
|||||||
|
|
||||||
it 'creates incoming message in the instagram inbox' do
|
it 'creates incoming message in the instagram inbox' do
|
||||||
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
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(
|
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])
|
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
|
it 'creates standby message in the instagram inbox' do
|
||||||
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
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(
|
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])
|
instagram_webhook.perform_now(message_events[:standby][:entry])
|
||||||
|
|
||||||
@@ -82,10 +83,11 @@ describe Webhooks::InstagramEventsJob do
|
|||||||
it 'handle instagram unsend message event' do
|
it 'handle instagram unsend message event' do
|
||||||
message = create(:message, inbox_id: instagram_messenger_inbox.id, source_id: 'message-id-to-delete')
|
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)
|
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(
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
{
|
{
|
||||||
name: 'Jane',
|
name: 'Jane',
|
||||||
id: 'Sender-id-1',
|
id: sender_id,
|
||||||
account_id: instagram_messenger_inbox.account_id,
|
account_id: instagram_messenger_inbox.account_id,
|
||||||
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
||||||
}.with_indifferent_access
|
}.with_indifferent_access
|
||||||
@@ -104,8 +106,9 @@ describe Webhooks::InstagramEventsJob do
|
|||||||
|
|
||||||
it 'creates incoming message with attachments in the instagram inbox' do
|
it 'creates incoming message with attachments in the instagram inbox' do
|
||||||
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
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(
|
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])
|
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
|
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)
|
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(
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
return_object.with_indifferent_access,
|
return_object_for(sender_id).with_indifferent_access,
|
||||||
{ story:
|
{ story:
|
||||||
{
|
{
|
||||||
mention: {
|
mention: {
|
||||||
@@ -165,8 +169,9 @@ describe Webhooks::InstagramEventsJob do
|
|||||||
|
|
||||||
it 'handles unsupported message' do
|
it 'handles unsupported message' do
|
||||||
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
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(
|
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])
|
instagram_webhook.perform_now(message_events[:unsupported][:entry])
|
||||||
@@ -184,19 +189,22 @@ describe Webhooks::InstagramEventsJob do
|
|||||||
before do
|
before do
|
||||||
instagram_channel.update(access_token: 'valid_instagram_token')
|
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(
|
.to_return(
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: proc { |request|
|
||||||
name: 'Jane',
|
sender_id = request.uri.path.split('/').last.split('?').first
|
||||||
username: 'some_user_name',
|
{
|
||||||
profile_pic: 'https://chatwoot-assets.local/sample.png',
|
name: 'Jane',
|
||||||
id: 'Sender-id-1',
|
username: 'some_user_name',
|
||||||
follower_count: 100,
|
profile_pic: 'https://chatwoot-assets.local/sample.png',
|
||||||
is_user_follow_business: true,
|
id: sender_id,
|
||||||
is_business_follow_user: true,
|
follower_count: 100,
|
||||||
is_verified_user: false
|
is_user_follow_business: true,
|
||||||
}.to_json,
|
is_business_follow_user: true,
|
||||||
|
is_verified_user: false
|
||||||
|
}.to_json
|
||||||
|
},
|
||||||
headers: { 'Content-Type' => 'application/json' }
|
headers: { 'Content-Type' => 'application/json' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@@ -289,14 +297,15 @@ describe Webhooks::InstagramEventsJob do
|
|||||||
stub_request(:get, %r{https://graph\.instagram\.com/v22\.0/.*\?.*})
|
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)
|
.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_webhook.perform_now(message_events[:dm][:entry])
|
||||||
|
|
||||||
instagram_inbox.reload
|
instagram_inbox.reload
|
||||||
|
|
||||||
expect(instagram_inbox.contacts.count).to be 1
|
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.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.conversations.count).to eq 1
|
||||||
expect(instagram_inbox.messages.count).to eq 1
|
expect(instagram_inbox.messages.count).to eq 1
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ RSpec.configure do |config|
|
|||||||
config.include SlackStubs
|
config.include SlackStubs
|
||||||
config.include FileUploadHelpers
|
config.include FileUploadHelpers
|
||||||
config.include CsvSpecHelpers
|
config.include CsvSpecHelpers
|
||||||
|
config.include InstagramSpecHelpers
|
||||||
config.include Devise::Test::IntegrationHelpers, type: :request
|
config.include Devise::Test::IntegrationHelpers, type: :request
|
||||||
config.include ActiveSupport::Testing::TimeHelpers
|
config.include ActiveSupport::Testing::TimeHelpers
|
||||||
config.include ActionCable::TestHelper
|
config.include ActionCable::TestHelper
|
||||||
|
|||||||
20
spec/support/instagram_spec_helpers.rb
Normal file
20
spec/support/instagram_spec_helpers.rb
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user