From bdcb080e402d19447b94cd31fca2bf8373ee8d46 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Fri, 11 Apr 2025 19:11:29 +0530 Subject: [PATCH] feat: Handle instagram test service (#11244) This PR will handle the Instagram test events. We are using the last created Instagram channel as the test channel since we don't have any other channels for testing purposes at the time of Meta approval. https://github.com/user-attachments/assets/98302b7a-d72c-4950-9660-861a5e08d55f --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Shivam Mishra --- app/jobs/webhooks/instagram_events_job.rb | 19 +++++ app/services/instagram/test_event_service.rb | 79 +++++++++++++++++++ .../instagram_message_create_event.rb | 30 +++++++ .../instagram/test_event_service_spec.rb | 71 +++++++++++++++++ 4 files changed, 199 insertions(+) create mode 100644 app/services/instagram/test_event_service.rb create mode 100644 spec/services/instagram/test_event_service_spec.rb diff --git a/app/jobs/webhooks/instagram_events_job.rb b/app/jobs/webhooks/instagram_events_job.rb index eb08f2ebb..3db8b6ba9 100644 --- a/app/jobs/webhooks/instagram_events_job.rb +++ b/app/jobs/webhooks/instagram_events_job.rb @@ -24,6 +24,11 @@ class Webhooks::InstagramEventsJob < MutexApplicationJob private def process_single_entry(entry) + if test_event?(entry) + process_test_event(entry) + return + end + process_messages(entry) end @@ -46,6 +51,20 @@ class Webhooks::InstagramEventsJob < MutexApplicationJob messaging[:message].present? && messaging[:message][:is_echo].present? end + def test_event?(entry) + entry[:changes].present? + end + + def process_test_event(entry) + messaging = extract_messaging_from_test_event(entry) + + Instagram::TestEventService.new(messaging).perform if messaging.present? + end + + def extract_messaging_from_test_event(entry) + entry[:changes].first&.dig(:value) if entry[:changes].present? + end + def instagram_id(messaging) if agent_message_via_echo?(messaging) messaging[:sender][:id] diff --git a/app/services/instagram/test_event_service.rb b/app/services/instagram/test_event_service.rb new file mode 100644 index 000000000..6fe242494 --- /dev/null +++ b/app/services/instagram/test_event_service.rb @@ -0,0 +1,79 @@ +class Instagram::TestEventService + def initialize(messaging) + @messaging = messaging + end + + def perform + Rails.logger.info("Processing Instagram test webhook event, #{@messaging}") + + return false unless test_webhook_event? + + create_test_text + end + + private + + def test_webhook_event? + @messaging[:sender][:id] == '12334' && @messaging[:recipient][:id] == '23245' + end + + def create_test_text + # As of now, we are using the last created instagram channel as the test channel, + # since we don't have any other channel for testing purpose at the time of meta approval + channel = Channel::Instagram.last + + @inbox = ::Inbox.find_by(channel: channel) + return unless @inbox + + @contact = create_test_contact + + @conversation ||= create_test_conversation(conversation_params) + + @message = @conversation.messages.create!(test_message_params) + end + + def create_test_contact + @contact_inbox = @inbox.contact_inboxes.where(source_id: @messaging[:sender][:id]).first + unless @contact_inbox + @contact_inbox ||= @inbox.channel.create_contact_inbox( + 'sender_username', 'sender_username' + ) + end + + @contact_inbox.contact + end + + def create_test_conversation(conversation_params) + Conversation.find_by(conversation_params) || build_conversation(conversation_params) + end + + def test_message_params + { + account_id: @conversation.account_id, + inbox_id: @conversation.inbox_id, + message_type: 'incoming', + source_id: @messaging[:message][:mid], + content: @messaging[:message][:text], + sender: @contact + } + end + + def build_conversation(conversation_params) + Conversation.create!( + conversation_params.merge( + contact_inbox_id: @contact_inbox.id + ) + ) + end + + def conversation_params + { + account_id: @inbox.account_id, + inbox_id: @inbox.id, + contact_id: @contact.id, + additional_attributes: { + type: 'instagram_direct_message' + } + } + end +end diff --git a/spec/factories/instagram/instagram_message_create_event.rb b/spec/factories/instagram/instagram_message_create_event.rb index 99572d620..66d5e8a81 100644 --- a/spec/factories/instagram/instagram_message_create_event.rb +++ b/spec/factories/instagram/instagram_message_create_event.rb @@ -361,4 +361,34 @@ FactoryBot.define do end initialize_with { attributes } end + + factory :instagram_test_event, class: Hash do + entry do + [ + { + 'id': '0', + 'time': '2021-09-08T06:34:04+0000', + 'changes': [ + { + 'field': 'messages', + 'value': { + 'sender': { + 'id': '12334' + }, + 'recipient': { + 'id': '23245' + }, + 'timestamp': '1527459824', + 'message': { + 'mid': 'random_mid', + 'text': 'random_text' + } + } + } + ] + } + ] + end + initialize_with { attributes } + end end diff --git a/spec/services/instagram/test_event_service_spec.rb b/spec/services/instagram/test_event_service_spec.rb new file mode 100644 index 000000000..a783197e6 --- /dev/null +++ b/spec/services/instagram/test_event_service_spec.rb @@ -0,0 +1,71 @@ +require 'rails_helper' + +describe Instagram::TestEventService do + let(:account) { create(:account) } + let(:instagram_channel) { create(:channel_instagram, account: account) } + let(:inbox) { create(:inbox, channel: instagram_channel, account: account) } + + describe '#perform' do + context 'when validating test webhook event' do + let(:test_messaging) do + { + 'sender': { + 'id': '12334' + }, + 'recipient': { + 'id': '23245' + }, + 'timestamp': '1527459824', + 'message': { + 'mid': 'random_mid', + 'text': 'random_text' + } + }.with_indifferent_access + end + + it 'creates test message for valid test webhook event' do + # Ensure inbox exists before test + inbox + + service = described_class.new(test_messaging) + + expect { service.perform }.to change(Message, :count).by(1) + + message = Message.last + expect(message.content).to eq('random_text') + expect(message.source_id).to eq('random_mid') + expect(message.message_type).to eq('incoming') + end + + it 'creates a contact with sender_username' do + # Ensure inbox exists before test + inbox + + service = described_class.new(test_messaging) + service.perform + + contact = Contact.last + expect(contact.name).to eq('sender_username') + end + + it 'returns false for non-test webhook events' do + invalid_messaging = test_messaging.deep_dup + invalid_messaging[:sender][:id] = 'different_id' + + service = described_class.new(invalid_messaging) + + expect(service.perform).to be(false) + end + + it 'returns nil when no Instagram channel exists' do + # Delete all inboxes and channels + Inbox.destroy_all + Channel::Instagram.destroy_all + + service = described_class.new(test_messaging) + + expect(service.perform).to be_nil + end + end + end +end