diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue index 865122386..a2efc93db 100644 --- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue +++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue @@ -218,8 +218,14 @@ export default { if (additionalAttributes) { const { agent_reply_time_window_message: agentReplyTimeWindowMessage, + agent_reply_time_window: agentReplyTimeWindow, } = additionalAttributes; - return agentReplyTimeWindowMessage; + return ( + agentReplyTimeWindowMessage || + this.$t('CONVERSATION.API_HOURS_WINDOW', { + hours: agentReplyTimeWindow, + }) + ); } return ''; } diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json index 6cfe9d082..3fa98ffe3 100644 --- a/app/javascript/dashboard/i18n/locale/en/conversation.json +++ b/app/javascript/dashboard/i18n/locale/en/conversation.json @@ -32,6 +32,7 @@ "LOADING_CONVERSATIONS": "Loading Conversations", "CANNOT_REPLY": "You cannot reply due to", "24_HOURS_WINDOW": "24 hour message window restriction", + "API_HOURS_WINDOW": "You can only reply to this conversation within {hours} hours", "NOT_ASSIGNED_TO_YOU": "This conversation is not assigned to you. Would you like to assign this conversation to yourself?", "ASSIGN_TO_ME": "Assign to me", "TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to", diff --git a/app/models/channel/api.rb b/app/models/channel/api.rb index a2bf9da48..17270e560 100644 --- a/app/models/channel/api.rb +++ b/app/models/channel/api.rb @@ -33,10 +33,6 @@ class Channel::Api < ApplicationRecord 'API' end - def messaging_window_enabled? - additional_attributes.present? && additional_attributes['agent_reply_time_window'].present? - end - private def ensure_valid_agent_reply_time_window diff --git a/app/models/channel/facebook_page.rb b/app/models/channel/facebook_page.rb index 1755889f8..1b6e151cb 100644 --- a/app/models/channel/facebook_page.rb +++ b/app/models/channel/facebook_page.rb @@ -32,10 +32,6 @@ class Channel::FacebookPage < ApplicationRecord 'Facebook' end - def messaging_window_enabled? - false - end - def create_contact_inbox(instagram_id, name) @contact_inbox = ::ContactInboxWithContactBuilder.new({ source_id: instagram_id, diff --git a/app/models/channel/twilio_sms.rb b/app/models/channel/twilio_sms.rb index 935ab2a14..8dbe47703 100644 --- a/app/models/channel/twilio_sms.rb +++ b/app/models/channel/twilio_sms.rb @@ -41,10 +41,6 @@ class Channel::TwilioSms < ApplicationRecord medium == 'sms' ? 'Twilio SMS' : 'Whatsapp' end - def messaging_window_enabled? - medium == 'whatsapp' - end - def send_message(to:, body:, media_url: nil) params = send_message_from.merge(to: to, body: body) params[:media_url] = media_url if media_url.present? diff --git a/app/models/channel/whatsapp.rb b/app/models/channel/whatsapp.rb index f95c2c30a..1848e17a9 100644 --- a/app/models/channel/whatsapp.rb +++ b/app/models/channel/whatsapp.rb @@ -46,10 +46,6 @@ class Channel::Whatsapp < ApplicationRecord end end - def messaging_window_enabled? - true - end - def mark_message_templates_updated # rubocop:disable Rails/SkipsModelValidations update_column(:message_templates_last_updated, Time.zone.now) diff --git a/app/models/concerns/channelable.rb b/app/models/concerns/channelable.rb index 0b23283e1..e3e9eba94 100644 --- a/app/models/concerns/channelable.rb +++ b/app/models/concerns/channelable.rb @@ -7,10 +7,6 @@ module Channelable after_update :create_audit_log_entry end - def messaging_window_enabled? - false - end - def create_audit_log_entry; end end diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 2a7a49223..887db286e 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -115,14 +115,7 @@ class Conversation < ApplicationRecord delegate :auto_resolve_duration, to: :account def can_reply? - channel = inbox&.channel - - return can_reply_on_instagram? if additional_attributes['type'] == 'instagram_direct_message' - - return true unless channel&.messaging_window_enabled? - - messaging_window = inbox.api? ? channel.additional_attributes['agent_reply_time_window'].to_i : 24 - last_message_in_messaging_window?(messaging_window) + Conversations::MessageWindowService.new(self).can_reply? end def language @@ -137,24 +130,6 @@ class Conversation < ApplicationRecord messages&.incoming&.last end - def last_message_in_messaging_window?(time) - return false if last_incoming_message.nil? - - Time.current < last_incoming_message.created_at + time.hours - end - - def can_reply_on_instagram? - global_config = GlobalConfig.get('ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT') - - return false if last_incoming_message.nil? - - if global_config['ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT'] - Time.current < last_incoming_message.created_at + 7.days - else - last_message_in_messaging_window?(24) - end - end - def toggle_status # FIXME: implement state machine with aasm self.status = open? ? :resolved : :open diff --git a/app/services/conversations/message_window_service.rb b/app/services/conversations/message_window_service.rb new file mode 100644 index 000000000..2394c1791 --- /dev/null +++ b/app/services/conversations/message_window_service.rb @@ -0,0 +1,64 @@ +class Conversations::MessageWindowService + MESSAGING_WINDOW_24_HOURS = 24.hours + MESSAGING_WINDOW_7_DAYS = 7.days + + def initialize(conversation) + @conversation = conversation + end + + def can_reply? + return true if messaging_window.blank? + + last_message_in_messaging_window?(messaging_window) + end + + private + + def messaging_window + case @conversation.inbox.channel_type + when 'Channel::Api' + api_messaging_window + when 'Channel::FacebookPage' + messenger_messaging_window + when 'Channel::Instagram' + instagram_messaging_window + when 'Channel::Whatsapp' + MESSAGING_WINDOW_24_HOURS + when 'Channel::TwilioSms' + twilio_messaging_window + end + end + + def last_message_in_messaging_window?(time) + return false if last_incoming_message.nil? + + Time.current < last_incoming_message.created_at + time + end + + def api_messaging_window + return if @conversation.inbox.channel.additional_attributes['agent_reply_time_window'].blank? + + @conversation.inbox.channel.additional_attributes['agent_reply_time_window'].to_i.hours + end + + # Check medium of the inbox to determine the messaging window + def twilio_messaging_window + @conversation.inbox.channel.medium == 'whatsapp' ? MESSAGING_WINDOW_24_HOURS : nil + end + + def messenger_messaging_window + meta_messaging_window('ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT') + end + + def instagram_messaging_window + meta_messaging_window('ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT') + end + + def meta_messaging_window(config_key) + GlobalConfigService.load(config_key, nil) ? MESSAGING_WINDOW_7_DAYS : MESSAGING_WINDOW_24_HOURS + end + + def last_incoming_message + @last_incoming_message ||= @conversation.messages&.incoming&.last + end +end diff --git a/spec/models/channel/twilio_sms_spec.rb b/spec/models/channel/twilio_sms_spec.rb index 5f26eb706..ac89a0f7f 100644 --- a/spec/models/channel/twilio_sms_spec.rb +++ b/spec/models/channel/twilio_sms_spec.rb @@ -3,28 +3,6 @@ require 'rails_helper' RSpec.describe Channel::TwilioSms do - describe '#has_24_hour_messaging_window?' do - context 'with medium whatsapp' do - let!(:whatsapp_channel) { create(:channel_twilio_sms, medium: :whatsapp) } - - it 'returns true' do - expect(whatsapp_channel.messaging_window_enabled?).to be true - expect(whatsapp_channel.name).to eq 'Whatsapp' - expect(whatsapp_channel.medium).to eq 'whatsapp' - end - end - - context 'with medium sms' do - let!(:sms_channel) { create(:channel_twilio_sms, medium: :sms) } - - it 'returns false' do - expect(sms_channel.messaging_window_enabled?).to be false - expect(sms_channel.name).to eq 'Twilio SMS' - expect(sms_channel.medium).to eq 'sms' - end - end - end - describe '#validations' do context 'with phone number blank' do let!(:sms_channel) { create(:channel_twilio_sms, medium: :sms, phone_number: nil) } diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index 1938fb204..e437f5d89 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -585,116 +585,6 @@ RSpec.describe Conversation do end end - describe '#can_reply?' do - describe 'on channels without 24 hour restriction' do - let(:conversation) { create(:conversation) } - - it 'returns true' do - expect(conversation.can_reply?).to be true - end - - it 'return true for facebook channels' do - stub_request(:post, /graph.facebook.com/) - facebook_channel = create(:channel_facebook_page) - facebook_inbox = create(:inbox, channel: facebook_channel, account: facebook_channel.account) - fb_conversation = create(:conversation, inbox: facebook_inbox, account: facebook_channel.account) - - expect(fb_conversation.can_reply?).to be true - expect(facebook_channel.messaging_window_enabled?).to be false - end - end - - describe 'on channels with 24 hour restriction' do - before do - stub_request(:post, /graph.facebook.com/) - end - - let!(:facebook_channel) { create(:channel_facebook_page) } - let!(:facebook_inbox) { create(:inbox, channel: facebook_channel, account: facebook_channel.account) } - let!(:conversation) { create(:conversation, inbox: facebook_inbox, account: facebook_channel.account) } - - context 'when instagram channel' do - it 'return true with HUMAN_AGENT if it is outside of 24 hour window' do - InstallationConfig.where(name: 'ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT').first_or_create(value: true) - - conversation.update(additional_attributes: { type: 'instagram_direct_message' }) - create( - :message, - account: conversation.account, - inbox: facebook_inbox, - conversation: conversation, - created_at: 48.hours.ago - ) - - expect(conversation.can_reply?).to be true - end - - it 'return false without HUMAN_AGENT if it is outside of 24 hour window' do - InstallationConfig.where(name: 'ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT').first_or_create(value: false) - - conversation.update(additional_attributes: { type: 'instagram_direct_message' }) - create( - :message, - account: conversation.account, - inbox: facebook_inbox, - conversation: conversation, - created_at: 48.hours.ago - ) - - expect(conversation.can_reply?).to be false - end - end - end - - describe 'on API channels' do - let!(:api_channel) { create(:channel_api, additional_attributes: {}) } - let!(:api_channel_with_limit) { create(:channel_api, additional_attributes: { agent_reply_time_window: '12' }) } - - context 'when agent_reply_time_window is not configured' do - it 'return true irrespective of the last message time' do - conversation = create(:conversation, inbox: api_channel.inbox) - create( - :message, - account: conversation.account, - inbox: api_channel.inbox, - conversation: conversation, - created_at: 13.hours.ago - ) - - expect(api_channel.additional_attributes['agent_reply_time_window']).to be_nil - expect(conversation.can_reply?).to be true - end - end - - context 'when agent_reply_time_window is configured' do - it 'return false if it is outside of agent_reply_time_window' do - conversation = create(:conversation, inbox: api_channel_with_limit.inbox) - create( - :message, - account: conversation.account, - inbox: api_channel_with_limit.inbox, - conversation: conversation, - created_at: 13.hours.ago - ) - - expect(api_channel_with_limit.additional_attributes['agent_reply_time_window']).to eq '12' - expect(conversation.can_reply?).to be false - end - - it 'return true if it is inside of agent_reply_time_window' do - conversation = create(:conversation, inbox: api_channel_with_limit.inbox) - create( - :message, - account: conversation.account, - inbox: api_channel_with_limit.inbox, - conversation: conversation - ) - expect(conversation.can_reply?).to be true - end - end - end - end - describe '#delete conversation' do include ActiveJob::TestHelper @@ -918,4 +808,25 @@ RSpec.describe Conversation do end end end + + describe '#can_reply?' do + let(:conversation) { create(:conversation) } + let(:message_window_service) { instance_double(Conversations::MessageWindowService) } + + before do + allow(Conversations::MessageWindowService).to receive(:new).with(conversation).and_return(message_window_service) + end + + it 'delegates to MessageWindowService' do + allow(message_window_service).to receive(:can_reply?).and_return(true) + expect(conversation.can_reply?).to be true + expect(message_window_service).to have_received(:can_reply?) + end + + it 'returns false when MessageWindowService returns false' do + allow(message_window_service).to receive(:can_reply?).and_return(false) + expect(conversation.can_reply?).to be false + expect(message_window_service).to have_received(:can_reply?) + end + end end diff --git a/spec/services/conversations/message_window_service_spec.rb b/spec/services/conversations/message_window_service_spec.rb new file mode 100644 index 000000000..34c5ebbad --- /dev/null +++ b/spec/services/conversations/message_window_service_spec.rb @@ -0,0 +1,627 @@ +require 'rails_helper' + +RSpec.describe Conversations::MessageWindowService do + describe 'on API channels' do + let!(:api_channel) { create(:channel_api, additional_attributes: {}) } + let!(:api_channel_with_limit) { create(:channel_api, additional_attributes: { agent_reply_time_window: '12' }) } + + context 'when agent_reply_time_window is not configured' do + it 'return true irrespective of the last message time' do + conversation = create(:conversation, inbox: api_channel.inbox) + create( + :message, + account: conversation.account, + inbox: api_channel.inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + + expect(api_channel.additional_attributes['agent_reply_time_window']).to be_nil + expect(service.can_reply?).to be true + end + end + + context 'when agent_reply_time_window is configured' do + it 'return false if it is outside of agent_reply_time_window' do + conversation = create(:conversation, inbox: api_channel_with_limit.inbox) + create( + :message, + account: conversation.account, + inbox: api_channel_with_limit.inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + + expect(api_channel_with_limit.additional_attributes['agent_reply_time_window']).to eq '12' + expect(service.can_reply?).to be false + end + + it 'return true if it is inside of agent_reply_time_window' do + conversation = create(:conversation, inbox: api_channel_with_limit.inbox) + create( + :message, + account: conversation.account, + inbox: api_channel_with_limit.inbox, + conversation: conversation + ) + service = described_class.new(conversation) + + expect(service.can_reply?).to be true + end + end + end + + describe 'on Facebook channels' do + before do + stub_request(:post, /graph.facebook.com/) + end + + let!(:facebook_channel) { create(:channel_facebook_page) } + let!(:facebook_inbox) { create(:inbox, channel: facebook_channel, account: facebook_channel.account) } + let!(:conversation) { create(:conversation, inbox: facebook_inbox, account: facebook_channel.account) } + + context 'when the HUMAN_AGENT is enabled' do + it 'return false if the last message is outgoing' do + with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + end + + it 'return true if the last message is incoming and within the messaging window (with in 24 hours)' do + with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do + create( + :message, + account: conversation.account, + inbox: facebook_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + it 'return true if the last message is incoming and within the messaging window (with in 7 days)' do + with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do + create( + :message, + account: conversation.account, + inbox: facebook_inbox, + conversation: conversation, + created_at: 5.days.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + it 'return false if the last message is incoming and outside the messaging window (8 days ago )' do + with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do + create( + :message, + account: conversation.account, + inbox: facebook_inbox, + conversation: conversation, + created_at: 8.days.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + end + + it 'return true if last message is outgoing but previous incoming message is within window' do + with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do + create( + :message, + account: conversation.account, + inbox: facebook_inbox, + conversation: conversation, + message_type: :incoming, + created_at: 6.hours.ago + ) + + create( + :message, + account: conversation.account, + inbox: facebook_inbox, + conversation: conversation, + message_type: :outgoing, + created_at: 1.hour.ago + ) + + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + it 'considers only the last incoming message for determining time window' do + with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do + # Old message outside window + create( + :message, + account: conversation.account, + inbox: facebook_inbox, + conversation: conversation, + created_at: 10.days.ago + ) + + # Recent message within window + create( + :message, + account: conversation.account, + inbox: facebook_inbox, + conversation: conversation, + created_at: 6.hours.ago + ) + + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + end + + context 'when the HUMAN_AGENT is disabled' do + with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'false' do + it 'return false if the last message is outgoing' do + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + + it 'return false if the last message is incoming and outside the messaging window ( 8 days ago )' do + create( + :message, + account: conversation.account, + inbox: facebook_inbox, + conversation: conversation, + created_at: 4.days.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + + it 'return true if the last message is incoming and within the messaging window (24 hours limit)' do + create( + :message, + account: conversation.account, + inbox: facebook_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + end + end + + describe 'on Instagram channels' do + let!(:instagram_channel) { create(:channel_instagram) } + let!(:instagram_inbox) { create(:inbox, channel: instagram_channel, account: instagram_channel.account) } + let!(:conversation) { create(:conversation, inbox: instagram_inbox, account: instagram_channel.account) } + + context 'when the HUMAN_AGENT is enabled' do + it 'return false if the last message is outgoing' do + with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + end + + it 'return true if the last message is incoming and within the messaging window (with in 24 hours)' do + with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do + create( + :message, + account: conversation.account, + inbox: instagram_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + it 'return true if the last message is incoming and within the messaging window (with in 7 days)' do + with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do + create( + :message, + account: conversation.account, + inbox: instagram_inbox, + conversation: conversation, + created_at: 6.days.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + it 'return false if the last message is incoming and outside the messaging window (8 days ago)' do + with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do + create( + :message, + account: conversation.account, + inbox: instagram_inbox, + conversation: conversation, + created_at: 8.days.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + end + + it 'return true if last message is outgoing but previous incoming message is within window' do + with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do + create( + :message, + account: conversation.account, + inbox: instagram_inbox, + conversation: conversation, + message_type: :incoming, + created_at: 6.hours.ago + ) + + create( + :message, + account: conversation.account, + inbox: instagram_inbox, + conversation: conversation, + message_type: :outgoing, + created_at: 1.hour.ago + ) + + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + it 'considers only the last incoming message for determining time window' do + with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do + # Old message outside window + create( + :message, + account: conversation.account, + inbox: instagram_inbox, + conversation: conversation, + created_at: 10.days.ago + ) + + # Recent message within window + create( + :message, + account: conversation.account, + inbox: instagram_inbox, + conversation: conversation, + created_at: 6.hours.ago + ) + + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + it 'return true if the last message is incoming and exactly at the edge of 24-hour window with HUMAN_AGENT disabled' do + with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do + create( + :message, + account: conversation.account, + inbox: instagram_inbox, + conversation: conversation, + created_at: 24.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + end + + context 'when the HUMAN_AGENT is disabled' do + it 'return false if the last message is outgoing' do + with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'false' do + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + end + + it 'return false if the last message is incoming and outside the messaging window (8 days ago)' do + with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'false' do + create( + :message, + account: conversation.account, + inbox: instagram_inbox, + conversation: conversation, + created_at: 9.days.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + end + + it 'return true if the last message is incoming and within the messaging window (24 hours limit)' do + with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'false' do + create( + :message, + account: conversation.account, + inbox: instagram_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + end + end + + describe 'on WhatsApp Cloud channels' do + let!(:whatsapp_channel) { create(:channel_whatsapp, provider: 'whatsapp_cloud', sync_templates: false, validate_provider_config: false) } + let!(:whatsapp_inbox) { create(:inbox, channel: whatsapp_channel, account: whatsapp_channel.account) } + let!(:conversation) { create(:conversation, inbox: whatsapp_inbox, account: whatsapp_channel.account) } + + it 'return false if the last message is outgoing' do + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + + it 'return true if the last message is incoming and within the messaging window (with in 24 hours)' do + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + + it 'return false if the last message is incoming and outside the messaging window (24 hours limit)' do + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + created_at: 25.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + + it 'return true if last message is outgoing but previous incoming message is within window' do + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + message_type: :incoming, + created_at: 6.hours.ago + ) + + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + message_type: :outgoing, + created_at: 1.hour.ago + ) + + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + + it 'considers only the last incoming message for determining time window' do + # Old message outside window + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + created_at: 10.days.ago + ) + + # Recent message within window + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + created_at: 6.hours.ago + ) + + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + describe 'on Web widget channels' do + let!(:widget_channel) { create(:channel_widget) } + let!(:widget_inbox) { create(:inbox, channel: widget_channel, account: widget_channel.account) } + let!(:conversation) { create(:conversation, inbox: widget_inbox, account: widget_channel.account) } + + it 'return true irrespective of the last message time' do + create( + :message, + account: conversation.account, + inbox: widget_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + describe 'on SMS channels' do + let!(:sms_channel) { create(:channel_sms) } + let!(:sms_inbox) { create(:inbox, channel: sms_channel, account: sms_channel.account) } + let!(:conversation) { create(:conversation, inbox: sms_inbox, account: sms_channel.account) } + + it 'return true irrespective of the last message time' do + create( + :message, + account: conversation.account, + inbox: sms_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + describe 'on Telegram channels' do + let!(:telegram_channel) { create(:channel_telegram) } + let!(:telegram_inbox) { create(:inbox, channel: telegram_channel, account: telegram_channel.account) } + let!(:conversation) { create(:conversation, inbox: telegram_inbox, account: telegram_channel.account) } + + it 'return true irrespective of the last message time' do + create( + :message, + account: conversation.account, + inbox: telegram_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + describe 'on Email channels' do + let!(:email_channel) { create(:channel_email) } + let!(:email_inbox) { create(:inbox, channel: email_channel, account: email_channel.account) } + let!(:conversation) { create(:conversation, inbox: email_inbox, account: email_channel.account) } + + it 'return true irrespective of the last message time' do + create( + :message, + account: conversation.account, + inbox: email_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + describe 'on Line channels' do + let!(:line_channel) { create(:channel_line) } + let!(:line_inbox) { create(:inbox, channel: line_channel, account: line_channel.account) } + let!(:conversation) { create(:conversation, inbox: line_inbox, account: line_channel.account) } + + it 'return true irrespective of the last message time' do + create( + :message, + account: conversation.account, + inbox: line_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + describe 'on Twilio SMS channels' do + let!(:twilio_sms_channel) { create(:channel_twilio_sms) } + let!(:twilio_sms_inbox) { create(:inbox, channel: twilio_sms_channel, account: twilio_sms_channel.account) } + let!(:conversation) { create(:conversation, inbox: twilio_sms_inbox, account: twilio_sms_channel.account) } + + it 'return true irrespective of the last message time' do + create( + :message, + account: conversation.account, + inbox: twilio_sms_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end + + describe 'on WhatsApp Twilio channels' do + let!(:whatsapp_channel) { create(:channel_twilio_sms, medium: :whatsapp) } + let!(:whatsapp_inbox) { create(:inbox, channel: whatsapp_channel, account: whatsapp_channel.account) } + let!(:conversation) { create(:conversation, inbox: whatsapp_inbox, account: whatsapp_channel.account) } + + it 'return false if the last message is outgoing' do + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + + it 'return true if the last message is incoming and within the messaging window (with in 24 hours)' do + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + created_at: 13.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + + it 'return false if the last message is incoming and outside the messaging window (24 hours limit)' do + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + created_at: 25.hours.ago + ) + service = described_class.new(conversation) + expect(service.can_reply?).to be false + end + + it 'return true if last message is outgoing but previous incoming message is within window' do + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + message_type: :incoming, + created_at: 6.hours.ago + ) + + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + message_type: :outgoing, + created_at: 1.hour.ago + ) + + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + + it 'considers only the last incoming message for determining time window' do + # Old message outside window + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + created_at: 10.days.ago + ) + + # Recent message within window + create( + :message, + account: conversation.account, + inbox: whatsapp_inbox, + conversation: conversation, + created_at: 6.hours.ago + ) + + service = described_class.new(conversation) + expect(service.can_reply?).to be true + end + end +end