Merge branch 'develop' into codex/cw-7519-intercom-stalled-retry-15m
This commit is contained in:
@@ -64,6 +64,14 @@ RSpec.describe 'Agent Bot API', type: :request do
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.body).to include(agent_bot.access_token.token)
|
||||
end
|
||||
|
||||
it 'supports API token authentication' do
|
||||
get "/api/v1/accounts/#{account.id}/agent_bots",
|
||||
headers: { api_access_token: admin.access_token.token },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::V1::Accounts::Captain::AgentSessions', type: :request do
|
||||
let(:account) { create(:account) }
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
let(:conversation) { create(:conversation, account: account, inbox: inbox) }
|
||||
let(:assistant) { create(:captain_assistant, account: account) }
|
||||
let(:message) do
|
||||
create(:message, account: account, conversation: conversation, message_type: :outgoing, sender: assistant)
|
||||
end
|
||||
|
||||
before { create(:inbox_member, user: agent, inbox: inbox) }
|
||||
|
||||
def json_response
|
||||
JSON.parse(response.body, symbolize_names: true)
|
||||
end
|
||||
|
||||
describe 'GET /api/v1/accounts/:account_id/captain/agent_sessions/:id' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v1/accounts/#{account.id}/captain/agent_sessions/#{message.id}", as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the message has an agent session' do
|
||||
let(:document) { create(:captain_document, account: account, assistant: assistant) }
|
||||
let(:documented_faq) do
|
||||
create(:captain_assistant_response, account: account, assistant: assistant,
|
||||
question: 'How do I reset my password?', documentable: document)
|
||||
end
|
||||
let(:plain_faq) do
|
||||
create(:captain_assistant_response, account: account, assistant: assistant, question: 'How do I change my email?')
|
||||
end
|
||||
let(:pdf_document) do
|
||||
create(:captain_document, account: account, assistant: assistant, external_link: nil,
|
||||
pdf_file: Rack::Test::UploadedFile.new(Rails.root.join('spec/assets/sample.pdf'), 'application/pdf'))
|
||||
end
|
||||
let(:pdf_faq) do
|
||||
create(:captain_assistant_response, account: account, assistant: assistant,
|
||||
question: 'What are the pricing tiers?', documentable: pdf_document)
|
||||
end
|
||||
let(:scenario) { create(:captain_scenario, account: account, assistant: assistant, title: 'Refund flow') }
|
||||
let(:run_context) do
|
||||
[
|
||||
{ 'role' => 'user', 'content' => 'I want a refund' },
|
||||
{ 'role' => 'assistant', 'content' => '', 'agent_name' => 'Assistant',
|
||||
'tool_calls' => [{ 'id' => 'call_1', 'name' => 'faq_lookup', 'arguments' => { 'query' => 'refund' } }] },
|
||||
{ 'role' => 'tool', 'content' => 'Refunds take 5 days', 'tool_call_id' => 'call_1' },
|
||||
{ 'role' => 'assistant', 'content' => 'Refunds take 5 days', 'agent_name' => "scenario_#{scenario.id}_refund_flow" }
|
||||
]
|
||||
end
|
||||
let!(:agent_session) do
|
||||
create(:captain_agent_session, account: account, assistant: assistant,
|
||||
subject: conversation, result: message,
|
||||
llm_model: 'openai-gpt-5.2', credits_consumed: 1.0,
|
||||
faq_ids: [documented_faq.id, plain_faq.id, pdf_faq.id, documented_faq.id + 100_000],
|
||||
scenario_ids: [scenario.id],
|
||||
run_context: run_context)
|
||||
end
|
||||
|
||||
it 'returns the session with hydrated citations and scenarios' do
|
||||
get "/api/v1/accounts/#{account.id}/captain/agent_sessions/#{message.id}",
|
||||
headers: agent.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
aggregate_failures do
|
||||
expect(json_response[:id]).to eq(agent_session.id)
|
||||
expect(json_response[:message_id]).to eq(message.id)
|
||||
expect(json_response[:llm_model]).to eq('openai-gpt-5.2')
|
||||
expect(json_response[:credits_consumed]).to eq(1.0)
|
||||
expect(json_response[:run_context].length).to eq(4)
|
||||
expect(json_response[:run_context].second[:tool_calls].first[:arguments][:query]).to eq('refund')
|
||||
|
||||
citations = json_response[:citations].index_by { |citation| citation[:id] }
|
||||
expect(citations.keys).to contain_exactly(documented_faq.id, plain_faq.id, pdf_faq.id)
|
||||
expect(citations[documented_faq.id][:title]).to eq('How do I reset my password?')
|
||||
expect(citations[documented_faq.id][:link]).to eq(document.external_link)
|
||||
expect(citations[plain_faq.id][:link]).to be_nil
|
||||
expect(pdf_document.external_link).to start_with('PDF:')
|
||||
expect(citations[pdf_faq.id][:link]).to eq(pdf_document.display_url)
|
||||
expect(citations[pdf_faq.id][:link]).to match(%r{\Ahttps?://})
|
||||
|
||||
expect(json_response[:scenarios]).to eq([{ id: scenario.id, title: 'Refund flow' }])
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not allow an agent without access to the conversation' do
|
||||
other_agent = create(:user, account: account, role: :agent)
|
||||
|
||||
get "/api/v1/accounts/#{account.id}/captain/agent_sessions/#{message.id}",
|
||||
headers: other_agent.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the message has no agent session' do
|
||||
it 'returns not found' do
|
||||
get "/api/v1/accounts/#{account.id}/captain/agent_sessions/#{message.id}",
|
||||
headers: agent.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the message does not belong to the account' do
|
||||
it 'returns not found' do
|
||||
other_message = create(:message)
|
||||
|
||||
get "/api/v1/accounts/#{account.id}/captain/agent_sessions/#{other_message.id}",
|
||||
headers: agent.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -257,10 +257,20 @@ RSpec.describe 'Api::V1::Accounts::Captain::Assistants', type: :request do
|
||||
let(:alice) { create(:user, account: account, role: :administrator, name: 'Alice Adams') }
|
||||
let(:bob) { create(:user, account: account, role: :administrator, name: 'Bob Brown') }
|
||||
let(:summary_service) { instance_double(Captain::OverviewSummaryService) }
|
||||
let(:summary_stats) do
|
||||
{
|
||||
conversations_handled: { current: 42 },
|
||||
hours_saved: { current: 12 },
|
||||
auto_resolution_rate: { current: 65.0, trend: 5.0 },
|
||||
handoff_rate: { current: 20.0, trend: -2.0 },
|
||||
reopen_rate: { current: 5.0, trend: -1.0 },
|
||||
knowledge: { coverage: 80, approved: 8, documents: 3 }
|
||||
}
|
||||
end
|
||||
|
||||
def get_summary(user)
|
||||
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/summary",
|
||||
params: { range: '30' },
|
||||
params: { range: '30', stats: summary_stats },
|
||||
headers: user.create_new_auth_token,
|
||||
as: :json
|
||||
end
|
||||
@@ -273,6 +283,7 @@ RSpec.describe 'Api::V1::Accounts::Captain::Assistants', type: :request do
|
||||
|
||||
it 'caches the summary per viewer so one user never receives another user\'s greeting' do
|
||||
allow(summary_service).to receive(:perform).and_return({ message: 'Hi Alice' })
|
||||
expect(Captain::AssistantStatsBuilder).not_to receive(:new)
|
||||
|
||||
get_summary(alice)
|
||||
get_summary(alice) # served from Alice's cache, no regeneration
|
||||
@@ -280,6 +291,7 @@ RSpec.describe 'Api::V1::Accounts::Captain::Assistants', type: :request do
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(Captain::OverviewSummaryService).to have_received(:new).twice
|
||||
expect(Captain::OverviewSummaryService).to have_received(:new).with(hash_including(stats: summary_stats)).twice
|
||||
end
|
||||
|
||||
it 'does not cache failures so a transient error is retried' do
|
||||
|
||||
@@ -33,8 +33,8 @@ RSpec.describe 'Twilio::VoiceController', type: :request do
|
||||
|
||||
expect(Voice::InboundCallBuilder).to receive(:perform!).with(
|
||||
inbox: inbox,
|
||||
from_number: from_number,
|
||||
call_sid: call_sid
|
||||
call_sid: call_sid,
|
||||
caller: { source_ids: [from_number], contact_attributes: { name: from_number, phone_number: from_number } }
|
||||
).and_return(call)
|
||||
|
||||
post "/twilio/voice/call/#{digits}", params: {
|
||||
|
||||
@@ -561,6 +561,22 @@ RSpec.describe Captain::Conversation::ResponseBuilderJob, type: :job do
|
||||
expect(account.reload.usage_limits[:captain][:responses][:consumed]).to eq(0)
|
||||
end
|
||||
|
||||
it 'attributes the handoff session to the private reason note when the tool recorded one' do
|
||||
handoff_note = create(:message, conversation: conversation, account: account, message_type: :outgoing,
|
||||
private: true, sender: assistant, content: 'Needs a human')
|
||||
run_context[:state][:cw_metadata][:handoff_note_id] = handoff_note.id
|
||||
allow(mock_agent_runner_service).to receive(:generate_response) do
|
||||
conversation.update!(status: :open)
|
||||
{ 'response' => 'Let me connect you', 'handoff_tool_called' => true }
|
||||
end
|
||||
|
||||
described_class.perform_now(conversation, assistant)
|
||||
|
||||
session = Captain::AgentSession.last
|
||||
expect(session.credits_consumed).to eq(0.0)
|
||||
expect(session.result_id).to eq(handoff_note.id)
|
||||
end
|
||||
|
||||
it 'creates a zero-credit session when the handoff tool fired but failed to commit' do
|
||||
allow(mock_agent_runner_service).to receive(:generate_response).and_return({
|
||||
'response' => 'I tried to hand off',
|
||||
|
||||
@@ -86,6 +86,12 @@ RSpec.describe Captain::Tools::HandoffTool, type: :model do
|
||||
|
||||
tool.perform(tool_context, reason: reason)
|
||||
end
|
||||
|
||||
it 'records the handoff note id in the run state for session capture' do
|
||||
tool.perform(tool_context, reason: 'Customer needs specialized support')
|
||||
|
||||
expect(tool_context.state[:cw_metadata][:handoff_note_id]).to eq(Message.last.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without reason provided' do
|
||||
@@ -107,6 +113,12 @@ RSpec.describe Captain::Tools::HandoffTool, type: :model do
|
||||
|
||||
tool.perform(tool_context)
|
||||
end
|
||||
|
||||
it 'does not record a handoff note id since the empty note never renders' do
|
||||
tool.perform(tool_context)
|
||||
|
||||
expect(tool_context.state[:cw_metadata]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when handoff fails' do
|
||||
|
||||
@@ -111,6 +111,25 @@ RSpec.describe Captain::Assistant::SessionCaptureService do
|
||||
expect(history.first).to include('role' => 'user', 'content' => 'CUST001')
|
||||
end
|
||||
|
||||
it 'stores multimodal content without cached attachment bytes' do
|
||||
content = RubyLLM::Content.new('See image', ['https://example.com/image.jpg'])
|
||||
content.attachments.first.instance_variable_set(:@content, "\xFF\xD8\xFF\xE0JFIF".b)
|
||||
run_context[:conversation_history] = [
|
||||
{ role: :user, content: content },
|
||||
{ role: :assistant, content: 'I can see the image', agent_name: 'Assistant' }
|
||||
]
|
||||
|
||||
history = service.capture!.run_context
|
||||
|
||||
expect(history.first).to include(
|
||||
'role' => 'user',
|
||||
'content' => {
|
||||
'text' => 'See image',
|
||||
'attachments' => [{ 'type' => 'image', 'source' => 'https://example.com/image.jpg' }]
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'stores the full history when it contains no user message' do
|
||||
run_context[:conversation_history] = conversation_history.reject { |message| message[:role] == :user }
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ RSpec.describe Voice::InboundCallBuilder do
|
||||
def perform_builder
|
||||
described_class.perform!(
|
||||
inbox: inbox,
|
||||
from_number: from_number,
|
||||
call_sid: call_sid
|
||||
call_sid: call_sid,
|
||||
caller: { source_ids: [from_number], contact_attributes: { name: from_number, phone_number: from_number } }
|
||||
)
|
||||
end
|
||||
|
||||
@@ -100,26 +100,29 @@ RSpec.describe Voice::InboundCallBuilder do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the WhatsApp wa_id needs Brazil normalization to match an existing ContactInbox' do
|
||||
context 'when a WhatsApp call shares a BSUID with an existing ContactInbox' do
|
||||
let(:whatsapp_channel) do
|
||||
create(:channel_whatsapp, account: account, provider: 'whatsapp_cloud',
|
||||
provider_config: { 'phone_number_id' => '123', 'source' => 'embedded_signup', 'calling_enabled' => true },
|
||||
validate_provider_config: false, sync_templates: false)
|
||||
end
|
||||
let(:whatsapp_inbox) { whatsapp_channel.inbox }
|
||||
let!(:stored_contact) { create(:contact, account: account, phone_number: '+5541988887777') }
|
||||
let!(:stored_contact) { create(:contact, account: account) }
|
||||
let!(:stored_contact_inbox) do
|
||||
create(:contact_inbox, contact: stored_contact, inbox: whatsapp_inbox, source_id: '5541988887777')
|
||||
create(:contact_inbox, contact: stored_contact, inbox: whatsapp_inbox, source_id: 'IN.2081978709342942')
|
||||
end
|
||||
|
||||
before { account.enable_features!('channel_voice') }
|
||||
|
||||
it 'reuses the contact via normalized wa_id rather than forking a new ContactInbox' do
|
||||
# Closes the gap: the contact was keyed by BSUID, but the call also carries a phone.
|
||||
# Matching across every source_id reuses the contact instead of forking on the phone.
|
||||
it 'reuses the contact by matching any source_id, not just the first' do
|
||||
call = described_class.perform!(
|
||||
inbox: whatsapp_inbox,
|
||||
from_number: '+554188887777',
|
||||
call_sid: 'wacall_br_1',
|
||||
provider: :whatsapp
|
||||
call_sid: 'wacall_bsuid_1',
|
||||
provider: :whatsapp,
|
||||
caller: { source_ids: ['5541988887777', 'IN.2081978709342942'],
|
||||
contact_attributes: { name: 'Ada Lovelace', phone_number: '+5541988887777' } }
|
||||
)
|
||||
|
||||
expect(call.contact).to eq(stored_contact)
|
||||
|
||||
@@ -74,6 +74,120 @@ describe Whatsapp::IncomingCallService do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'inbound connect from a username (BSUID) caller' do
|
||||
let(:sdp_offer) { "v=0\r\n...sdp..." }
|
||||
let(:bsuid) { 'IN.2081978709342942' }
|
||||
let!(:agent) { create(:user, account: account) }
|
||||
|
||||
before { create(:inbox_member, inbox: inbox, user: agent) }
|
||||
|
||||
it 'keys a phone caller by the phone (matching messaging) even when a BSUID is also present' do
|
||||
allow(ActionCable.server).to receive(:broadcast)
|
||||
|
||||
params = {
|
||||
calls: [{ id: provider_call_id, from: from_number, from_user_id: bsuid, event: 'connect',
|
||||
session: { sdp: sdp_offer, sdp_type: 'offer' } }],
|
||||
contacts: [{ wa_id: from_number, user_id: bsuid, profile: { name: 'Ada Lovelace' } }]
|
||||
}
|
||||
expect { described_class.new(inbox: inbox, params: params).perform }
|
||||
.to change(Call, :count).by(1).and change(Conversation, :count).by(1)
|
||||
|
||||
contact_inbox = Call.last.conversation.contact_inbox
|
||||
expect(contact_inbox.source_id).to eq(from_number)
|
||||
expect(contact_inbox.contact.name).to eq('Ada Lovelace')
|
||||
end
|
||||
|
||||
it 'keys a username-only caller by the BSUID when no phone `from` is present' do
|
||||
allow(ActionCable.server).to receive(:broadcast)
|
||||
|
||||
params = {
|
||||
calls: [{ id: provider_call_id, from_user_id: bsuid, event: 'connect',
|
||||
session: { sdp: sdp_offer, sdp_type: 'offer' } }],
|
||||
contacts: [{ user_id: bsuid, profile: { name: 'Ada Lovelace' } }]
|
||||
}
|
||||
expect { described_class.new(inbox: inbox, params: params).perform }
|
||||
.to change(Call, :count).by(1)
|
||||
|
||||
contact_inbox = Call.last.conversation.contact_inbox
|
||||
expect(contact_inbox.source_id).to eq(bsuid)
|
||||
expect(contact_inbox.contact.name).to eq('Ada Lovelace')
|
||||
end
|
||||
|
||||
it 'reuses the phone-keyed ContactInbox messaging created for a phone caller and backfills the BSUID alias' do
|
||||
allow(ActionCable.server).to receive(:broadcast)
|
||||
contact = create(:contact, account: account)
|
||||
existing = create(:contact_inbox, inbox: inbox, contact: contact, source_id: from_number)
|
||||
|
||||
params = {
|
||||
calls: [{ id: provider_call_id, from: from_number, from_user_id: bsuid, event: 'connect',
|
||||
session: { sdp: sdp_offer, sdp_type: 'offer' } }],
|
||||
contacts: [{ wa_id: from_number, user_id: bsuid }]
|
||||
}
|
||||
# The conversation reuses the existing phone thread; the BSUID alias is backfilled onto the same contact.
|
||||
expect { described_class.new(inbox: inbox, params: params).perform }
|
||||
.to change(Call, :count).by(1).and change(ContactInbox, :count).by(1)
|
||||
|
||||
expect(Call.last.contact).to eq(contact)
|
||||
expect(Call.last.conversation.contact_inbox).to eq(existing)
|
||||
expect(inbox.contact_inboxes.find_by(source_id: bsuid).contact).to eq(contact)
|
||||
end
|
||||
|
||||
it 'reuses a phone ContactInbox via the same wa_id normalization messaging uses' do
|
||||
allow(ActionCable.server).to receive(:broadcast)
|
||||
contact = create(:contact, account: account, phone_number: '+5541988887777')
|
||||
existing = create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5541988887777')
|
||||
|
||||
params = {
|
||||
calls: [{ id: provider_call_id, from: '554188887777', event: 'connect',
|
||||
session: { sdp: sdp_offer, sdp_type: 'offer' } }],
|
||||
contacts: [{ wa_id: '554188887777' }]
|
||||
}
|
||||
expect { described_class.new(inbox: inbox, params: params).perform }
|
||||
.to change(Call, :count).by(1).and not_change(ContactInbox, :count)
|
||||
|
||||
expect(Call.last.conversation.contact_inbox).to eq(existing)
|
||||
end
|
||||
|
||||
it 'reuses the BSUID-keyed ContactInbox messaging created for a username-only caller' do
|
||||
allow(ActionCable.server).to receive(:broadcast)
|
||||
contact = create(:contact, account: account)
|
||||
existing = create(:contact_inbox, inbox: inbox, contact: contact, source_id: bsuid)
|
||||
|
||||
params = {
|
||||
calls: [{ id: provider_call_id, from_user_id: bsuid, event: 'connect',
|
||||
session: { sdp: sdp_offer, sdp_type: 'offer' } }],
|
||||
contacts: [{ user_id: bsuid }]
|
||||
}
|
||||
expect { described_class.new(inbox: inbox, params: params).perform }
|
||||
.to change(Call, :count).by(1).and not_change(ContactInbox, :count)
|
||||
|
||||
expect(Call.last.contact).to eq(contact)
|
||||
expect(Call.last.conversation.contact_inbox).to eq(existing)
|
||||
end
|
||||
|
||||
# The gap: messaging created the contact username-only (BSUID-keyed), and the call now
|
||||
# also exposes a phone. Matching across every source_id reuses the BSUID thread instead
|
||||
# of forking a new phone-keyed contact.
|
||||
it 'reuses a BSUID-keyed ContactInbox even when the call also carries a phone and backfills the phone alias' do
|
||||
allow(ActionCable.server).to receive(:broadcast)
|
||||
contact = create(:contact, account: account)
|
||||
existing = create(:contact_inbox, inbox: inbox, contact: contact, source_id: bsuid)
|
||||
|
||||
params = {
|
||||
calls: [{ id: provider_call_id, from: from_number, from_user_id: bsuid, event: 'connect',
|
||||
session: { sdp: sdp_offer, sdp_type: 'offer' } }],
|
||||
contacts: [{ wa_id: from_number, user_id: bsuid }]
|
||||
}
|
||||
# The conversation reuses the existing BSUID thread; the phone alias is backfilled onto the same contact.
|
||||
expect { described_class.new(inbox: inbox, params: params).perform }
|
||||
.to change(Call, :count).by(1).and change(ContactInbox, :count).by(1)
|
||||
|
||||
expect(Call.last.contact).to eq(contact)
|
||||
expect(Call.last.conversation.contact_inbox).to eq(existing)
|
||||
expect(inbox.contact_inboxes.find_by(source_id: from_number).contact).to eq(contact)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'outbound connect (existing call)' do
|
||||
let!(:call) do
|
||||
conversation = create(:conversation, account: account, inbox: inbox)
|
||||
|
||||
@@ -117,10 +117,16 @@ RSpec.describe Account do
|
||||
|
||||
it 'configures the account feature flag extension column' do
|
||||
expect(described_class.flag_columns).to include('feature_flags', 'feature_flags_ext_1')
|
||||
expect(described_class.flag_mapping['feature_flags_ext_1']).to eq(feature_whatsapp_manual_transfer: 1, feature_data_import: 1 << 1,
|
||||
feature_api_and_webhooks: 1 << 2, feature_whatsapp_reconfigure: 1 << 3)
|
||||
expect(described_class.flag_mapping['feature_flags_ext_1']).to eq(
|
||||
feature_whatsapp_manual_transfer: 1,
|
||||
feature_data_import: 1 << 1,
|
||||
feature_api_and_webhooks: 1 << 2,
|
||||
feature_whatsapp_reconfigure: 1 << 3,
|
||||
feature_whatsapp_embedded_signup_inbox_creation: 1 << 4
|
||||
)
|
||||
expect(described_class.flag_mapping['feature_flags_ext_1'][:feature_whatsapp_manual_transfer]).to eq(1)
|
||||
expect(described_class.flag_mapping['feature_flags_ext_1'][:feature_data_import]).to eq(2)
|
||||
expect(described_class.flag_mapping['feature_flags_ext_1'][:feature_whatsapp_embedded_signup_inbox_creation]).to eq(16)
|
||||
end
|
||||
|
||||
it 'keeps existing feature flags on the original column' do
|
||||
|
||||
@@ -34,8 +34,8 @@ describe Whatsapp::IncomingMessageService do
|
||||
|
||||
it 'appends to last conversation when if conversation already exists' do
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: params[:messages].first[:from])
|
||||
2.times.each { create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox) }
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
2.times.each { create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact) }
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(3)
|
||||
@@ -46,7 +46,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
it 'reopen last conversation if last conversation is resolved and lock to single conversation is enabled' do
|
||||
whatsapp_channel.inbox.update(lock_to_single_conversation: true)
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: params[:messages].first[:from])
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
last_conversation.update(status: 'resolved')
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
@@ -59,7 +59,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
it 'creates a new conversation if last conversation is resolved and lock to single conversation is disabled' do
|
||||
whatsapp_channel.inbox.update(lock_to_single_conversation: false)
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: params[:messages].first[:from])
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
last_conversation.update(status: 'resolved')
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# new conversation should be created
|
||||
@@ -70,7 +70,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
it 'will not create a new conversation if last conversation is not resolved and lock to single conversation is disabled' do
|
||||
whatsapp_channel.inbox.update(lock_to_single_conversation: false)
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: params[:messages].first[:from])
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
last_conversation.update(status: Conversation.statuses.except('resolved').keys.sample)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# new conversation should be created
|
||||
@@ -238,7 +238,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
end
|
||||
|
||||
before do
|
||||
create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
end
|
||||
|
||||
@@ -453,7 +453,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
|
||||
it 'appends to existing contact if contact inbox exists' do
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: wa_id)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(1)
|
||||
@@ -468,7 +468,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
context 'when a contact inbox exists in the old format without 9 included' do
|
||||
it 'appends to existing contact' do
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: wa_id)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(1)
|
||||
@@ -480,7 +480,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
context 'when a contact inbox exists in the new format with 9 included' do
|
||||
it 'appends to existing contact' do
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: '5541988887777')
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(1)
|
||||
@@ -515,7 +515,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
# Normalized format removes the 9 after country code
|
||||
normalized_wa_id = '541123456789'
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: normalized_wa_id)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(1)
|
||||
@@ -532,7 +532,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
context 'when a contact inbox exists with the same format' do
|
||||
it 'appends to existing contact' do
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: wa_id)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(1)
|
||||
|
||||
Reference in New Issue
Block a user