Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Sms::DeliveryStatusService do
|
||||
describe '#perform' do
|
||||
let!(:account) { create(:account) }
|
||||
let!(:sms_channel) { create(:channel_sms) }
|
||||
let!(:contact) { create(:contact, account: account, phone_number: '+12345') }
|
||||
let(:contact_inbox) { create(:contact_inbox, source_id: '+12345', contact: contact, inbox: sms_channel.inbox) }
|
||||
let!(:conversation) { create(:conversation, contact: contact, inbox: sms_channel.inbox, contact_inbox: contact_inbox) }
|
||||
|
||||
describe '#perform' do
|
||||
context 'when message delivery status is fired' do
|
||||
before do
|
||||
create(:message, account: account, inbox: sms_channel.inbox, conversation: conversation, status: :sent,
|
||||
source_id: 'SMd560ac79e4a4d36b3ce59f1f50471986')
|
||||
end
|
||||
|
||||
it 'updates the message if the message status is delivered' do
|
||||
params = {
|
||||
time: '2022-02-02T23:14:05.309Z',
|
||||
type: 'message-delivered',
|
||||
to: sms_channel.phone_number,
|
||||
description: 'ok',
|
||||
message: {
|
||||
'id': conversation.messages.last.source_id
|
||||
}
|
||||
}
|
||||
|
||||
described_class.new(params: params, inbox: sms_channel.inbox).perform
|
||||
expect(conversation.reload.messages.last.status).to eq('delivered')
|
||||
end
|
||||
|
||||
it 'updates the message if the message status is failed' do
|
||||
params = {
|
||||
time: '2022-02-02T23:14:05.309Z',
|
||||
type: 'message-failed',
|
||||
to: sms_channel.phone_number,
|
||||
description: 'Undeliverable',
|
||||
errorCode: 995,
|
||||
message: {
|
||||
'id': conversation.messages.last.source_id
|
||||
}
|
||||
}
|
||||
|
||||
described_class.new(params: params, inbox: sms_channel.inbox).perform
|
||||
expect(conversation.reload.messages.last.status).to eq('failed')
|
||||
|
||||
expect(conversation.reload.messages.last.external_error).to eq('995 - Undeliverable')
|
||||
end
|
||||
|
||||
it 'does not update the message if the status is not a support status' do
|
||||
params = {
|
||||
time: '2022-02-02T23:14:05.309Z',
|
||||
type: 'queued',
|
||||
to: sms_channel.phone_number,
|
||||
description: 'ok',
|
||||
message: {
|
||||
'id': conversation.messages.last.source_id
|
||||
}
|
||||
}
|
||||
|
||||
described_class.new(params: params, inbox: sms_channel.inbox).perform
|
||||
expect(conversation.reload.messages.last.status).to eq('sent')
|
||||
end
|
||||
|
||||
it 'does not update the message if the message is not present' do
|
||||
params = {
|
||||
time: '2022-02-02T23:14:05.309Z',
|
||||
type: 'message-delivered',
|
||||
to: sms_channel.phone_number,
|
||||
description: 'ok',
|
||||
message: {
|
||||
'id': '123'
|
||||
}
|
||||
}
|
||||
|
||||
described_class.new(params: params, inbox: sms_channel.inbox).perform
|
||||
expect(conversation.reload.messages.last.status).to eq('sent')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,97 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Sms::IncomingMessageService do
|
||||
describe '#perform' do
|
||||
let!(:sms_channel) { create(:channel_sms) }
|
||||
let(:params) do
|
||||
{
|
||||
|
||||
'id': '3232420-2323-234324',
|
||||
'owner': sms_channel.phone_number,
|
||||
'applicationId': '2342349-324234d-32432432',
|
||||
'time': '2022-02-02T23:14:05.262Z',
|
||||
'segmentCount': 1,
|
||||
'direction': 'in',
|
||||
'to': [
|
||||
sms_channel.phone_number
|
||||
],
|
||||
'from': '+14234234234',
|
||||
'text': 'test message'
|
||||
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
context 'when valid text message params' do
|
||||
it 'creates appropriate conversations, message and contacts' do
|
||||
described_class.new(inbox: sms_channel.inbox, params: params).perform
|
||||
expect(sms_channel.inbox.conversations.count).not_to eq(0)
|
||||
expect(Contact.all.first.name).to eq('+1 423-423-4234')
|
||||
expect(sms_channel.inbox.messages.first.content).to eq(params[:text])
|
||||
end
|
||||
|
||||
it 'appends to last conversation when if conversation already exisits' do
|
||||
contact_inbox = create(:contact_inbox, inbox: sms_channel.inbox, source_id: params[:from])
|
||||
2.times.each { create(:conversation, inbox: sms_channel.inbox, contact_inbox: contact_inbox) }
|
||||
last_conversation = create(:conversation, inbox: sms_channel.inbox, contact_inbox: contact_inbox)
|
||||
described_class.new(inbox: sms_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(sms_channel.inbox.conversations.count).to eq(3)
|
||||
# message appended to the last conversation
|
||||
expect(last_conversation.messages.last.content).to eq(params[:text])
|
||||
end
|
||||
|
||||
it 'reopen last conversation if last conversation is resolved and lock to single conversation is enabled' do
|
||||
sms_channel.inbox.update(lock_to_single_conversation: true)
|
||||
contact_inbox = create(:contact_inbox, inbox: sms_channel.inbox, source_id: params[:from])
|
||||
last_conversation = create(:conversation, inbox: sms_channel.inbox, contact_inbox: contact_inbox)
|
||||
last_conversation.update(status: 'resolved')
|
||||
described_class.new(inbox: sms_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(sms_channel.inbox.conversations.count).to eq(1)
|
||||
expect(sms_channel.inbox.conversations.open.last.messages.last.content).to eq(params[:text])
|
||||
expect(sms_channel.inbox.conversations.open.last.status).to eq('open')
|
||||
end
|
||||
|
||||
it 'creates a new conversation if last conversation is resolved and lock to single conversation is disabled' do
|
||||
sms_channel.inbox.update(lock_to_single_conversation: false)
|
||||
contact_inbox = create(:contact_inbox, inbox: sms_channel.inbox, source_id: params[:from])
|
||||
last_conversation = create(:conversation, inbox: sms_channel.inbox, contact_inbox: contact_inbox)
|
||||
last_conversation.update(status: 'resolved')
|
||||
described_class.new(inbox: sms_channel.inbox, params: params).perform
|
||||
# new conversation should be created
|
||||
expect(sms_channel.inbox.conversations.count).to eq(2)
|
||||
# message appended to the last conversation
|
||||
expect(contact_inbox.conversations.last.messages.last.content).to eq(params[:text])
|
||||
end
|
||||
|
||||
it 'will not create a new conversation if last conversation is not resolved and lock to single conversation is disabled' do
|
||||
sms_channel.inbox.update(lock_to_single_conversation: false)
|
||||
contact_inbox = create(:contact_inbox, inbox: sms_channel.inbox, source_id: params[:from])
|
||||
last_conversation = create(:conversation, inbox: sms_channel.inbox, contact_inbox: contact_inbox)
|
||||
last_conversation.update(status: Conversation.statuses.except('resolved').keys.sample)
|
||||
described_class.new(inbox: sms_channel.inbox, params: params).perform
|
||||
# new conversation should be created
|
||||
expect(sms_channel.inbox.conversations.count).to eq(1)
|
||||
# message appended to the last conversation
|
||||
expect(contact_inbox.conversations.last.messages.last.content).to eq(params[:text])
|
||||
end
|
||||
|
||||
it 'creates attachment messages and ignores .smil files' do
|
||||
stub_request(:get, 'http://test.com/test.png').to_return(status: 200, body: File.read('spec/assets/sample.png'), headers: {})
|
||||
stub_request(:get, 'http://test.com/test2.png').to_return(status: 200, body: File.read('spec/assets/sample.png'), headers: {})
|
||||
|
||||
media_params = { 'media': [
|
||||
'http://test.com/test.smil',
|
||||
'http://test.com/test.png',
|
||||
'http://test.com/test2.png'
|
||||
] }.with_indifferent_access
|
||||
|
||||
described_class.new(inbox: sms_channel.inbox, params: params.merge(media_params)).perform
|
||||
expect(sms_channel.inbox.conversations.count).not_to eq(0)
|
||||
expect(Contact.all.first.name).to eq('+1 423-423-4234')
|
||||
expect(sms_channel.inbox.messages.first.content).to eq('test message')
|
||||
expect(sms_channel.inbox.messages.first.attachments.present?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,73 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Sms::OneoffSmsCampaignService do
|
||||
subject(:sms_campaign_service) { described_class.new(campaign: campaign) }
|
||||
|
||||
let(:account) { create(:account) }
|
||||
let!(:sms_channel) { create(:channel_sms, account: account) }
|
||||
let!(:sms_inbox) { create(:inbox, channel: sms_channel, account: account) }
|
||||
let(:label1) { create(:label, account: account) }
|
||||
let(:label2) { create(:label, account: account) }
|
||||
let!(:campaign) do
|
||||
create(:campaign, inbox: sms_inbox, account: account,
|
||||
audience: [{ type: 'Label', id: label1.id }, { type: 'Label', id: label2.id }])
|
||||
end
|
||||
|
||||
describe 'perform' do
|
||||
before do
|
||||
stub_request(:post, 'https://messaging.bandwidth.com/api/v2/users/1/messages').to_return(
|
||||
status: 200,
|
||||
body: { 'id' => '1' }.to_json,
|
||||
headers: {}
|
||||
)
|
||||
allow_any_instance_of(described_class).to receive(:channel).and_return(sms_channel) # rubocop:disable RSpec/AnyInstance
|
||||
end
|
||||
|
||||
it 'raises error if the campaign is completed' do
|
||||
campaign.completed!
|
||||
|
||||
expect { sms_campaign_service.perform }.to raise_error 'Completed Campaign'
|
||||
end
|
||||
|
||||
it 'raises error invalid campaign when its not a oneoff sms campaign' do
|
||||
campaign = create(:campaign)
|
||||
|
||||
expect { described_class.new(campaign: campaign).perform }.to raise_error "Invalid campaign #{campaign.id}"
|
||||
end
|
||||
|
||||
it 'send messages to contacts in the audience and marks the campaign completed' do
|
||||
contact_with_label1, contact_with_label2, contact_with_both_labels = FactoryBot.create_list(:contact, 3, :with_phone_number, account: account)
|
||||
contact_with_label1.update_labels([label1.title])
|
||||
contact_with_label2.update_labels([label2.title])
|
||||
contact_with_both_labels.update_labels([label1.title, label2.title])
|
||||
sms_campaign_service.perform
|
||||
assert_requested(:post, 'https://messaging.bandwidth.com/api/v2/users/1/messages', times: 3)
|
||||
expect(campaign.reload.completed?).to be true
|
||||
end
|
||||
|
||||
it 'uses liquid template service to process campaign message' do
|
||||
contact = create(:contact, :with_phone_number, account: account)
|
||||
contact.update_labels([label1.title])
|
||||
|
||||
expect(Liquid::CampaignTemplateService).to receive(:new).with(campaign: campaign, contact: contact).and_call_original
|
||||
|
||||
sms_campaign_service.perform
|
||||
end
|
||||
|
||||
it 'continues processing contacts when sending message raises an error' do
|
||||
contact_error, contact_success = FactoryBot.create_list(:contact, 2, :with_phone_number, account: account)
|
||||
contact_error.update_labels([label1.title])
|
||||
contact_success.update_labels([label1.title])
|
||||
|
||||
error_message = 'SMS provider error'
|
||||
|
||||
expect(sms_channel).to receive(:send_text_message).with(contact_error.phone_number, anything).and_raise(StandardError, error_message)
|
||||
expect(sms_channel).to receive(:send_text_message).with(contact_success.phone_number, anything).and_return(nil)
|
||||
|
||||
expect(Rails.logger).to receive(:error).with("[SMS Campaign #{campaign.id}] Failed to send to #{contact_error.phone_number}: #{error_message}")
|
||||
|
||||
sms_campaign_service.perform
|
||||
expect(campaign.reload.completed?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,53 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Sms::SendOnSmsService do
|
||||
describe '#perform' do
|
||||
context 'when a valid message' do
|
||||
let(:sms_request) { double }
|
||||
let!(:sms_channel) { create(:channel_sms) }
|
||||
let!(:contact_inbox) { create(:contact_inbox, inbox: sms_channel.inbox, source_id: '+123456789') }
|
||||
let!(:conversation) { create(:conversation, contact_inbox: contact_inbox, inbox: sms_channel.inbox) }
|
||||
|
||||
it 'calls channel.send_message' do
|
||||
message = create(:message, message_type: :outgoing, content: 'test',
|
||||
conversation: conversation)
|
||||
allow(HTTParty).to receive(:post).and_return(sms_request)
|
||||
allow(sms_request).to receive(:success?).and_return(true)
|
||||
allow(sms_request).to receive(:parsed_response).and_return({ 'id' => '123456789' })
|
||||
expect(HTTParty).to receive(:post).with(
|
||||
'https://messaging.bandwidth.com/api/v2/users/1/messages',
|
||||
basic_auth: { username: '1', password: '1' },
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
body: { 'to' => '+123456789', 'from' => sms_channel.phone_number, 'text' => 'test', 'applicationId' => '1' }.to_json
|
||||
)
|
||||
described_class.new(message: message).perform
|
||||
expect(message.reload.source_id).to eq('123456789')
|
||||
end
|
||||
|
||||
it 'calls channel.send_message with attachments' do
|
||||
message = build(:message, message_type: :outgoing, content: 'test',
|
||||
conversation: conversation)
|
||||
attachment = message.attachments.new(account_id: message.account_id, file_type: :image)
|
||||
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
||||
attachment2 = message.attachments.new(account_id: message.account_id, file_type: :image)
|
||||
attachment2.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
||||
message.save!
|
||||
|
||||
allow(HTTParty).to receive(:post).and_return(sms_request)
|
||||
allow(sms_request).to receive(:success?).and_return(true)
|
||||
allow(sms_request).to receive(:parsed_response).and_return({ 'id' => '123456789' })
|
||||
allow(attachment).to receive(:download_url).and_return('url1')
|
||||
allow(attachment2).to receive(:download_url).and_return('url2')
|
||||
expect(HTTParty).to receive(:post).with(
|
||||
'https://messaging.bandwidth.com/api/v2/users/1/messages',
|
||||
basic_auth: { username: '1', password: '1' },
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
body: { 'to' => '+123456789', 'from' => sms_channel.phone_number, 'text' => 'test', 'applicationId' => '1',
|
||||
'media' => %w[url1 url2] }.to_json
|
||||
)
|
||||
described_class.new(message: message).perform
|
||||
expect(message.reload.source_id).to eq('123456789')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user