diff --git a/spec/builders/messages/forwarded_message_builder_spec.rb b/spec/builders/messages/forwarded_message_builder_spec.rb
new file mode 100644
index 000000000..2babdd6e2
--- /dev/null
+++ b/spec/builders/messages/forwarded_message_builder_spec.rb
@@ -0,0 +1,323 @@
+require 'rails_helper'
+
+RSpec.describe Messages::ForwardedMessageBuilder do
+ let(:account) { create(:account) }
+ let(:inbox) { create(:inbox, account: account) }
+ let(:conversation) { create(:conversation, inbox: inbox, account: account) }
+ let(:user) { create(:user, account: account) }
+
+ # Create a message with email data
+ let(:forwarded_message) do
+ create(:message, conversation: conversation, account: account,
+ content_attributes: {
+ email: {
+ 'from' => ['sender@example.com'],
+ 'to' => ['recipient@example.com'],
+ 'subject' => 'Test Subject',
+ 'date' => '2025-04-29T14:29:07+05:30',
+ 'html_content' => {
+ 'full' => '
HTML content
',
+ 'quoted' => 'HTML content',
+ 'reply' => 'HTML content'
+ },
+ 'text_content' => {
+ 'full' => 'Text content',
+ 'quoted' => 'Text content',
+ 'reply' => 'Text content'
+ }
+ }
+ })
+ end
+
+ # Create a message with no email data
+ let(:regular_message) { create(:message, conversation: conversation, account: account) }
+
+ describe '#perform' do
+ context 'when message_id is nil' do
+ it 'returns an empty hash' do
+ builder = described_class.new(nil)
+ expect(builder.perform).to eq({})
+ end
+ end
+
+ context 'when forwarded message is not found' do
+ it 'returns basic attributes' do
+ builder = described_class.new(999)
+ expect(builder.perform).to eq(content_attributes: { forwarded_message_id: 999 })
+ end
+ end
+
+ context 'when forwarded message has no email data' do
+ it 'returns basic attributes' do
+ builder = described_class.new(regular_message.id)
+ expect(builder.perform).to eq(content_attributes: { forwarded_message_id: regular_message.id })
+ end
+ end
+
+ context 'when forwarded message has email data' do
+ it 'returns forwarded attributes' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.perform
+
+ expect(result).to be_a(Hash)
+ expect(result[:content_attributes]).to be_present
+ expect(result[:content_attributes][:forwarded_message_id]).to eq(forwarded_message.id)
+ expect(result[:content_attributes][:email]).to be_present
+ end
+ end
+ end
+
+ describe '#formatted_content' do
+ context 'when forwarded message has no email data' do
+ it 'returns original content' do
+ builder = described_class.new(regular_message.id)
+ expect(builder.formatted_content('Original')).to eq('Original')
+ end
+ end
+
+ context 'when forwarded message has email data' do
+ it 'returns formatted content with header and body' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.formatted_content('Original')
+
+ expect(result).to include('Original')
+ expect(result).to include('---------- Forwarded message ---------')
+ expect(result).to include('From:')
+ expect(result).to include('Date:')
+ expect(result).to include('Subject: Test Subject')
+ expect(result).to include('Text content') # Should include the text content
+ end
+ end
+ end
+
+ describe '#formatted_html_content' do
+ context 'when forwarded message has no email data' do
+ it 'returns original content' do
+ builder = described_class.new(regular_message.id)
+ expect(builder.formatted_html_content('Original')).to eq('Original')
+ end
+ end
+
+ context 'when forwarded message has email data' do
+ it 'returns formatted HTML content with header and body' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.formatted_html_content('**Original**')
+
+ expect(result).to include('Original') # Markdown converted to HTML
+ expect(result).to include('---------- Forwarded message ---------')
+ expect(result).to include('From:')
+ expect(result).to include('Date:')
+ expect(result).to include('Subject: Test Subject')
+ expect(result).to include('HTML content
') # Should include the HTML content
+ end
+ end
+ end
+
+ describe '#forwarded_email_data' do
+ context 'when forwarded message has no email data' do
+ it 'returns empty hash' do
+ builder = described_class.new(regular_message.id)
+ expect(builder.forwarded_email_data('Original')).to eq({})
+ end
+ end
+
+ context 'when forwarded message has email data' do
+ it 'returns complete email data structure' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.forwarded_email_data('**Original**')
+
+ expect(result).to be_a(Hash)
+ expect(result['html_content']).to be_present
+ expect(result['text_content']).to be_present
+
+ # Check HTML content
+ expect(result['html_content']['quoted']).to eq('Original') # Markdown stripped
+ expect(result['html_content']['full']).to include('Original') # HTML formatted
+
+ # Check text content
+ expect(result['text_content']['quoted']).to eq('**Original**') # Original text preserved
+ expect(result['text_content']['full']).to include('---------- Forwarded message ---------')
+ end
+ end
+
+ context 'when handling multipart emails' do
+ let(:multipart_message) do
+ create(:message, conversation: conversation, account: account,
+ content_attributes: {
+ email: {
+ 'from' => ['sender@example.com'],
+ 'to' => ['recipient@example.com'],
+ 'subject' => 'Multipart Test',
+ 'date' => '2025-04-29T14:29:07+05:30',
+ 'html_content' => {
+ 'full' => 'HTML formatted content
'
+ },
+ 'text_content' => {
+ 'full' => 'Plain text content'
+ }
+ }
+ })
+ end
+
+ it 'preserves both HTML and text parts' do
+ builder = described_class.new(multipart_message.id)
+ result = builder.forwarded_email_data('New message')
+
+ expect(result['html_content']['full']).to include('HTML formatted content
')
+ expect(result['text_content']['full']).to include('Plain text content')
+ end
+ end
+ end
+
+ describe '#convert_markdown_to_html' do
+ it 'converts bold markdown to HTML' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:convert_markdown_to_html, '**Bold Text**')
+ expect(result).to eq('Bold Text')
+ end
+
+ it 'converts italic markdown to HTML' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:convert_markdown_to_html, '*Italic Text*')
+ expect(result).to eq('Italic Text')
+ end
+
+ it 'converts underscore italic markdown to HTML' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:convert_markdown_to_html, '_Italic Text_')
+ expect(result).to eq('Italic Text')
+ end
+
+ it 'handles empty text' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:convert_markdown_to_html, '')
+ expect(result).to eq('')
+ end
+
+ it 'handles nil text' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:convert_markdown_to_html, nil)
+ expect(result).to eq('')
+ end
+ end
+
+ describe '#strip_markdown' do
+ it 'strips bold markdown' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:strip_markdown, '**Bold Text**')
+ expect(result).to eq('Bold Text')
+ end
+
+ it 'strips italic markdown' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:strip_markdown, '*Italic Text*')
+ expect(result).to eq('Italic Text')
+ end
+
+ it 'strips underscore italic markdown' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:strip_markdown, '_Italic Text_')
+ expect(result).to eq('Italic Text')
+ end
+
+ it 'handles empty text' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:strip_markdown, '')
+ expect(result).to eq('')
+ end
+
+ it 'handles nil text' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:strip_markdown, nil)
+ expect(result).to eq('')
+ end
+ end
+
+ describe '#extract_email' do
+ it 'extracts email from format "Name "' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:extract_email, 'John Doe ')
+ expect(result).to eq('john@example.com')
+ end
+
+ it 'returns plain email as-is' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:extract_email, 'john@example.com')
+ expect(result).to eq('john@example.com')
+ end
+
+ it 'handles empty string' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:extract_email, '')
+ expect(result).to eq('')
+ end
+
+ it 'handles nil' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:extract_email, nil)
+ expect(result).to eq('')
+ end
+ end
+
+ describe '#parse_from_field' do
+ it 'formats "Name " correctly' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:parse_from_field, 'John Doe ')
+ expect(result).to eq('John Doe ')
+ end
+
+ it 'handles plain email' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:parse_from_field, 'john@example.com')
+ expect(result).to eq('john@example.com')
+ end
+
+ it 'handles extra spaces' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:parse_from_field, 'John Doe < john@example.com >')
+ expect(result).to eq('John Doe ')
+ end
+
+ it 'handles empty string' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:parse_from_field, '')
+ expect(result).to eq('')
+ end
+
+ it 'handles nil' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:parse_from_field, nil)
+ expect(result).to eq('')
+ end
+ end
+
+ describe '#format_date_string' do
+ it 'formats the date in a readable format' do
+ builder = described_class.new(forwarded_message.id)
+ # NOTE: We don't test the exact formatted output since it uses DateTime.now
+ # which would be different for each test run
+ result = builder.send(:format_date_string, '2025-04-29T14:29:07+05:30')
+ expect(result).to be_a(String)
+ expect(result).to match(/\w{3}, \w{3} \d+, \d{4} at \d+:\d+ [AP]M/)
+ end
+
+ it 'handles empty string' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:format_date_string, '')
+ expect(result).to eq('')
+ end
+
+ it 'handles nil' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:format_date_string, nil)
+ expect(result).to eq('')
+ end
+
+ it 'returns current date formatted regardless of input' do
+ builder = described_class.new(forwarded_message.id)
+ result = builder.send(:format_date_string, 'Invalid Date')
+
+ expect(result).to match(/\w{3}, \w{3} \d+, \d{4} at \d+:\d+ [AP]M/)
+ end
+ end
+end
diff --git a/spec/builders/messages/message_builder_spec.rb b/spec/builders/messages/message_builder_spec.rb
index 891f8eb02..68e38f9c7 100644
--- a/spec/builders/messages/message_builder_spec.rb
+++ b/spec/builders/messages/message_builder_spec.rb
@@ -181,4 +181,164 @@ describe Messages::MessageBuilder do
end
end
end
+
+ describe '#perform with forwarded messages' do
+ let(:email_channel) { create(:channel_email, account: account) }
+ let(:email_inbox) { email_channel.inbox }
+ let(:email_conversation) { create(:conversation, inbox: email_inbox, account: account) }
+
+ # Create a message with email data to be forwarded
+ let(:forwarded_message) do
+ create(:message, conversation: email_conversation, account: account,
+ content_attributes: {
+ email: {
+ 'from' => ['sender@example.com'],
+ 'to' => ['recipient@example.com'],
+ 'subject' => 'Test Subject',
+ 'date' => '2025-04-29T14:29:07+05:30',
+ 'html_content' => {
+ 'full' => 'HTML content
',
+ 'quoted' => 'HTML content',
+ 'reply' => 'HTML content'
+ },
+ 'text_content' => {
+ 'full' => 'Text content',
+ 'quoted' => 'Text content',
+ 'reply' => 'Text content'
+ }
+ }
+ })
+ end
+
+ context 'when forwarding a message from a non-email inbox' do
+ let(:params) do
+ ActionController::Parameters.new({
+ content: 'Forwarded message:',
+ content_attributes: { forwarded_message_id: forwarded_message.id }
+ })
+ end
+
+ it 'includes the forwarded message ID in content_attributes' do
+ message = described_class.new(user, conversation, params).perform
+ expect(message.content_attributes[:forwarded_message_id]).to eq(forwarded_message.id)
+ end
+
+ it 'updates the content with the forwarded message text' do
+ message = described_class.new(user, conversation, params).perform
+ expect(message.content).to include('Forwarded message:')
+ expect(message.content).to include('---------- Forwarded message ---------')
+ end
+ end
+
+ context 'when forwarding a message from an email inbox' do
+ let(:params) do
+ ActionController::Parameters.new({
+ content: 'Forwarded message:',
+ content_attributes: { forwarded_message_id: forwarded_message.id }
+ })
+ end
+
+ let(:email_conversation_target) { create(:conversation, inbox: email_inbox, account: account) }
+
+ it 'includes the forwarded email data in content_attributes' do
+ message = described_class.new(user, email_conversation_target, params).perform
+
+ expect(message.content_attributes[:forwarded_message_id]).to eq(forwarded_message.id)
+ expect(message.content_attributes[:email]).to be_present
+ expect(message.content_attributes[:email]['html_content']).to be_present
+ expect(message.content_attributes[:email]['text_content']).to be_present
+ end
+
+ it 'preserves the HTML content in the forwarded email' do
+ message = described_class.new(user, email_conversation_target, params).perform
+
+ html_content = message.content_attributes[:email]['html_content']['full']
+ expect(html_content).to include('HTML content
')
+ end
+
+ it 'preserves the text content in the forwarded email' do
+ message = described_class.new(user, email_conversation_target, params).perform
+
+ text_content = message.content_attributes[:email]['text_content']['full']
+ expect(text_content).to include('Text content')
+ end
+ end
+
+ context 'when forwarding a message with markdown content' do
+ let(:params) do
+ ActionController::Parameters.new({
+ content: '**Bold text** and _italic text_',
+ content_attributes: { forwarded_message_id: forwarded_message.id }
+ })
+ end
+
+ it 'converts markdown to HTML in the HTML content' do
+ message = described_class.new(user, email_conversation, params).perform
+
+ html_content = message.content_attributes[:email]['html_content']['full']
+ expect(html_content).to include('Bold text')
+ expect(html_content).to include('italic text')
+ end
+
+ it 'preserves markdown in the text content' do
+ message = described_class.new(user, email_conversation, params).perform
+
+ text_content = message.content_attributes[:email]['text_content']['full']
+ expect(text_content).to include('**Bold text**')
+ expect(text_content).to include('_italic text_')
+ end
+ end
+
+ context 'when forwarding a message with no email data' do
+ let(:regular_message) { create(:message, conversation: conversation, account: account) }
+
+ let(:params) do
+ ActionController::Parameters.new({
+ content: 'Forwarding a regular message:',
+ content_attributes: { forwarded_message_id: regular_message.id }
+ })
+ end
+
+ it 'includes the forwarded message ID and empty email data' do
+ message = described_class.new(user, email_conversation, params).perform
+
+ expect(message.content_attributes[:forwarded_message_id]).to eq(regular_message.id)
+ expect(message.content_attributes[:email]).to eq({})
+ end
+ end
+
+ context 'with multipart email content' do
+ let(:multipart_message) do
+ create(:message, conversation: email_conversation, account: account,
+ content_attributes: {
+ email: {
+ 'from' => ['sender@example.com'],
+ 'to' => ['recipient@example.com'],
+ 'subject' => 'Multipart Test',
+ 'date' => '2025-04-29T14:29:07+05:30',
+ 'html_content' => {
+ 'full' => 'HTML formatted content
'
+ },
+ 'text_content' => {
+ 'full' => 'Plain text content'
+ }
+ }
+ })
+ end
+
+ let(:params) do
+ ActionController::Parameters.new({
+ content: 'Forwarding multipart email:',
+ content_attributes: { forwarded_message_id: multipart_message.id }
+ })
+ end
+
+ it 'preserves both HTML and text parts in the forwarded email' do
+ message = described_class.new(user, email_conversation, params).perform
+
+ expect(message.content_attributes[:email]['html_content']['full']).to include('HTML formatted content
')
+ expect(message.content_attributes[:email]['text_content']['full']).to include('Plain text content')
+ end
+ end
+ end
end