From 0e41263f9c3da2fe34cb1c04fde14620ecb7283a Mon Sep 17 00:00:00 2001 From: mix5003 Date: Mon, 22 Sep 2025 18:35:25 +0700 Subject: [PATCH] fix: Ensure messages go to correct conversation when receive multi user in 1 LINE webhook (#12322) # Pull Request Template ## Description Ensure messages go to correct conversation when receive multi user in 1 LINE webhook. base on [document](https://developers.line.biz/en/reference/messaging-api/#webhook-event-objects:~:text=There%20is%20not%20necessarily%20one%20user%20per%20webhook). it said ``` There is not necessarily one user per webhook. A message event from person A and a follow event from person B may be in the same webhook. ``` this PR has 1 break changes. In old version. when receive [follow](https://developers.line.biz/en/reference/messaging-api/#follow-event) event, it will create conversation with no messages. After this PR. when receive follow event, it will not create conversation, contact and messages ## Type of change Please delete options that are not relevant. - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? add test case. and follow event test by delete conversation, and block and unblock line account ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: mix5003 Co-authored-by: Muhsin Keloth Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/services/line/incoming_message_service.rb | 27 ++--- .../line/incoming_message_service_spec.rb | 104 +++++++++++++++++- 2 files changed, 116 insertions(+), 15 deletions(-) diff --git a/app/services/line/incoming_message_service.rb b/app/services/line/incoming_message_service.rb index 0d2f78c7a..6a1192d02 100644 --- a/app/services/line/incoming_message_service.rb +++ b/app/services/line/incoming_message_service.rb @@ -10,11 +10,6 @@ class Line::IncomingMessageService # probably test events return if params[:events].blank? - line_contact_info - return if line_contact_info['userId'].blank? - - set_contact - set_conversation parse_events end @@ -22,6 +17,14 @@ class Line::IncomingMessageService def parse_events params[:events].each do |event| + next unless event_type_message?(event) + + get_line_contact_info(event) + next if @line_contact_info['userId'].blank? + + set_contact + set_conversation + next unless message_created? event attach_files event['message'] @@ -30,8 +33,6 @@ class Line::IncomingMessageService end def message_created?(event) - return unless event_type_message?(event) - @message = @conversation.messages.build( content: message_content(event), account_id: @inbox.account_id, @@ -119,13 +120,13 @@ class Line::IncomingMessageService @account ||= inbox.account end - def line_contact_info - @line_contact_info ||= JSON.parse(inbox.channel.client.get_profile(params[:events].first['source']['userId']).body) + def get_line_contact_info(event) + @line_contact_info = JSON.parse(inbox.channel.client.get_profile(event['source']['userId']).body) end def set_contact contact_inbox = ::ContactInboxWithContactBuilder.new( - source_id: line_contact_info['userId'], + source_id: @line_contact_info['userId'], inbox: inbox, contact_attributes: contact_attributes ).perform @@ -152,15 +153,15 @@ class Line::IncomingMessageService def contact_attributes { - name: line_contact_info['displayName'], - avatar_url: line_contact_info['pictureUrl'], + name: @line_contact_info['displayName'], + avatar_url: @line_contact_info['pictureUrl'], additional_attributes: additional_attributes } end def additional_attributes { - social_line_user_id: line_contact_info['userId'] + social_line_user_id: @line_contact_info['userId'] } end diff --git a/spec/services/line/incoming_message_service_spec.rb b/spec/services/line/incoming_message_service_spec.rb index 9160efff2..a7805ce9b 100644 --- a/spec/services/line/incoming_message_service_spec.rb +++ b/spec/services/line/incoming_message_service_spec.rb @@ -35,6 +35,62 @@ describe Line::IncomingMessageService do }.with_indifferent_access end + let(:follow_params) do + { + 'destination': '2342234234', + 'events': [ + { + 'replyToken': '8cf9239d56244f4197887e939187e19e', + 'type': 'follow', + 'mode': 'active', + 'timestamp': 1_462_629_479_859, + 'source': { + 'type': 'user', + 'userId': 'U4af4980629' + } + } + ] + }.with_indifferent_access + end + + let(:multi_user_params) do + { + 'destination': '2342234234', + 'events': [ + { + 'replyToken': '0f3779fba3b349968c5d07db31eab56f1', + 'type': 'message', + 'mode': 'active', + 'timestamp': 1_462_629_479_859, + 'source': { + 'type': 'user', + 'userId': 'U4af4980629' + }, + 'message': { + 'id': '3257081', + 'type': 'text', + 'text': 'Hello, world 1' + } + }, + { + 'replyToken': '0f3779fba3b349968c5d07db31eab56f2', + 'type': 'message', + 'mode': 'active', + 'timestamp': 1_462_629_479_859, + 'source': { + 'type': 'user', + 'userId': 'U4af49806292' + }, + 'message': { + 'id': '3257082', + 'type': 'text', + 'text': 'Hello, world 2' + } + } + ] + }.with_indifferent_access + end + let(:image_params) do { 'destination': '2342234234', @@ -175,8 +231,8 @@ describe Line::IncomingMessageService do end describe '#perform' do - context 'when valid text message params' do - it 'creates appropriate conversations, message and contacts' do + context 'when non-text message params' do + it 'does not create conversations, messages and contacts' do line_bot = double line_user_profile = double allow(Line::Bot::Client).to receive(:new).and_return(line_bot) @@ -188,12 +244,56 @@ describe Line::IncomingMessageService do 'pictureUrl': 'https://test.com' }.to_json ) + described_class.new(inbox: line_channel.inbox, params: follow_params).perform + expect(line_channel.inbox.conversations.size).to eq(0) + expect(Contact.all.size).to eq(0) + expect(line_channel.inbox.messages.size).to eq(0) + end + end + + context 'when valid text message params' do + let(:line_bot) { double } + let(:line_user_profile) { double } + + before do + allow(Line::Bot::Client).to receive(:new).and_return(line_bot) + allow(line_bot).to receive(:get_profile).with('U4af4980629').and_return(line_user_profile) + allow(line_user_profile).to receive(:body).and_return( + { + 'displayName': 'LINE Test', + 'userId': 'U4af4980629', + 'pictureUrl': 'https://test.com' + }.to_json + ) + end + + it 'creates appropriate conversations, message and contacts' do described_class.new(inbox: line_channel.inbox, params: params).perform expect(line_channel.inbox.conversations).not_to eq(0) expect(Contact.all.first.name).to eq('LINE Test') expect(Contact.all.first.additional_attributes['social_line_user_id']).to eq('U4af4980629') expect(line_channel.inbox.messages.first.content).to eq('Hello, world') end + + it 'creates appropriate conversations, message and contacts for multi user' do + line_user_profile2 = double + allow(line_bot).to receive(:get_profile).with('U4af49806292').and_return(line_user_profile2) + allow(line_user_profile2).to receive(:body).and_return( + { + 'displayName': 'LINE Test 2', + 'userId': 'U4af49806292', + 'pictureUrl': 'https://test.com' + }.to_json + ) + described_class.new(inbox: line_channel.inbox, params: multi_user_params).perform + expect(line_channel.inbox.conversations.size).to eq(2) + expect(Contact.all.first.name).to eq('LINE Test') + expect(Contact.all.first.additional_attributes['social_line_user_id']).to eq('U4af4980629') + expect(Contact.all.last.name).to eq('LINE Test 2') + expect(Contact.all.last.additional_attributes['social_line_user_id']).to eq('U4af49806292') + expect(line_channel.inbox.messages.first.content).to eq('Hello, world 1') + expect(line_channel.inbox.messages.last.content).to eq('Hello, world 2') + end end context 'when valid sticker message params' do