diff --git a/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue b/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue index f41bff005..4ee7aaa9d 100644 --- a/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue +++ b/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue @@ -205,6 +205,11 @@ export default { ) { return this.sourceId && this.isSent; } + // There is no source id for the line channel + if (this.isALineChannel) { + return true; + } + return false; }, showDeliveredIndicator() { @@ -223,6 +228,9 @@ export default { if (this.isAWebWidgetInbox || this.isAPIInbox) { return this.isSent; } + if (this.isALineChannel) { + return this.isDelivered; + } return false; }, diff --git a/app/services/line/send_on_line_service.rb b/app/services/line/send_on_line_service.rb index 6ac3024fc..a30de4ab4 100644 --- a/app/services/line/send_on_line_service.rb +++ b/app/services/line/send_on_line_service.rb @@ -6,6 +6,30 @@ class Line::SendOnLineService < Base::SendOnChannelService end def perform_reply - channel.client.push_message(message.conversation.contact_inbox.source_id, [{ type: 'text', text: message.content }]) + response = channel.client.push_message(message.conversation.contact_inbox.source_id, [{ type: 'text', text: message.content }]) + return if response.blank? + + parsed_json = JSON.parse(response.body) + + if response.code == '200' + # If the request is successful, update the message status to delivered + message.update!(status: :delivered) + else + # If the request is not successful, update the message status to failed and save the external error + message.update!(status: :failed, external_error: external_error(parsed_json)) + end + end + + # https://developers.line.biz/en/reference/messaging-api/#error-responses + def external_error(error) + # Message containing information about the error. See https://developers.line.biz/en/reference/messaging-api/#error-messages + message = error['message'] + # An array of error details. If the array is empty, this property will not be included in the response. + details = error['details'] + + return message if details.blank? + + detail_messages = details.map { |detail| "#{detail['property']}: #{detail['message']}" } + [message, detail_messages].join(', ') end end diff --git a/spec/services/line/send_on_line_service_spec.rb b/spec/services/line/send_on_line_service_spec.rb index 117353de1..2320f8679 100644 --- a/spec/services/line/send_on_line_service_spec.rb +++ b/spec/services/line/send_on_line_service_spec.rb @@ -2,17 +2,95 @@ require 'rails_helper' describe Line::SendOnLineService do describe '#perform' do - context 'when a valid message' do + let(:line_client) { double } + let(:line_channel) { create(:channel_line) } + let(:message) do + create(:message, message_type: :outgoing, content: 'test', + conversation: create(:conversation, inbox: line_channel.inbox)) + end + + before do + allow(Line::Bot::Client).to receive(:new).and_return(line_client) + end + + context 'when message send' do it 'calls @channel.client.push_message' do - line_client = double - line_channel = create(:channel_line) - message = create(:message, message_type: :outgoing, content: 'test', - conversation: create(:conversation, inbox: line_channel.inbox)) allow(line_client).to receive(:push_message) - allow(Line::Bot::Client).to receive(:new).and_return(line_client) expect(line_client).to receive(:push_message) described_class.new(message: message).perform end end + + context 'when message send fails without details' do + let(:error_response) do + { + 'message' => 'The request was invalid' + }.to_json + end + + before do + allow(line_client).to receive(:push_message).and_return(OpenStruct.new(code: '400', body: error_response)) + end + + it 'updates the message status to failed' do + described_class.new(message: message).perform + message.reload + expect(message.status).to eq('failed') + end + + it 'updates the external error without details' do + described_class.new(message: message).perform + message.reload + expect(message.external_error).to eq('The request was invalid') + end + end + + context 'when message send fails with details' do + let(:error_response) do + { + 'message' => 'The request was invalid', + 'details' => [ + { + 'property' => 'messages[0].text', + 'message' => 'May not be empty' + } + ] + }.to_json + end + + before do + allow(line_client).to receive(:push_message).and_return(OpenStruct.new(code: '400', body: error_response)) + end + + it 'updates the message status to failed' do + described_class.new(message: message).perform + message.reload + expect(message.status).to eq('failed') + end + + it 'updates the external error with details' do + described_class.new(message: message).perform + message.reload + expect(message.external_error).to eq('The request was invalid, messages[0].text: May not be empty') + end + end + + context 'when message send succeeds' do + let(:success_response) do + { + 'message' => 'ok' + }.to_json + end + + before do + allow(line_client).to receive(:push_message).and_return(OpenStruct.new(code: '200', body: success_response)) + end + + it 'updates the message status to delivered' do + described_class.new(message: message).perform + message.reload + expect(message.status).to eq('delivered') + end + end end end