fix(tiktok): Resolve media upload failures and gate attachments by conversation capability (#13643)
This PR fixes TikTok attachment send failures and adds a capability-based guard so attachments are only enabled for conversations that support media sending. - Fixed TikTok media upload request formatting so TikTok accepts image uploads reliably. - Added TikTok capability check (IMAGE_SEND) during conversation creation. - Stored capability in conversation.additional_attributes.tiktok_capabilities. - Updated reply composer UI to disable/hide attachment upload for TikTok conversations where image_send is false. Fixes https://linear.app/chatwoot/issue/CW-6532/enable-attachments-based-on-the-conversation-capability and https://linear.app/chatwoot/issue/CW-6996/unable-to-send-image-attachments-to-tiktok-customer-400-parsing-error --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
Muhsin
parent
cc5974da9b
commit
b8108b71c1
@@ -273,6 +273,10 @@ export default {
|
||||
return MESSAGE_MAX_LENGTH.GENERAL;
|
||||
},
|
||||
showFileUpload() {
|
||||
const { image_send: imageSend } =
|
||||
this.currentChat?.additional_attributes?.tiktok_capabilities ?? {};
|
||||
const tiktokAttachmentSupported = imageSend ?? true;
|
||||
|
||||
return (
|
||||
this.isAWebWidgetInbox ||
|
||||
this.isAFacebookInbox ||
|
||||
@@ -283,7 +287,7 @@ export default {
|
||||
this.isATelegramChannel ||
|
||||
this.isALineChannel ||
|
||||
this.isAnInstagramChannel ||
|
||||
this.isATiktokChannel
|
||||
(this.isATiktokChannel && tiktokAttachmentSupported)
|
||||
);
|
||||
},
|
||||
replyButtonLabel() {
|
||||
@@ -706,6 +710,7 @@ export default {
|
||||
|
||||
// Don't handle paste if editor is disabled
|
||||
if (this.isEditorDisabled) return;
|
||||
if (!this.showFileUpload && !this.isOnPrivateNote) return;
|
||||
|
||||
// Filter valid files (non-zero size)
|
||||
Array.from(e.clipboardData.files)
|
||||
@@ -1025,6 +1030,8 @@ export default {
|
||||
});
|
||||
},
|
||||
attachFile({ blob, file }) {
|
||||
if (!this.showFileUpload && !this.isOnPrivateNote) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file.file);
|
||||
reader.onloadend = () => {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
require 'faraday'
|
||||
require 'faraday/multipart'
|
||||
|
||||
class Tiktok::Client
|
||||
# Always use Tiktok::TokenService to get a valid access token
|
||||
pattr_initialize [:business_id!, :access_token!]
|
||||
@@ -31,14 +34,32 @@ class Tiktok::Client
|
||||
json['data']['download_url']
|
||||
end
|
||||
|
||||
def image_send_capable?(conversation_id, conversation_type: 'SINGLE')
|
||||
endpoint = "#{api_base_url}/business/message/capabilities/get/"
|
||||
headers = { 'Access-Token': access_token }
|
||||
query = {
|
||||
business_id: business_id,
|
||||
conversation_id: conversation_id,
|
||||
conversation_type: conversation_type,
|
||||
capability_types: ['IMAGE_SEND'].to_json
|
||||
}
|
||||
|
||||
response = HTTParty.get(endpoint, query: query, headers: headers)
|
||||
json = process_json_response(response, 'Failed to fetch TikTok message capabilities')
|
||||
capabilities = json.dig('data', 'capability_infos') || []
|
||||
image_send = capabilities.find { |capability| capability['capability_type'] == 'IMAGE_SEND' }
|
||||
|
||||
image_send&.[]('capability_result') == true
|
||||
end
|
||||
|
||||
def send_text_message(conversation_id, text, referenced_message_id: nil)
|
||||
send_message(conversation_id, 'TEXT', text, referenced_message_id: referenced_message_id)
|
||||
end
|
||||
|
||||
def send_media_message(conversation_id, attachment, referenced_message_id: nil)
|
||||
def send_media_message(conversation_id, attachment)
|
||||
# As of now, only IMAGE media type is supported
|
||||
media_id = upload_media(attachment.file, 'IMAGE')
|
||||
send_message(conversation_id, 'IMAGE', media_id, referenced_message_id: referenced_message_id)
|
||||
media_id = upload_media(attachment.file.blob, 'IMAGE')
|
||||
send_message(conversation_id, 'IMAGE', media_id)
|
||||
end
|
||||
|
||||
private
|
||||
@@ -69,31 +90,39 @@ class Tiktok::Client
|
||||
json['data']['message']['message_id']
|
||||
end
|
||||
|
||||
def upload_media(file, media_type = 'IMAGE')
|
||||
def upload_media(blob, media_type = 'IMAGE')
|
||||
endpoint = "#{api_base_url}/business/message/media/upload/"
|
||||
headers = { 'Access-Token': access_token, 'Content-Type': 'multipart/form-data' }
|
||||
|
||||
file.open do |temp_file|
|
||||
body = {
|
||||
blob.open do |temp_file|
|
||||
temp_file.rewind
|
||||
payload = {
|
||||
business_id: business_id,
|
||||
media_type: media_type,
|
||||
file: temp_file
|
||||
file: Faraday::Multipart::FilePart.new(temp_file, blob.content_type || 'application/octet-stream', blob.filename.to_s)
|
||||
}
|
||||
|
||||
response = HTTParty.post(endpoint, body: body, headers: headers)
|
||||
response = multipart_connection.post(endpoint, payload) do |request|
|
||||
request.headers['Access-Token'] = access_token
|
||||
end
|
||||
json = process_json_response(response, 'Failed to upload TikTok media')
|
||||
json['data']['media_id']
|
||||
end
|
||||
end
|
||||
|
||||
def multipart_connection
|
||||
@multipart_connection ||= Faraday.new do |faraday|
|
||||
faraday.request :multipart
|
||||
end
|
||||
end
|
||||
|
||||
def api_base_url
|
||||
"https://business-api.tiktok.com/open_api/#{GlobalConfigService.load('TIKTOK_API_VERSION', 'v1.3')}"
|
||||
end
|
||||
|
||||
def process_json_response(response, error_prefix)
|
||||
unless response.success?
|
||||
Rails.logger.error "#{error_prefix}. Status: #{response.code}, Body: #{response.body}"
|
||||
raise "#{response.code}: #{response.body}"
|
||||
Rails.logger.error "#{error_prefix}. Status: #{response_status(response)}, Body: #{response.body}"
|
||||
raise "#{response_status(response)}: #{response.body}"
|
||||
end
|
||||
|
||||
res = JSON.parse(response.body)
|
||||
@@ -101,4 +130,8 @@ class Tiktok::Client
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def response_status(response)
|
||||
response.respond_to?(:code) ? response.code : response.status
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,14 +48,26 @@ module Tiktok::MessagingHelpers
|
||||
inbox_id: channel.inbox.id,
|
||||
contact_id: contact_inbox.contact.id,
|
||||
contact_inbox_id: contact_inbox.id,
|
||||
additional_attributes: conversation_additional_attributes(tt_conversation_id)
|
||||
additional_attributes: conversation_additional_attributes(channel, tt_conversation_id)
|
||||
}
|
||||
end
|
||||
|
||||
def conversation_additional_attributes(tt_conversation_id)
|
||||
{
|
||||
conversation_id: tt_conversation_id
|
||||
}
|
||||
def conversation_additional_attributes(channel, tt_conversation_id)
|
||||
attributes = { conversation_id: tt_conversation_id }
|
||||
capabilities = tiktok_conversation_capabilities(channel, tt_conversation_id)
|
||||
attributes[:tiktok_capabilities] = capabilities if capabilities.present?
|
||||
attributes
|
||||
end
|
||||
|
||||
def tiktok_conversation_capabilities(channel, tt_conversation_id)
|
||||
image_send = tiktok_client(channel).image_send_capable?(tt_conversation_id)
|
||||
{ image_send: image_send, updated_at: Time.current.iso8601 }
|
||||
rescue StandardError => e
|
||||
Rails.logger.error(
|
||||
'Failed to fetch TikTok conversation capabilities ' \
|
||||
"for tt_conversation_id=#{tt_conversation_id}, business_id=#{channel.business_id}: #{e.class}: #{e.message}"
|
||||
)
|
||||
{}
|
||||
end
|
||||
|
||||
def find_message(tt_conversation_id, tt_message_id)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
class Tiktok::SendOnTiktokService < Base::SendOnChannelService
|
||||
SUPPORTED_IMAGE_CONTENT_TYPES = %w[image/jpeg image/png].freeze
|
||||
MAX_IMAGE_SIZE = 3.megabytes
|
||||
|
||||
private
|
||||
|
||||
def channel_class
|
||||
@@ -18,8 +21,22 @@ class Tiktok::SendOnTiktokService < Base::SendOnChannelService
|
||||
|
||||
def validate_message_support!
|
||||
return unless message.attachments.any?
|
||||
|
||||
raise 'Sending attachments with text is not supported on TikTok.' if message.outgoing_content.present?
|
||||
raise 'Sending multiple attachments in a single TikTok message is not supported.' unless message.attachments.one?
|
||||
|
||||
validate_attachment_support!(message.attachments.first)
|
||||
end
|
||||
|
||||
def validate_attachment_support!(attachment)
|
||||
raise 'Sending image attachments is not supported for this TikTok conversation.' unless image_send_capable?
|
||||
raise 'Only image attachments are supported on TikTok.' unless attachment.image?
|
||||
raise 'TikTok supports only JPG and PNG images.' unless SUPPORTED_IMAGE_CONTENT_TYPES.include?(attachment.file.content_type)
|
||||
raise 'TikTok image attachments must be smaller than 3 MB.' if attachment.file.byte_size > MAX_IMAGE_SIZE
|
||||
end
|
||||
|
||||
def image_send_capable?
|
||||
message.conversation.additional_attributes.dig('tiktok_capabilities', 'image_send') != false
|
||||
end
|
||||
|
||||
def send_message
|
||||
@@ -27,7 +44,7 @@ class Tiktok::SendOnTiktokService < Base::SendOnChannelService
|
||||
tt_referenced_message_id = message.content_attributes['in_reply_to_external_id']
|
||||
|
||||
if message.attachments.any?
|
||||
tiktok_client.send_media_message(tt_conversation_id, message.attachments.first, referenced_message_id: tt_referenced_message_id)
|
||||
tiktok_client.send_media_message(tt_conversation_id, message.attachments.first)
|
||||
else
|
||||
tiktok_client.send_text_message(tt_conversation_id, message.outgoing_content, referenced_message_id: tt_referenced_message_id)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Tiktok::Client do
|
||||
let(:client) { described_class.new(business_id: 'biz-123', access_token: 'token-123') }
|
||||
let(:response) { instance_double(HTTParty::Response) }
|
||||
|
||||
describe '#image_send_capable?' do
|
||||
before do
|
||||
allow(HTTParty).to receive(:get).and_return(response)
|
||||
allow(GlobalConfigService).to receive(:load).with('TIKTOK_API_VERSION', 'v1.3').and_return('v1.3')
|
||||
end
|
||||
|
||||
it 'returns true when IMAGE_SEND capability is enabled' do
|
||||
allow(client).to receive(:process_json_response).with(
|
||||
response,
|
||||
'Failed to fetch TikTok message capabilities'
|
||||
).and_return(
|
||||
{
|
||||
'data' => {
|
||||
'capability_infos' => [
|
||||
{ 'capability_type' => 'IMAGE_SEND', 'capability_result' => true }
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
result = client.image_send_capable?('tt-conv-1')
|
||||
|
||||
expect(result).to be(true)
|
||||
expect(HTTParty).to have_received(:get).with(
|
||||
'https://business-api.tiktok.com/open_api/v1.3/business/message/capabilities/get/',
|
||||
query: {
|
||||
business_id: 'biz-123',
|
||||
conversation_id: 'tt-conv-1',
|
||||
conversation_type: 'SINGLE',
|
||||
capability_types: '["IMAGE_SEND"]'
|
||||
},
|
||||
headers: { 'Access-Token': 'token-123' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns false when IMAGE_SEND capability is not enabled' do
|
||||
allow(client).to receive(:process_json_response).with(
|
||||
response,
|
||||
'Failed to fetch TikTok message capabilities'
|
||||
).and_return(
|
||||
{
|
||||
'data' => {
|
||||
'capability_infos' => [
|
||||
{ 'capability_type' => 'IMAGE_SEND', 'capability_result' => false }
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
result = client.image_send_capable?('tt-conv-1')
|
||||
|
||||
expect(result).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#upload_media' do
|
||||
let(:connection) { instance_double(Faraday::Connection) }
|
||||
let(:request) { instance_double(Faraday::Request, headers: {}) }
|
||||
let(:response) { instance_double(Faraday::Response, success?: true, body: response_body) }
|
||||
let(:response_body) do
|
||||
{
|
||||
code: 0,
|
||||
message: 'OK',
|
||||
data: { media_id: 'media-123' }
|
||||
}.to_json
|
||||
end
|
||||
let(:blob) do
|
||||
instance_double(
|
||||
ActiveStorage::Blob,
|
||||
content_type: 'image/png',
|
||||
filename: ActiveStorage::Filename.new('avatar.png')
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(GlobalConfigService).to receive(:load).with('TIKTOK_API_VERSION', 'v1.3').and_return('v1.3')
|
||||
allow(Faraday).to receive(:new).and_return(connection)
|
||||
allow(blob).to receive(:open) do |&block|
|
||||
File.open(Rails.root.join('spec/assets/avatar.png'), 'rb', &block)
|
||||
end
|
||||
end
|
||||
|
||||
it 'posts media upload with access token header' do
|
||||
captured_endpoint = nil
|
||||
allow(connection).to receive(:post) do |endpoint, _payload, &block|
|
||||
captured_endpoint = endpoint
|
||||
block.call(request)
|
||||
response
|
||||
end
|
||||
|
||||
media_id = client.send(:upload_media, blob)
|
||||
|
||||
expect(media_id).to eq('media-123')
|
||||
expect(captured_endpoint).to eq('https://business-api.tiktok.com/open_api/v1.3/business/message/media/upload/')
|
||||
expect(request.headers['Access-Token']).to eq('token-123')
|
||||
end
|
||||
|
||||
it 'uploads media as a multipart file with filename and content type' do
|
||||
captured_payload = nil
|
||||
allow(connection).to receive(:post) do |_endpoint, payload, &block|
|
||||
captured_payload = payload
|
||||
block.call(request)
|
||||
response
|
||||
end
|
||||
|
||||
client.send(:upload_media, blob)
|
||||
|
||||
expect(captured_payload[:business_id]).to eq('biz-123')
|
||||
expect(captured_payload[:media_type]).to eq('IMAGE')
|
||||
expect(captured_payload[:file]).to be_a(Faraday::Multipart::FilePart)
|
||||
expect(captured_payload[:file].content_type).to eq('image/png')
|
||||
expect(captured_payload[:file].original_filename).to eq('avatar.png')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#send_media_message' do
|
||||
let(:file) { Struct.new(:blob).new('blob') }
|
||||
let(:attachment) { instance_double(Attachment, file: file) }
|
||||
|
||||
it 'sends image messages' do
|
||||
allow(client).to receive(:upload_media).with('blob', 'IMAGE').and_return('media-123')
|
||||
allow(client).to receive(:send_message).and_return('tt-msg-123')
|
||||
|
||||
message_id = client.send_media_message('tt-conv-1', attachment)
|
||||
|
||||
expect(message_id).to eq('tt-msg-123')
|
||||
expect(client).to have_received(:send_message).with('tt-conv-1', 'IMAGE', 'media-123')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,10 +6,11 @@ RSpec.describe Tiktok::MessageService do
|
||||
let(:inbox) { channel.inbox }
|
||||
let(:contact) { create(:contact, account: account) }
|
||||
let(:contact_inbox) { create(:contact_inbox, inbox: inbox, contact: contact, source_id: 'tt-conv-1') }
|
||||
let(:tiktok_client) { instance_double(Tiktok::Client, image_send_capable?: true) }
|
||||
let(:text_content) do
|
||||
{
|
||||
type: 'text',
|
||||
message_id: 'tt-msg-lock',
|
||||
message_id: 'tt-msg-1',
|
||||
timestamp: 1_700_000_000_000,
|
||||
conversation_id: 'tt-conv-1',
|
||||
text: { body: 'Hello from TikTok' },
|
||||
@@ -20,6 +21,10 @@ RSpec.describe Tiktok::MessageService do
|
||||
}.deep_symbolize_keys
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Tiktok::Client).to receive(:new).and_return(tiktok_client)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject(:perform_text_message) do
|
||||
service = described_class.new(channel: channel, content: current_content)
|
||||
@@ -30,20 +35,8 @@ RSpec.describe Tiktok::MessageService do
|
||||
let(:current_content) { text_content }
|
||||
|
||||
it 'creates an incoming text message' do
|
||||
content = {
|
||||
type: 'text',
|
||||
message_id: 'tt-msg-1',
|
||||
timestamp: 1_700_000_000_000,
|
||||
conversation_id: 'tt-conv-1',
|
||||
text: { body: 'Hello from TikTok' },
|
||||
from: 'Alice',
|
||||
from_user: { id: 'user-1' },
|
||||
to: 'Biz',
|
||||
to_user: { id: 'biz-123' }
|
||||
}.deep_symbolize_keys
|
||||
|
||||
expect do
|
||||
service = described_class.new(channel: channel, content: content)
|
||||
service = described_class.new(channel: channel, content: text_content)
|
||||
allow(service).to receive(:create_contact_inbox).and_return(contact_inbox)
|
||||
service.perform
|
||||
end.to change(Message, :count).by(1)
|
||||
@@ -57,6 +50,18 @@ RSpec.describe Tiktok::MessageService do
|
||||
expect(message.content_attributes['is_unsupported']).to be_nil
|
||||
end
|
||||
|
||||
it 'stores TikTok conversation capabilities when creating a new conversation' do
|
||||
service = described_class.new(channel: channel, content: text_content)
|
||||
allow(service).to receive(:create_contact_inbox).and_return(contact_inbox)
|
||||
|
||||
service.perform
|
||||
|
||||
message = Message.last
|
||||
expect(message.conversation.additional_attributes.dig('tiktok_capabilities', 'image_send')).to be(true)
|
||||
expect(message.conversation.additional_attributes.dig('tiktok_capabilities', 'updated_at')).to be_present
|
||||
expect(tiktok_client).to have_received(:image_send_capable?).with('tt-conv-1')
|
||||
end
|
||||
|
||||
it 'creates an incoming unsupported message for non-supported types' do
|
||||
content = {
|
||||
type: 'sticker',
|
||||
@@ -135,6 +140,30 @@ RSpec.describe Tiktok::MessageService do
|
||||
tempfile.close!
|
||||
end
|
||||
|
||||
it 'creates a conversation even when capability lookup fails' do
|
||||
allow(tiktok_client).to receive(:image_send_capable?).and_raise('TikTok capability API error')
|
||||
|
||||
content = {
|
||||
type: 'text',
|
||||
message_id: 'tt-msg-5',
|
||||
timestamp: 1_700_000_000_000,
|
||||
conversation_id: 'tt-conv-1',
|
||||
text: { body: 'Hello with capability failure' },
|
||||
from: 'Alice',
|
||||
from_user: { id: 'user-1' },
|
||||
to: 'Biz',
|
||||
to_user: { id: 'biz-123' }
|
||||
}.deep_symbolize_keys
|
||||
|
||||
service = described_class.new(channel: channel, content: content)
|
||||
allow(service).to receive(:create_contact_inbox).and_return(contact_inbox)
|
||||
|
||||
expect { service.perform }.to change(Message, :count).by(1)
|
||||
|
||||
message = Message.last
|
||||
expect(message.conversation.additional_attributes['tiktok_capabilities']).to be_nil
|
||||
end
|
||||
|
||||
context 'when lock_to_single_conversation is enabled' do
|
||||
it 'reuses the last resolved conversation' do
|
||||
inbox.update!(lock_to_single_conversation: true)
|
||||
|
||||
@@ -48,10 +48,33 @@ RSpec.describe Tiktok::SendOnTiktokService do
|
||||
|
||||
described_class.new(message: message).perform
|
||||
|
||||
expect(tiktok_client).to have_received(:send_media_message).with('tt-conv-1', message.attachments.first, referenced_message_id: nil)
|
||||
expect(tiktok_client).to have_received(:send_media_message).with('tt-conv-1', message.attachments.first)
|
||||
expect(message.reload.source_id).to eq('tt-msg-124')
|
||||
end
|
||||
|
||||
it 'sends outgoing image message without quote metadata' do
|
||||
allow(tiktok_client).to receive(:send_media_message).and_return('tt-msg-124')
|
||||
allow(tiktok_client).to receive(:send_text_message)
|
||||
|
||||
message = build(
|
||||
:message,
|
||||
message_type: :outgoing,
|
||||
inbox: inbox,
|
||||
conversation: conversation,
|
||||
account: inbox.account,
|
||||
content: nil,
|
||||
content_attributes: { in_reply_to_external_id: 'quoted-message-id' }
|
||||
)
|
||||
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')
|
||||
message.save!
|
||||
|
||||
described_class.new(message: message).perform
|
||||
|
||||
expect(tiktok_client).to have_received(:send_media_message).with('tt-conv-1', message.attachments.first)
|
||||
expect(tiktok_client).not_to have_received(:send_text_message)
|
||||
end
|
||||
|
||||
it 'marks message as failed when sending multiple attachments' do
|
||||
allow(tiktok_client).to receive(:send_media_message)
|
||||
|
||||
@@ -67,5 +90,67 @@ RSpec.describe Tiktok::SendOnTiktokService do
|
||||
expect(Messages::StatusUpdateService).to have_received(:new).with(message, 'failed', kind_of(String))
|
||||
expect(tiktok_client).not_to have_received(:send_media_message)
|
||||
end
|
||||
|
||||
it 'marks message as failed when conversation cannot send images' do
|
||||
allow(tiktok_client).to receive(:send_media_message)
|
||||
conversation.update!(additional_attributes: { conversation_id: 'tt-conv-1', tiktok_capabilities: { image_send: false } })
|
||||
|
||||
message = build(:message, message_type: :outgoing, inbox: inbox, conversation: conversation, account: inbox.account, content: nil)
|
||||
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')
|
||||
message.save!
|
||||
|
||||
described_class.new(message: message).perform
|
||||
|
||||
expect(Messages::StatusUpdateService).to have_received(:new).with(
|
||||
message,
|
||||
'failed',
|
||||
'Sending image attachments is not supported for this TikTok conversation.'
|
||||
)
|
||||
expect(tiktok_client).not_to have_received(:send_media_message)
|
||||
end
|
||||
|
||||
it 'marks message as failed when attachment is not an image' do
|
||||
allow(tiktok_client).to receive(:send_media_message)
|
||||
|
||||
message = build(:message, message_type: :outgoing, inbox: inbox, conversation: conversation, account: inbox.account, content: nil)
|
||||
attachment = message.attachments.new(account_id: message.account_id, file_type: :file)
|
||||
attachment.file.attach(io: Rails.root.join('spec/assets/contacts.csv').open, filename: 'contacts.csv', content_type: 'text/csv')
|
||||
message.save!
|
||||
|
||||
described_class.new(message: message).perform
|
||||
|
||||
expect(Messages::StatusUpdateService).to have_received(:new).with(message, 'failed', 'Only image attachments are supported on TikTok.')
|
||||
expect(tiktok_client).not_to have_received(:send_media_message)
|
||||
end
|
||||
|
||||
it 'marks message as failed when image format is unsupported' do
|
||||
allow(tiktok_client).to receive(:send_media_message)
|
||||
|
||||
message = build(:message, message_type: :outgoing, inbox: inbox, conversation: conversation, account: inbox.account, content: nil)
|
||||
attachment = message.attachments.new(account_id: message.account_id, file_type: :image)
|
||||
attachment.file.attach(io: Rails.root.join('spec/assets/contacts.csv').open, filename: 'contacts.csv', content_type: 'text/csv')
|
||||
message.save!
|
||||
|
||||
described_class.new(message: message).perform
|
||||
|
||||
expect(Messages::StatusUpdateService).to have_received(:new).with(message, 'failed', 'TikTok supports only JPG and PNG images.')
|
||||
expect(tiktok_client).not_to have_received(:send_media_message)
|
||||
end
|
||||
|
||||
it 'marks message as failed when image is larger than 3 MB' do
|
||||
allow(tiktok_client).to receive(:send_media_message)
|
||||
|
||||
message = build(:message, message_type: :outgoing, inbox: inbox, conversation: conversation, account: inbox.account, content: nil)
|
||||
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')
|
||||
message.save!
|
||||
allow(message.attachments.first.file).to receive(:byte_size).and_return(4.megabytes)
|
||||
|
||||
described_class.new(message: message).perform
|
||||
|
||||
expect(Messages::StatusUpdateService).to have_received(:new).with(message, 'failed', 'TikTok image attachments must be smaller than 3 MB.')
|
||||
expect(tiktok_client).not_to have_received(:send_media_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user