diff --git a/app/javascript/dashboard/routes/dashboard/onboarding/specs/inbox-setup/InboxChannelsDialog.spec.js b/app/javascript/dashboard/routes/dashboard/onboarding/specs/inbox-setup/InboxChannelsDialog.spec.js index bf150f95d..a2ddcf7a1 100644 --- a/app/javascript/dashboard/routes/dashboard/onboarding/specs/inbox-setup/InboxChannelsDialog.spec.js +++ b/app/javascript/dashboard/routes/dashboard/onboarding/specs/inbox-setup/InboxChannelsDialog.spec.js @@ -1,11 +1,11 @@ import { mount } from '@vue/test-utils'; import { nextTick } from 'vue'; +import { createStore } from 'vuex'; +import { useRoute } from 'vue-router'; import InboxChannelsDialog from '../../inbox-setup/InboxChannelsDialog.vue'; vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: key => key }) })); -vi.mock('dashboard/composables/store', () => ({ - useMapGetter: () => ({ value: {} }), -})); +vi.mock('vue-router'); vi.mock('../../inbox-setup/useChannelConnect', () => ({ useChannelConnect: () => ({ connectViaOAuth: vi.fn(), @@ -13,10 +13,30 @@ vi.mock('../../inbox-setup/useChannelConnect', () => ({ }), })); +const store = createStore({ + modules: { + globalConfig: { + namespaced: true, + getters: { + get: () => ({}), + isOnChatwootCloud: () => false, + }, + }, + accounts: { + namespaced: true, + getters: { + getAccount: () => () => ({ id: 1, features: {} }), + isFeatureEnabledonAccount: () => () => false, + }, + }, + }, +}); + const mountDialog = () => mount(InboxChannelsDialog, { props: { inboxes: [] }, global: { + plugins: [store], stubs: { Dialog: { template: '
', @@ -31,6 +51,10 @@ const mountDialog = () => }); describe('InboxChannelsDialog Facebook gating', () => { + beforeEach(() => { + useRoute.mockReturnValue({ params: { accountId: '1' } }); + }); + afterEach(() => { delete window.chatwootConfig; }); diff --git a/enterprise/app/services/enterprise/message_templates/hook_execution_service.rb b/enterprise/app/services/enterprise/message_templates/hook_execution_service.rb index 0f377ada0..fa12996b5 100644 --- a/enterprise/app/services/enterprise/message_templates/hook_execution_service.rb +++ b/enterprise/app/services/enterprise/message_templates/hook_execution_service.rb @@ -50,7 +50,7 @@ module Enterprise::MessageTemplates::HookExecutionService end def should_process_captain_response? - conversation.pending? && message.incoming? && captain_should_engage? + conversation.pending? && message.incoming? && captain_assistant_configured? end def perform_handoff @@ -76,11 +76,10 @@ module Enterprise::MessageTemplates::HookExecutionService end def captain_handling_conversation? - conversation.pending? && captain_should_engage? + conversation.pending? && captain_assistant_configured? end - def captain_should_engage? - assistant = inbox.captain_assistant - assistant.present? && assistant.engages?(conversation.contact, conversation) + def captain_assistant_configured? + inbox.captain_assistant.present? end end diff --git a/spec/enterprise/services/enterprise/message_templates/hook_execution_service_spec.rb b/spec/enterprise/services/enterprise/message_templates/hook_execution_service_spec.rb index c0fa2654c..de4937030 100644 --- a/spec/enterprise/services/enterprise/message_templates/hook_execution_service_spec.rb +++ b/spec/enterprise/services/enterprise/message_templates/hook_execution_service_spec.rb @@ -126,21 +126,6 @@ RSpec.describe MessageTemplates::HookExecutionService do end end - context 'when the contact is outside the assistant audience' do - before do - assistant.update!(config: assistant.config.merge('audience' => { - 'attribute_key' => 'country_code', 'filter_operator' => 'equal_to', 'values' => ['US'] - })) - contact.update!(additional_attributes: { 'country_code' => 'CA' }) - end - - it 'does not schedule captain response job' do - expect(Captain::Conversation::ResponseBuilderJob).not_to receive(:perform_later) - - create(:message, conversation: conversation, message_type: :incoming, account: account) - end - end - context 'when the contact is inside the assistant audience' do before do assistant.update!(config: assistant.config.merge('audience' => { @@ -156,6 +141,77 @@ RSpec.describe MessageTemplates::HookExecutionService do end end + context 'when the conversation stops matching the audience mid-conversation' do + it 'still schedules captain response job for the pending conversation' do + conversation + assistant.update!(config: assistant.config.merge('audience' => { + 'attribute_key' => 'country_code', 'filter_operator' => 'equal_to', 'values' => ['US'] + })) + contact.update!(additional_attributes: { 'country_code' => 'CA' }) + + expect(Captain::Conversation::ResponseBuilderJob).to receive(:perform_later).with(conversation, assistant) + + create(:message, conversation: conversation, message_type: :incoming, account: account) + end + end + + context 'when the reply schedule stops matching mid-conversation' do + before do + inbox.update!(working_hours_enabled: true) + inbox.working_hours.find_by(day_of_week: Time.current.in_time_zone(inbox.timezone).wday).update!( + open_all_day: true, + closed_all_day: false + ) + end + + it 'still schedules captain response job when business hours end after captain took the conversation' do + assistant.update!(config: assistant.config.merge('response_window' => 'business_hours')) + conversation + inbox.working_hours.find_by(day_of_week: Time.current.in_time_zone(inbox.timezone).wday).update!( + open_all_day: false, + closed_all_day: true + ) + + expect(Captain::Conversation::ResponseBuilderJob).to receive(:perform_later).with(conversation, assistant) + + create(:message, conversation: conversation, message_type: :incoming, account: account) + end + + it 'still schedules captain response job when business hours begin after captain took the conversation' do + assistant.update!(config: assistant.config.merge('response_window' => 'outside_business_hours')) + inbox.working_hours.find_by(day_of_week: Time.current.in_time_zone(inbox.timezone).wday).update!( + open_all_day: false, + closed_all_day: true + ) + conversation + inbox.working_hours.find_by(day_of_week: Time.current.in_time_zone(inbox.timezone).wday).update!( + open_all_day: true, + closed_all_day: false + ) + + expect(Captain::Conversation::ResponseBuilderJob).to receive(:perform_later).with(conversation, assistant) + + create(:message, conversation: conversation, message_type: :incoming, account: account) + end + + it 'still schedules captain response job when both audience and schedule stop matching' do + assistant.update!(config: assistant.config.merge('response_window' => 'business_hours')) + conversation + assistant.update!(config: assistant.config.merge('audience' => { + 'attribute_key' => 'country_code', 'filter_operator' => 'equal_to', 'values' => ['US'] + })) + contact.update!(additional_attributes: { 'country_code' => 'CA' }) + inbox.working_hours.find_by(day_of_week: Time.current.in_time_zone(inbox.timezone).wday).update!( + open_all_day: false, + closed_all_day: true + ) + + expect(Captain::Conversation::ResponseBuilderJob).to receive(:perform_later).with(conversation, assistant) + + create(:message, conversation: conversation, message_type: :incoming, account: account) + end + end + context 'when message is outgoing' do it 'does not schedule captain response job' do expect(Captain::Conversation::ResponseBuilderJob).not_to receive(:perform_later)