mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-29 10:12:34 +00:00
This PR adds sending custom HTML content in outgoing email messages
through Chatwoot's Email channels, while maintaining backward
compatibility with existing markdown rendering.
### API Usage
**Endpoint:** `POST
/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages`
```json
{
"content": "Fallback text content",
"email_html_content": "<div><h1>Welcome!</h1><p>This is <strong>custom HTML</strong></p></div>"
}
```
---------
Co-authored-by: Muhsin <muhsinkeramam@gmail.com>
242 lines
10 KiB
Ruby
242 lines
10 KiB
Ruby
require 'rails_helper'
|
|
|
|
describe Messages::MessageBuilder do
|
|
subject(:message_builder) { described_class.new(user, conversation, params).perform }
|
|
|
|
let(:account) { create(:account) }
|
|
let(:user) { create(:user, account: account) }
|
|
let(:inbox) { create(:inbox, account: account) }
|
|
let(:inbox_member) { create(:inbox_member, inbox: inbox, account: account) }
|
|
let(:conversation) { create(:conversation, inbox: inbox, account: account) }
|
|
let(:message_for_reply) { create(:message, conversation: conversation) }
|
|
let(:params) do
|
|
ActionController::Parameters.new({
|
|
content: 'test'
|
|
})
|
|
end
|
|
|
|
describe '#perform' do
|
|
it 'creates a message' do
|
|
message = message_builder
|
|
expect(message.content).to eq params[:content]
|
|
end
|
|
end
|
|
|
|
describe '#content_attributes' do
|
|
context 'when content_attributes is a JSON string' do
|
|
let(:params) do
|
|
ActionController::Parameters.new({
|
|
content: 'test',
|
|
content_attributes: "{\"in_reply_to\":#{message_for_reply.id}}"
|
|
})
|
|
end
|
|
|
|
it 'parses content_attributes from JSON string' do
|
|
message = described_class.new(user, conversation, params).perform
|
|
expect(message.content_attributes).to include(in_reply_to: message_for_reply.id)
|
|
end
|
|
end
|
|
|
|
context 'when content_attributes is a hash' do
|
|
let(:params) do
|
|
ActionController::Parameters.new({
|
|
content: 'test',
|
|
content_attributes: { in_reply_to: message_for_reply.id }
|
|
})
|
|
end
|
|
|
|
it 'uses content_attributes as provided' do
|
|
message = described_class.new(user, conversation, params).perform
|
|
expect(message.content_attributes).to include(in_reply_to: message_for_reply.id)
|
|
end
|
|
end
|
|
|
|
context 'when content_attributes is absent' do
|
|
let(:params) do
|
|
ActionController::Parameters.new({ content: 'test' })
|
|
end
|
|
|
|
it 'defaults to an empty hash' do
|
|
message = message_builder
|
|
expect(message.content_attributes).to eq({})
|
|
end
|
|
end
|
|
|
|
context 'when content_attributes is nil' do
|
|
let(:params) do
|
|
ActionController::Parameters.new({
|
|
content: 'test',
|
|
content_attributes: nil
|
|
})
|
|
end
|
|
|
|
it 'defaults to an empty hash' do
|
|
message = message_builder
|
|
expect(message.content_attributes).to eq({})
|
|
end
|
|
end
|
|
|
|
context 'when content_attributes is an invalid JSON string' do
|
|
let(:params) do
|
|
ActionController::Parameters.new({
|
|
content: 'test',
|
|
content_attributes: 'invalid_json'
|
|
})
|
|
end
|
|
|
|
it 'defaults to an empty hash' do
|
|
message = message_builder
|
|
expect(message.content_attributes).to eq({})
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#perform when message_type is incoming' do
|
|
context 'when channel is not api' do
|
|
let(:params) do
|
|
ActionController::Parameters.new({
|
|
content: 'test',
|
|
message_type: 'incoming'
|
|
})
|
|
end
|
|
|
|
it 'creates throws error when channel is not api' do
|
|
expect { message_builder }.to raise_error 'Incoming messages are only allowed in Api inboxes'
|
|
end
|
|
end
|
|
|
|
context 'when channel is api' do
|
|
let(:channel_api) { create(:channel_api, account: account) }
|
|
let(:conversation) { create(:conversation, inbox: channel_api.inbox, account: account) }
|
|
let(:params) do
|
|
ActionController::Parameters.new({
|
|
content: 'test',
|
|
message_type: 'incoming'
|
|
})
|
|
end
|
|
|
|
it 'creates message when channel is api' do
|
|
message = message_builder
|
|
expect(message.message_type).to eq params[:message_type]
|
|
end
|
|
end
|
|
|
|
context 'when attachment messages' do
|
|
let(:params) do
|
|
ActionController::Parameters.new({
|
|
content: 'test',
|
|
attachments: [Rack::Test::UploadedFile.new('spec/assets/avatar.png', 'image/png')]
|
|
})
|
|
end
|
|
|
|
it 'creates message with attachments' do
|
|
message = message_builder
|
|
expect(message.attachments.first.file_type).to eq 'image'
|
|
end
|
|
|
|
context 'when DIRECT_UPLOAD_ENABLED' do
|
|
let(:params) do
|
|
ActionController::Parameters.new({
|
|
content: 'test',
|
|
attachments: [get_blob_for('spec/assets/avatar.png', 'image/png').signed_id]
|
|
})
|
|
end
|
|
|
|
it 'creates message with attachments' do
|
|
message = message_builder
|
|
expect(message.attachments.first.file_type).to eq 'image'
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when email channel messages' do
|
|
let!(:channel_email) { create(:channel_email, account: account) }
|
|
let(:inbox_member) { create(:inbox_member, inbox: channel_email.inbox) }
|
|
let(:conversation) { create(:conversation, inbox: channel_email.inbox, account: account) }
|
|
let(:params) do
|
|
ActionController::Parameters.new({ cc_emails: 'test_cc_mail@test.com', bcc_emails: 'test_bcc_mail@test.com' })
|
|
end
|
|
|
|
it 'creates message with content_attributes for cc and bcc email addresses' do
|
|
message = message_builder
|
|
|
|
expect(message.content_attributes[:cc_emails]).to eq [params[:cc_emails]]
|
|
expect(message.content_attributes[:bcc_emails]).to eq [params[:bcc_emails]]
|
|
end
|
|
|
|
it 'does not create message with wrong cc and bcc email addresses' do
|
|
params = ActionController::Parameters.new({ cc_emails: 'test.com', bcc_emails: 'test_bcc.com' })
|
|
expect { described_class.new(user, conversation, params).perform }.to raise_error 'Invalid email address'
|
|
end
|
|
|
|
it 'strips off whitespace before saving cc_emails and bcc_emails' do
|
|
cc_emails = ' test1@test.com , test2@test.com, test3@test.com'
|
|
bcc_emails = 'test1@test.com,test2@test.com, test3@test.com '
|
|
params = ActionController::Parameters.new({ cc_emails: cc_emails, bcc_emails: bcc_emails })
|
|
|
|
message = described_class.new(user, conversation, params).perform
|
|
|
|
expect(message.content_attributes[:cc_emails]).to eq ['test1@test.com', 'test2@test.com', 'test3@test.com']
|
|
expect(message.content_attributes[:bcc_emails]).to eq ['test1@test.com', 'test2@test.com', 'test3@test.com']
|
|
end
|
|
|
|
context 'when custom email content is provided' do
|
|
before do
|
|
account.enable_features('quoted_email_reply')
|
|
end
|
|
|
|
it 'creates message with custom HTML email content' do
|
|
params = ActionController::Parameters.new({
|
|
content: 'Regular message content',
|
|
email_html_content: '<p>Custom <strong>HTML</strong> content</p>'
|
|
})
|
|
|
|
message = described_class.new(user, conversation, params).perform
|
|
|
|
expect(message.content_attributes.dig('email', 'html_content', 'full')).to eq '<p>Custom <strong>HTML</strong> content</p>'
|
|
expect(message.content_attributes.dig('email', 'html_content', 'reply')).to eq '<p>Custom <strong>HTML</strong> content</p>'
|
|
expect(message.content_attributes.dig('email', 'text_content', 'full')).to eq 'Regular message content'
|
|
expect(message.content_attributes.dig('email', 'text_content', 'reply')).to eq 'Regular message content'
|
|
end
|
|
|
|
it 'does not process custom email content when quoted_email_reply feature is disabled' do
|
|
account.disable_features('quoted_email_reply')
|
|
params = ActionController::Parameters.new({
|
|
content: 'Regular message content',
|
|
email_html_content: '<p>Custom HTML content</p>'
|
|
})
|
|
|
|
message = described_class.new(user, conversation, params).perform
|
|
|
|
expect(message.content_attributes.dig('email', 'html_content')).to be_nil
|
|
expect(message.content_attributes.dig('email', 'text_content')).to be_nil
|
|
end
|
|
|
|
it 'does not process custom email content for private messages' do
|
|
params = ActionController::Parameters.new({
|
|
content: 'Regular message content',
|
|
email_html_content: '<p>Custom HTML content</p>',
|
|
private: true
|
|
})
|
|
|
|
message = described_class.new(user, conversation, params).perform
|
|
|
|
expect(message.content_attributes.dig('email', 'html_content')).to be_nil
|
|
expect(message.content_attributes.dig('email', 'text_content')).to be_nil
|
|
end
|
|
|
|
it 'falls back to default behavior when no custom email content is provided' do
|
|
params = ActionController::Parameters.new({
|
|
content: 'Regular **markdown** content'
|
|
})
|
|
|
|
message = described_class.new(user, conversation, params).perform
|
|
|
|
expect(message.content_attributes.dig('email', 'html_content', 'full')).to include('<strong>markdown</strong>')
|
|
expect(message.content_attributes.dig('email', 'text_content', 'full')).to eq 'Regular **markdown** content'
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|