diff --git a/app/services/whatsapp/conversation_sync_service.rb b/app/services/whatsapp/conversation_sync_service.rb index 0fe4d8b..ff7fc7f 100644 --- a/app/services/whatsapp/conversation_sync_service.rb +++ b/app/services/whatsapp/conversation_sync_service.rb @@ -322,6 +322,7 @@ def create_echo_media_attachment(message, echo_data, media_data, attachment_file message.attachments.build( file_type: file_type.to_s, + fallback_title: filename, file: { io: attachment_file, filename: filename, diff --git a/app/services/whatsapp/evolution_go_handlers/messages_upsert.rb b/app/services/whatsapp/evolution_go_handlers/messages_upsert.rb index 21dde53..5ca4d68 100644 --- a/app/services/whatsapp/evolution_go_handlers/messages_upsert.rb +++ b/app/services/whatsapp/evolution_go_handlers/messages_upsert.rb @@ -410,20 +410,20 @@ def file_content_type end def message_type_from_media - # Convert Evolution Go MediaType to message_type like Evolution v2 - media_type = @evolution_go_info[:MediaType] + media_type = @evolution_go_info&.dig(:MediaType) - case media_type - when 'image' - 'image' - when 'video' - 'video' - when 'audio', 'ptt' # PTT (push-to-talk) is audio in Evolution v2 - 'audio' - when 'document' - 'file' - else - 'file' + if media_type.blank? + struct_type = message_type + media_type = struct_type == 'file' ? 'document' : struct_type + end + + case media_type&.downcase + when 'image' then 'image' + when 'video' then 'video' + when 'audio', 'ptt' then 'audio' + when 'document', 'file' then 'file' + when 'sticker' then 'sticker' + else 'file' end end @@ -453,7 +453,7 @@ def configure_audio_metadata(attachment) end def audio_voice_note? - @evolution_go_info[:MediaType] == 'ptt' + @evolution_go_info&.dig(:MediaType) == 'ptt' end def create_attachment(attachment_file) @@ -468,7 +468,10 @@ def create_attachment(attachment_file) content_type: final_content_type ) - attachment = @message.attachments.build(file_type: file_content_type.to_s) + attachment = @message.attachments.build( + file_type: file_content_type.to_s, + fallback_title: generate_filename_with_extension + ) attachment.file.attach(blob) configure_audio_metadata(attachment) if audio_voice_note? diff --git a/app/services/whatsapp/evolution_handlers/attachment_processor.rb b/app/services/whatsapp/evolution_handlers/attachment_processor.rb index 3956df5..d44313f 100644 --- a/app/services/whatsapp/evolution_handlers/attachment_processor.rb +++ b/app/services/whatsapp/evolution_handlers/attachment_processor.rb @@ -33,7 +33,10 @@ def create_attachment(attachment_file) content_type: final_content_type ) - attachment = @message.attachments.build(file_type: file_content_type.to_s) + attachment = @message.attachments.build( + file_type: file_content_type.to_s, + fallback_title: generate_filename_with_extension + ) attachment.file.attach(blob) configure_audio_metadata(attachment) if audio_voice_note? diff --git a/app/services/whatsapp/incoming_message_notificame_service.rb b/app/services/whatsapp/incoming_message_notificame_service.rb index b3d9bbb..5d4b25a 100644 --- a/app/services/whatsapp/incoming_message_notificame_service.rb +++ b/app/services/whatsapp/incoming_message_notificame_service.rb @@ -305,6 +305,7 @@ def attach_file(message, content) message.attachments.new( file_type: file_content_type(file_type_key), + fallback_title: (File.basename(URI.parse(content[:fileUrl].to_s).path) rescue File.basename(content[:fileUrl].to_s)).presence, file: { io: io, filename: File.basename(io.path), content_type: mime_type } ) diff --git a/app/services/whatsapp/incoming_message_zapi_service.rb b/app/services/whatsapp/incoming_message_zapi_service.rb index f9ee7e9..281a17d 100644 --- a/app/services/whatsapp/incoming_message_zapi_service.rb +++ b/app/services/whatsapp/incoming_message_zapi_service.rb @@ -399,6 +399,7 @@ def attach_file(message, file_url, file_type, mime_type, filename = nil) message.attachments.new( file_type: file_content_type(file_type), + fallback_title: filename, file: { io: io, filename: filename, diff --git a/spec/services/whatsapp/evolution_go_handlers/messages_upsert_spec.rb b/spec/services/whatsapp/evolution_go_handlers/messages_upsert_spec.rb new file mode 100644 index 0000000..44e3c64 --- /dev/null +++ b/spec/services/whatsapp/evolution_go_handlers/messages_upsert_spec.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +begin + require 'rails_helper' +rescue LoadError + RSpec.describe 'Whatsapp::EvolutionGoHandlers::MessagesUpsert' do + it 'has spec scaffold ready' do + skip 'rails_helper is not available in this workspace snapshot' + end + end +end + +return unless defined?(Rails) + +RSpec.describe Whatsapp::EvolutionGoHandlers::MessagesUpsert do + let(:host_class) do + Class.new do + include Whatsapp::EvolutionGoHandlers::MessagesUpsert + + attr_writer :evolution_go_info, :evolution_go_message + + def initialize(info: nil, message: nil) + @evolution_go_info = info + @evolution_go_message = message + end + end + end + + subject(:service) { host_class.new(info: info, message: evo_message) } + + let(:evo_message) { {} } + let(:info) { nil } + + describe '#message_type_from_media' do + context 'when @evolution_go_info is nil' do + let(:info) { nil } + + context 'and message struct is videoMessage' do + let(:evo_message) { { videoMessage: {} } } + + it 'returns video' do + expect(service.send(:message_type_from_media)).to eq('video') + end + end + + context 'and message struct is imageMessage' do + let(:evo_message) { { imageMessage: {} } } + + it 'returns image' do + expect(service.send(:message_type_from_media)).to eq('image') + end + end + + context 'and message struct is documentMessage' do + let(:evo_message) { { documentMessage: {} } } + + it 'returns file' do + expect(service.send(:message_type_from_media)).to eq('file') + end + end + + context 'and message struct is audioMessage' do + let(:evo_message) { { audioMessage: {} } } + + it 'returns audio' do + expect(service.send(:message_type_from_media)).to eq('audio') + end + end + + context 'and message struct is stickerMessage' do + let(:evo_message) { { stickerMessage: {} } } + + it 'returns sticker' do + expect(service.send(:message_type_from_media)).to eq('sticker') + end + end + end + + context 'when MediaType is blank string' do + let(:info) { { MediaType: '' } } + let(:evo_message) { { videoMessage: {} } } + + it 'falls back to struct-based detection and returns video' do + expect(service.send(:message_type_from_media)).to eq('video') + end + end + + context 'when MediaType is present' do + let(:evo_message) { {} } + + it 'returns image for MediaType=image' do + service.instance_variable_set(:@evolution_go_info, { MediaType: 'image' }) + expect(service.send(:message_type_from_media)).to eq('image') + end + + it 'returns video for MediaType=video' do + service.instance_variable_set(:@evolution_go_info, { MediaType: 'video' }) + expect(service.send(:message_type_from_media)).to eq('video') + end + + it 'returns audio for MediaType=audio' do + service.instance_variable_set(:@evolution_go_info, { MediaType: 'audio' }) + expect(service.send(:message_type_from_media)).to eq('audio') + end + + it 'returns audio for MediaType=ptt' do + service.instance_variable_set(:@evolution_go_info, { MediaType: 'ptt' }) + expect(service.send(:message_type_from_media)).to eq('audio') + end + + it 'returns file for MediaType=document' do + service.instance_variable_set(:@evolution_go_info, { MediaType: 'document' }) + expect(service.send(:message_type_from_media)).to eq('file') + end + + it 'returns sticker for MediaType=sticker' do + service.instance_variable_set(:@evolution_go_info, { MediaType: 'sticker' }) + expect(service.send(:message_type_from_media)).to eq('sticker') + end + + it 'returns file for unknown MediaType' do + service.instance_variable_set(:@evolution_go_info, { MediaType: 'unknown_type' }) + expect(service.send(:message_type_from_media)).to eq('file') + end + end + end + + describe '#audio_voice_note?' do + it 'returns false without raising when @evolution_go_info is nil' do + service.instance_variable_set(:@evolution_go_info, nil) + expect { service.send(:audio_voice_note?) }.not_to raise_error + expect(service.send(:audio_voice_note?)).to be(false) + end + + it 'returns true when MediaType is ptt' do + service.instance_variable_set(:@evolution_go_info, { MediaType: 'ptt' }) + expect(service.send(:audio_voice_note?)).to be(true) + end + + it 'returns false when MediaType is audio' do + service.instance_variable_set(:@evolution_go_info, { MediaType: 'audio' }) + expect(service.send(:audio_voice_note?)).to be(false) + end + end +end diff --git a/spec/services/whatsapp/evolution_handlers/attachment_processor_spec.rb b/spec/services/whatsapp/evolution_handlers/attachment_processor_spec.rb index 6dcf6db..821b46d 100644 --- a/spec/services/whatsapp/evolution_handlers/attachment_processor_spec.rb +++ b/spec/services/whatsapp/evolution_handlers/attachment_processor_spec.rb @@ -103,10 +103,13 @@ def log_base64_error(*); end processor.create_attachment(tempfile) end - it 'builds the attachment with the resolved file_content_type' do + it 'builds the attachment with file_type and fallback_title' do allow(ActiveStorage::Blob).to receive(:create_and_upload!).and_return(fake_blob) - expect(attachments_relation).to receive(:build).with(file_type: 'image').and_return(built_attachment) + expect(attachments_relation).to receive(:build).with( + file_type: 'image', + fallback_title: 'msg-1.jpg' + ).and_return(built_attachment) processor.create_attachment(tempfile) end