fix: Attach instagram images with file type story_mentions (#4100)
This commit is contained in:
@@ -1,10 +1,14 @@
|
|||||||
class Messages::Messenger::MessageBuilder
|
class Messages::Messenger::MessageBuilder
|
||||||
|
include ::FileTypeHelper
|
||||||
|
|
||||||
def process_attachment(attachment)
|
def process_attachment(attachment)
|
||||||
return if attachment['type'].to_sym == :template
|
return if attachment['type'].to_sym == :template
|
||||||
|
|
||||||
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
||||||
attachment_obj.save!
|
attachment_obj.save!
|
||||||
attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url]
|
attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url]
|
||||||
|
fetch_story_link(attachment_obj) if attachment_obj.file_type == 'story_mention'
|
||||||
|
update_attachment_file_type(attachment_obj)
|
||||||
end
|
end
|
||||||
|
|
||||||
def attach_file(attachment, file_url)
|
def attach_file(attachment, file_url)
|
||||||
@@ -22,7 +26,7 @@ class Messages::Messenger::MessageBuilder
|
|||||||
file_type = attachment['type'].to_sym
|
file_type = attachment['type'].to_sym
|
||||||
params = { file_type: file_type, account_id: @message.account_id }
|
params = { file_type: file_type, account_id: @message.account_id }
|
||||||
|
|
||||||
if [:image, :file, :audio, :video].include? file_type
|
if [:image, :file, :audio, :video, :share, :story_mention].include? file_type
|
||||||
params.merge!(file_type_params(attachment))
|
params.merge!(file_type_params(attachment))
|
||||||
elsif file_type == :location
|
elsif file_type == :location
|
||||||
params.merge!(location_params(attachment))
|
params.merge!(location_params(attachment))
|
||||||
@@ -39,4 +43,31 @@ class Messages::Messenger::MessageBuilder
|
|||||||
remote_file_url: attachment['payload']['url']
|
remote_file_url: attachment['payload']['url']
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_attachment_file_type(attachment)
|
||||||
|
return unless attachment.file_type == 'share' || attachment.file_type == 'story_mention'
|
||||||
|
|
||||||
|
attachment.file_type = file_type(attachment.file&.content_type)
|
||||||
|
attachment.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_story_link(attachment)
|
||||||
|
message = attachment.message
|
||||||
|
begin
|
||||||
|
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
||||||
|
result = k.get_object(message.source_id, fields: %w[story from]) || {}
|
||||||
|
rescue Koala::Facebook::AuthenticationError
|
||||||
|
@inbox.channel.authorization_error!
|
||||||
|
raise
|
||||||
|
rescue StandardError => e
|
||||||
|
result = {}
|
||||||
|
Sentry.capture_exception(e)
|
||||||
|
end
|
||||||
|
story_id = result['story']['mention']['id']
|
||||||
|
story_sender = result['from']['username']
|
||||||
|
message.content_attributes[:story_sender] = story_sender
|
||||||
|
message.content_attributes[:story_id] = story_id
|
||||||
|
message.content = I18n.t('conversations.messages.instagram_story_content', story_sender: story_sender)
|
||||||
|
message.save!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ module FileTypeHelper
|
|||||||
'image/png',
|
'image/png',
|
||||||
'image/gif',
|
'image/gif',
|
||||||
'image/bmp',
|
'image/bmp',
|
||||||
'image/webp'
|
'image/webp',
|
||||||
|
'image'
|
||||||
].include?(content_type)
|
].include?(content_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -23,7 +24,8 @@ module FileTypeHelper
|
|||||||
'video/ogg',
|
'video/ogg',
|
||||||
'video/mp4',
|
'video/mp4',
|
||||||
'video/webm',
|
'video/webm',
|
||||||
'video/quicktime'
|
'video/quicktime',
|
||||||
|
'video'
|
||||||
].include?(content_type)
|
].include?(content_type)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,7 +50,10 @@
|
|||||||
<bubble-actions
|
<bubble-actions
|
||||||
:id="data.id"
|
:id="data.id"
|
||||||
:sender="data.sender"
|
:sender="data.sender"
|
||||||
|
:story-sender="storySender"
|
||||||
|
:story-id="storyId"
|
||||||
:is-a-tweet="isATweet"
|
:is-a-tweet="isATweet"
|
||||||
|
:has-instagram-story="hasInstagramStory"
|
||||||
:is-email="isEmailContentType"
|
:is-email="isEmailContentType"
|
||||||
:is-private="data.private"
|
:is-private="data.private"
|
||||||
:message-type="data.message_type"
|
:message-type="data.message_type"
|
||||||
@@ -146,6 +149,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
hasInstagramStory: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -209,6 +216,12 @@ export default {
|
|||||||
sender() {
|
sender() {
|
||||||
return this.data.sender || {};
|
return this.data.sender || {};
|
||||||
},
|
},
|
||||||
|
storySender() {
|
||||||
|
return this.contentAttributes.story_sender || {};
|
||||||
|
},
|
||||||
|
storyId() {
|
||||||
|
return this.contentAttributes.story_id || {};
|
||||||
|
},
|
||||||
contentType() {
|
contentType() {
|
||||||
const {
|
const {
|
||||||
data: { content_type: contentType },
|
data: { content_type: contentType },
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
class="message--read"
|
class="message--read"
|
||||||
:data="message"
|
:data="message"
|
||||||
:is-a-tweet="isATweet"
|
:is-a-tweet="isATweet"
|
||||||
|
:has-instagram-story="hasInstagramStory"
|
||||||
/>
|
/>
|
||||||
<li v-show="getUnreadCount != 0" class="unread--toast">
|
<li v-show="getUnreadCount != 0" class="unread--toast">
|
||||||
<span class="text-uppercase">
|
<span class="text-uppercase">
|
||||||
@@ -64,6 +65,7 @@
|
|||||||
class="message--unread"
|
class="message--unread"
|
||||||
:data="message"
|
:data="message"
|
||||||
:is-a-tweet="isATweet"
|
:is-a-tweet="isATweet"
|
||||||
|
:has-instagram-story="hasInstagramStory"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
<div
|
<div
|
||||||
@@ -215,6 +217,10 @@ export default {
|
|||||||
return this.conversationType === 'tweet';
|
return this.conversationType === 'tweet';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hasInstagramStory() {
|
||||||
|
return this.conversationType === 'instagram_direct_message';
|
||||||
|
},
|
||||||
|
|
||||||
selectedTweet() {
|
selectedTweet() {
|
||||||
if (this.selectedTweetId) {
|
if (this.selectedTweetId) {
|
||||||
const { messages = [] } = this.getMessages;
|
const { messages = [] } = this.getMessages;
|
||||||
|
|||||||
@@ -35,6 +35,19 @@
|
|||||||
size="16"
|
size="16"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
<a
|
||||||
|
v-if="hasInstagramStory && (isIncoming || isOutgoing) && linkToStory"
|
||||||
|
:href="linkToStory"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer nofollow"
|
||||||
|
>
|
||||||
|
<fluent-icon
|
||||||
|
v-tooltip.top-start="$t('CHAT_LIST.LINK_TO_STORY')"
|
||||||
|
icon="open"
|
||||||
|
class="action--icon cursor-pointer"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
v-if="isATweet && (isOutgoing || isIncoming) && linkToTweet"
|
v-if="isATweet && (isOutgoing || isIncoming) && linkToTweet"
|
||||||
:href="linkToTweet"
|
:href="linkToTweet"
|
||||||
@@ -67,6 +80,14 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
storySender: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
storyId: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
isEmail: {
|
isEmail: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
@@ -79,6 +100,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
hasInstagramStory: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
messageType: {
|
messageType: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1,
|
default: 1,
|
||||||
@@ -119,6 +144,13 @@ export default {
|
|||||||
return `https://twitter.com/${screenName ||
|
return `https://twitter.com/${screenName ||
|
||||||
this.inbox.name}/status/${sourceId}`;
|
this.inbox.name}/status/${sourceId}`;
|
||||||
},
|
},
|
||||||
|
linkToStory() {
|
||||||
|
if (!this.storyId || !this.storySender) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const { storySender, storyId } = this;
|
||||||
|
return `https://www.instagram.com/stories/${storySender}/${storyId}`;
|
||||||
|
},
|
||||||
showSentIndicator() {
|
showSentIndicator() {
|
||||||
return this.isOutgoing && this.sourceId && this.isAnEmailChannel;
|
return this.isOutgoing && this.sourceId && this.isAnEmailChannel;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -76,6 +76,7 @@
|
|||||||
"RECEIVED_VIA_EMAIL": "Received via email",
|
"RECEIVED_VIA_EMAIL": "Received via email",
|
||||||
"VIEW_TWEET_IN_TWITTER": "View tweet in Twitter",
|
"VIEW_TWEET_IN_TWITTER": "View tweet in Twitter",
|
||||||
"REPLY_TO_TWEET": "Reply to this tweet",
|
"REPLY_TO_TWEET": "Reply to this tweet",
|
||||||
|
"LINK_TO_STORY": "Go to instagram story",
|
||||||
"SENT": "Sent successfully",
|
"SENT": "Sent successfully",
|
||||||
"NO_MESSAGES": "No Messages",
|
"NO_MESSAGES": "No Messages",
|
||||||
"NO_CONTENT": "No content available",
|
"NO_CONTENT": "No content available",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class Attachment < ApplicationRecord
|
|||||||
has_one_attached :file
|
has_one_attached :file
|
||||||
validate :acceptable_file
|
validate :acceptable_file
|
||||||
|
|
||||||
enum file_type: [:image, :audio, :video, :file, :location, :fallback]
|
enum file_type: [:image, :audio, :video, :file, :location, :fallback, :share, :story_mention]
|
||||||
|
|
||||||
def push_event_data
|
def push_event_data
|
||||||
return unless file_type
|
return unless file_type
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class Message < ApplicationRecord
|
|||||||
# [:deleted] : Used to denote whether the message was deleted by the agent
|
# [:deleted] : Used to denote whether the message was deleted by the agent
|
||||||
# [:external_created_at] : Can specify if the message was created at a different timestamp externally
|
# [:external_created_at] : Can specify if the message was created at a different timestamp externally
|
||||||
store :content_attributes, accessors: [:submitted_email, :items, :submitted_values, :email, :in_reply_to, :deleted,
|
store :content_attributes, accessors: [:submitted_email, :items, :submitted_values, :email, :in_reply_to, :deleted,
|
||||||
:external_created_at], coder: JSON
|
:external_created_at, :story_sender, :story_id], coder: JSON
|
||||||
|
|
||||||
store :external_source_ids, accessors: [:slack], coder: JSON, prefix: :external_source_id
|
store :external_source_ids, accessors: [:slack], coder: JSON, prefix: :external_source_id
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ en:
|
|||||||
failed: Signup failed
|
failed: Signup failed
|
||||||
contacts:
|
contacts:
|
||||||
import:
|
import:
|
||||||
failed: File is blank
|
failed: File is blank
|
||||||
|
|
||||||
reports:
|
reports:
|
||||||
period: Reporting period %{since} to %{until}
|
period: Reporting period %{since} to %{until}
|
||||||
@@ -67,6 +67,7 @@ en:
|
|||||||
conversation_mention: "You have been mentioned in conversation [ID - %{display_id}] by %{name}"
|
conversation_mention: "You have been mentioned in conversation [ID - %{display_id}] by %{name}"
|
||||||
conversations:
|
conversations:
|
||||||
messages:
|
messages:
|
||||||
|
instagram_story_content: "%{story_sender} mentioned you in the story: "
|
||||||
deleted: This message was deleted
|
deleted: This message was deleted
|
||||||
activity:
|
activity:
|
||||||
status:
|
status:
|
||||||
|
|||||||
@@ -55,4 +55,72 @@ FactoryBot.define do
|
|||||||
end
|
end
|
||||||
initialize_with { attributes }
|
initialize_with { attributes }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
factory :instagram_message_attachment_event, class: Hash do
|
||||||
|
entry do
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'id': 'instagram-message-id-1234',
|
||||||
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
|
'messaging': [
|
||||||
|
{
|
||||||
|
'sender': {
|
||||||
|
'id': 'Sender-id-1'
|
||||||
|
},
|
||||||
|
'recipient': {
|
||||||
|
'id': 'chatwoot-app-user-id-1'
|
||||||
|
},
|
||||||
|
'timestamp': '2021-09-08T06:34:04+0000',
|
||||||
|
'message': {
|
||||||
|
'mid': 'message-id-1',
|
||||||
|
'attachments': [
|
||||||
|
{
|
||||||
|
'type': 'share',
|
||||||
|
'payload': {
|
||||||
|
'url': 'https://imagekit.io/blog/content/images/2020/05/media_library.jpeg'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
initialize_with { attributes }
|
||||||
|
end
|
||||||
|
|
||||||
|
factory :instagram_story_mention_event, class: Hash do
|
||||||
|
entry do
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'id': 'instagram-message-id-1234',
|
||||||
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
|
'messaging': [
|
||||||
|
{
|
||||||
|
'sender': {
|
||||||
|
'id': 'Sender-id-1'
|
||||||
|
},
|
||||||
|
'recipient': {
|
||||||
|
'id': 'chatwoot-app-user-id-1'
|
||||||
|
},
|
||||||
|
'timestamp': '2021-09-08T06:34:04+0000',
|
||||||
|
'message': {
|
||||||
|
'mid': 'message-id-1',
|
||||||
|
'attachments': [
|
||||||
|
{
|
||||||
|
'type': 'story_mention',
|
||||||
|
'payload': {
|
||||||
|
'url': 'https://imagekit.io/blog/content/images/2020/05/media_library.jpeg'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
initialize_with { attributes }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,13 +6,30 @@ describe Webhooks::InstagramEventsJob do
|
|||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:post, /graph.facebook.com/)
|
stub_request(:post, /graph.facebook.com/)
|
||||||
|
stub_request(:get, 'https://imagekit.io/blog/content/images/2020/05/media_library.jpeg')
|
||||||
|
.with(
|
||||||
|
headers: {
|
||||||
|
'Accept' => '*/*',
|
||||||
|
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
|
||||||
|
'User-Agent' => 'Down/5.3.0'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.to_return(status: 200, body: '', headers: {})
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:account) { create(:account) }
|
let!(:account) { create(:account) }
|
||||||
|
let(:return_onject) do
|
||||||
|
{ name: 'Jane',
|
||||||
|
id: 'Sender-id-1',
|
||||||
|
account_id: instagram_inbox.account_id,
|
||||||
|
profile_pic: 'https://chatwoot-assets.local/sample.png' }
|
||||||
|
end
|
||||||
let!(:instagram_channel) { create(:channel_instagram_fb_page, account: account, instagram_id: 'chatwoot-app-user-id-1') }
|
let!(:instagram_channel) { create(:channel_instagram_fb_page, 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) }
|
||||||
let!(:dm_params) { build(:instagram_message_create_event).with_indifferent_access }
|
let!(:dm_params) { build(:instagram_message_create_event).with_indifferent_access }
|
||||||
let!(:test_params) { build(:instagram_test_text_event).with_indifferent_access }
|
let!(:test_params) { build(:instagram_test_text_event).with_indifferent_access }
|
||||||
|
let!(:attachment_params) { build(:instagram_message_attachment_event).with_indifferent_access }
|
||||||
|
let!(:story_mention_params) { build(:instagram_story_mention_event).with_indifferent_access }
|
||||||
let(:fb_object) { double }
|
let(:fb_object) { double }
|
||||||
|
|
||||||
describe '#perform' do
|
describe '#perform' do
|
||||||
@@ -20,12 +37,7 @@ 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)
|
||||||
allow(fb_object).to receive(:get_object).and_return(
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
{
|
return_onject.with_indifferent_access
|
||||||
name: 'Jane',
|
|
||||||
id: 'Sender-id-1',
|
|
||||||
account_id: instagram_inbox.account_id,
|
|
||||||
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
|
||||||
}.with_indifferent_access
|
|
||||||
)
|
)
|
||||||
instagram_webhook.perform_now(dm_params[:entry])
|
instagram_webhook.perform_now(dm_params[:entry])
|
||||||
|
|
||||||
@@ -39,12 +51,7 @@ describe Webhooks::InstagramEventsJob do
|
|||||||
it 'creates test text message in the instagram inbox' do
|
it 'creates test text 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)
|
||||||
allow(fb_object).to receive(:get_object).and_return(
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
{
|
return_onject.with_indifferent_access
|
||||||
name: 'Jane',
|
|
||||||
id: 'Sender-id-1',
|
|
||||||
account_id: instagram_inbox.account_id,
|
|
||||||
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
|
||||||
}.with_indifferent_access
|
|
||||||
)
|
)
|
||||||
instagram_webhook.perform_now(test_params[:entry])
|
instagram_webhook.perform_now(test_params[:entry])
|
||||||
|
|
||||||
@@ -53,6 +60,46 @@ describe Webhooks::InstagramEventsJob do
|
|||||||
expect(instagram_inbox.messages.count).to be 1
|
expect(instagram_inbox.messages.count).to be 1
|
||||||
expect(instagram_inbox.messages.last.content).to eq('This is a test message from facebook.')
|
expect(instagram_inbox.messages.last.content).to eq('This is a test message from facebook.')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'creates incoming message with attachments in the instagram inbox' do
|
||||||
|
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||||
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
|
return_onject.with_indifferent_access
|
||||||
|
)
|
||||||
|
instagram_webhook.perform_now(attachment_params[:entry])
|
||||||
|
|
||||||
|
instagram_inbox.reload
|
||||||
|
|
||||||
|
expect(instagram_inbox.contacts.count).to be 1
|
||||||
|
expect(instagram_inbox.messages.count).to be 1
|
||||||
|
expect(instagram_inbox.messages.last.attachments.count).to be 1
|
||||||
|
end
|
||||||
|
|
||||||
|
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(fb_object).to receive(:get_object).and_return(
|
||||||
|
return_onject.with_indifferent_access,
|
||||||
|
{ story:
|
||||||
|
{
|
||||||
|
mention: {
|
||||||
|
link:
|
||||||
|
'https://lookaside.fbsbx.com/ig_messaging_cdn/?asset_id=17920786367196703&signature=Aby8EXbvNu4on9efDQecXDasiJX2s0FgWhFGz3mNFB__CsHR22O_1bJiYHkbp3mC1NQeW4jHxls9WyqVgRPcyonUbSJmD44UwLfFhbCK2obesWnFi7VOnisqLu48Xd6KYuNex7uSCQKWM-nw55zQ23bBgfCYw6h5hiJjFHwJDZYm65zXpQ',
|
||||||
|
id: '17920786367196703'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
username: 'Sender-id-1', id: 'Sender-id-1'
|
||||||
|
},
|
||||||
|
id: 'instagram-message-id-1234' }.with_indifferent_access
|
||||||
|
)
|
||||||
|
|
||||||
|
instagram_webhook.perform_now(story_mention_params[:entry])
|
||||||
|
|
||||||
|
instagram_inbox.reload
|
||||||
|
|
||||||
|
expect(instagram_inbox.messages.count).to be 1
|
||||||
|
expect(instagram_inbox.messages.last.attachments.count).to be 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user