Skip calling the Slack file upload API for message types such as fallback (e.g., Facebook and location messages) that lack actual file data in attachments. This prevents unnecessary API calls and resolves a Sentry error currently occurring in production. fixes: https://github.com/chatwoot/chatwoot/issues/10460
320 lines
13 KiB
Ruby
320 lines
13 KiB
Ruby
require 'rails_helper'
|
|
|
|
describe Integrations::Slack::SendOnSlackService do
|
|
let!(:contact) { create(:contact) }
|
|
let(:channel_email) { create(:channel_email) }
|
|
let!(:conversation) { create(:conversation, inbox: channel_email.inbox, contact: contact, identifier: nil) }
|
|
let(:account) { conversation.account }
|
|
let!(:hook) { create(:integrations_hook, account: account) }
|
|
let!(:message) do
|
|
create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation)
|
|
end
|
|
let!(:template_message) do
|
|
create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation, message_type: :template)
|
|
end
|
|
|
|
let(:slack_message) { double }
|
|
let(:file_attachment) { double }
|
|
let(:slack_message_content) { double }
|
|
let(:slack_client) { double }
|
|
let(:builder) { described_class.new(message: message, hook: hook) }
|
|
let(:link_builder) { described_class.new(message: nil, hook: hook) }
|
|
let(:conversation_link) do
|
|
"<#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{account.id}/conversations/#{conversation.display_id}|Click here> to view the conversation."
|
|
end
|
|
|
|
before do
|
|
allow(builder).to receive(:slack_client).and_return(slack_client)
|
|
allow(link_builder).to receive(:slack_client).and_return(slack_client)
|
|
allow(slack_message).to receive(:[]).with('ts').and_return('12345.6789')
|
|
allow(slack_message).to receive(:[]).with('message').and_return(slack_message_content)
|
|
allow(slack_message_content).to receive(:[]).with('ts').and_return('6789.12345')
|
|
allow(slack_message_content).to receive(:[]).with('thread_ts').and_return('12345.6789')
|
|
end
|
|
|
|
describe '#perform' do
|
|
context 'without identifier' do
|
|
it 'updates slack thread id in conversation' do
|
|
inbox = conversation.inbox
|
|
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: "\n*Inbox:* #{inbox.name} (#{inbox.inbox_type})\n#{conversation_link}\n\n#{message.content}",
|
|
username: "#{message.sender.name} (Contact)",
|
|
thread_ts: nil,
|
|
icon_url: anything,
|
|
unfurl_links: false
|
|
).and_return(slack_message)
|
|
|
|
builder.perform
|
|
|
|
expect(conversation.reload.identifier).to eq '12345.6789'
|
|
end
|
|
|
|
context 'with subject line in email' do
|
|
let(:message) do
|
|
create(:message,
|
|
content_attributes: { 'email': { 'subject': 'Sample subject line' } },
|
|
content: 'Sample Body',
|
|
account: conversation.account,
|
|
inbox: conversation.inbox, conversation: conversation)
|
|
end
|
|
|
|
it 'creates slack message with subject line' do
|
|
inbox = conversation.inbox
|
|
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: "\n*Inbox:* #{inbox.name} (#{inbox.inbox_type})\n#{conversation_link}\n" \
|
|
"*Subject:* Sample subject line\n\n\n#{message.content}",
|
|
username: "#{message.sender.name} (Contact)",
|
|
thread_ts: nil,
|
|
icon_url: anything,
|
|
unfurl_links: false
|
|
).and_return(slack_message)
|
|
|
|
builder.perform
|
|
|
|
expect(conversation.reload.identifier).to eq '12345.6789'
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with identifier' do
|
|
before do
|
|
conversation.update!(identifier: 'random_slack_thread_ts')
|
|
end
|
|
|
|
it 'sent message to slack' do
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: message.content,
|
|
username: "#{message.sender.name} (Contact)",
|
|
thread_ts: conversation.identifier,
|
|
icon_url: anything,
|
|
unfurl_links: true
|
|
).and_return(slack_message)
|
|
|
|
builder.perform
|
|
|
|
expect(message.external_source_id_slack).to eq 'cw-origin-6789.12345'
|
|
end
|
|
|
|
it 'sent message will send to the the previous thread if the slack disconnects and connects to a same channel.' do
|
|
allow(slack_message).to receive(:[]).with('message').and_return({ 'thread_ts' => conversation.identifier })
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: message.content,
|
|
username: "#{message.sender.name} (Contact)",
|
|
thread_ts: conversation.identifier,
|
|
icon_url: anything,
|
|
unfurl_links: true
|
|
).and_return(slack_message)
|
|
|
|
builder.perform
|
|
|
|
expect(conversation.identifier).to eq 'random_slack_thread_ts'
|
|
end
|
|
|
|
it 'sent message will create a new thread if the slack disconnects and connects to a different channel' do
|
|
allow(slack_message).to receive(:[]).with('message').and_return({ 'thread_ts' => nil })
|
|
allow(slack_message).to receive(:[]).with('ts').and_return('1691652432.896169')
|
|
|
|
hook.update!(reference_id: 'C12345')
|
|
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: 'C12345',
|
|
text: message.content,
|
|
username: "#{message.sender.name} (Contact)",
|
|
thread_ts: conversation.identifier,
|
|
icon_url: anything,
|
|
unfurl_links: true
|
|
).and_return(slack_message)
|
|
|
|
builder.perform
|
|
|
|
expect(hook.reload.reference_id).to eq 'C12345'
|
|
expect(conversation.identifier).to eq '1691652432.896169'
|
|
end
|
|
|
|
it 'sent lnk unfurl to slack' do
|
|
unflur_payload = { :channel => 'channel',
|
|
:ts => 'timestamp',
|
|
:unfurls =>
|
|
{ :'https://qa.chatwoot.com/app/accounts/1/conversations/1' =>
|
|
{ :blocks => [{ :type => 'section',
|
|
:text => { :type => 'plain_text', :text => 'This is a plain text section block.', :emoji => true } }] } } }
|
|
allow(slack_client).to receive(:chat_unfurl).with(unflur_payload)
|
|
link_builder.link_unfurl(unflur_payload)
|
|
expect(slack_client).to have_received(:chat_unfurl).with(unflur_payload)
|
|
end
|
|
|
|
it 'sent attachment on slack' do
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: message.content,
|
|
username: "#{message.sender.name} (Contact)",
|
|
thread_ts: conversation.identifier,
|
|
icon_url: anything,
|
|
unfurl_links: true
|
|
).and_return(slack_message)
|
|
|
|
attachment = message.attachments.new(account_id: message.account_id, file_type: :image)
|
|
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
|
|
|
expect(slack_client).to receive(:files_upload).with(hash_including(
|
|
channels: hook.reference_id,
|
|
thread_ts: conversation.identifier,
|
|
initial_comment: 'Attached File!',
|
|
filetype: 'png',
|
|
content: anything,
|
|
filename: attachment.file.filename,
|
|
title: attachment.file.filename
|
|
)).and_return(file_attachment)
|
|
|
|
message.save!
|
|
|
|
builder.perform
|
|
|
|
expect(message.external_source_id_slack).to eq 'cw-origin-6789.12345'
|
|
expect(message.attachments).to be_any
|
|
end
|
|
|
|
it 'will not call file_upload if attachment does not have a file (e.g facebook - fallback type)' do
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: message.content,
|
|
username: "#{message.sender.name} (Contact)",
|
|
thread_ts: conversation.identifier,
|
|
icon_url: anything,
|
|
unfurl_links: true
|
|
).and_return(slack_message)
|
|
|
|
message.attachments.new(account_id: message.account_id, file_type: :fallback)
|
|
|
|
expect(slack_client).not_to receive(:files_upload)
|
|
|
|
message.save!
|
|
|
|
builder.perform
|
|
|
|
expect(message.external_source_id_slack).to eq 'cw-origin-6789.12345'
|
|
expect(message.attachments).to be_any
|
|
end
|
|
|
|
it 'sent a template message on slack' do
|
|
builder = described_class.new(message: template_message, hook: hook)
|
|
allow(builder).to receive(:slack_client).and_return(slack_client)
|
|
template_message.update!(sender: nil)
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: template_message.content,
|
|
username: 'Bot',
|
|
thread_ts: conversation.identifier,
|
|
icon_url: anything,
|
|
unfurl_links: true
|
|
).and_return(slack_message)
|
|
|
|
builder.perform
|
|
|
|
expect(template_message.external_source_id_slack).to eq 'cw-origin-6789.12345'
|
|
end
|
|
|
|
it 'sent a activity message on slack' do
|
|
template_message.update!(message_type: :activity)
|
|
template_message.update!(sender: nil)
|
|
builder = described_class.new(message: template_message, hook: hook)
|
|
allow(builder).to receive(:slack_client).and_return(slack_client)
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: "_#{template_message.content}_",
|
|
username: 'System',
|
|
thread_ts: conversation.identifier,
|
|
icon_url: anything,
|
|
unfurl_links: true
|
|
).and_return(slack_message)
|
|
|
|
builder.perform
|
|
expect(template_message.external_source_id_slack).to eq 'cw-origin-6789.12345'
|
|
end
|
|
|
|
it 'disables hook on Slack AccountInactive error' do
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: message.content,
|
|
username: "#{message.sender.name} (Contact)",
|
|
thread_ts: conversation.identifier,
|
|
icon_url: anything,
|
|
unfurl_links: true
|
|
).and_raise(Slack::Web::Api::Errors::AccountInactive.new('Account disconnected'))
|
|
|
|
allow(hook).to receive(:prompt_reauthorization!)
|
|
|
|
builder.perform
|
|
expect(hook).to be_disabled
|
|
expect(hook).to have_received(:prompt_reauthorization!)
|
|
end
|
|
|
|
it 'disables hook on Slack MissingScope error' do
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: message.content,
|
|
username: "#{message.sender.name} (Contact)",
|
|
thread_ts: conversation.identifier,
|
|
icon_url: anything,
|
|
unfurl_links: true
|
|
).and_raise(Slack::Web::Api::Errors::MissingScope.new('Account disconnected'))
|
|
|
|
allow(hook).to receive(:prompt_reauthorization!)
|
|
|
|
builder.perform
|
|
expect(hook).to be_disabled
|
|
expect(hook).to have_received(:prompt_reauthorization!)
|
|
end
|
|
end
|
|
|
|
context 'when message contains mentions' do
|
|
it 'sends formatted message to slack along with inbox name when identifier not present' do
|
|
inbox = conversation.inbox
|
|
message.update!(content: "Hi [@#{contact.name}](mention://user/#{contact.id}/#{contact.name}), welcome to Chatwoot!")
|
|
formatted_message_text = message.content.gsub(RegexHelper::MENTION_REGEX, '\1')
|
|
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: "\n*Inbox:* #{inbox.name} (#{inbox.inbox_type})\n#{conversation_link}\n\n#{formatted_message_text}",
|
|
username: "#{message.sender.name} (Contact)",
|
|
thread_ts: nil,
|
|
icon_url: anything,
|
|
unfurl_links: false
|
|
).and_return(slack_message)
|
|
|
|
builder.perform
|
|
end
|
|
|
|
it 'sends formatted message to slack when identifier is present' do
|
|
conversation.update!(identifier: 'random_slack_thread_ts')
|
|
message.update!(content: "Hi [@#{contact.name}](mention://user/#{contact.id}/#{contact.name}), welcome to Chatwoot!")
|
|
formatted_message_text = message.content.gsub(RegexHelper::MENTION_REGEX, '\1')
|
|
|
|
expect(slack_client).to receive(:chat_postMessage).with(
|
|
channel: hook.reference_id,
|
|
text: formatted_message_text,
|
|
username: "#{message.sender.name} (Contact)",
|
|
thread_ts: 'random_slack_thread_ts',
|
|
icon_url: anything,
|
|
unfurl_links: true
|
|
).and_return(slack_message)
|
|
|
|
builder.perform
|
|
end
|
|
|
|
it 'will not throw error if message content is nil' do
|
|
message.update!(content: nil)
|
|
conversation.update!(identifier: 'random_slack_thread_ts')
|
|
|
|
expect { builder.perform }.not_to raise_error
|
|
end
|
|
end
|
|
end
|
|
end
|