From 1cf8ec8969116526e46ad271f1a0bc105f57f30e Mon Sep 17 00:00:00 2001 From: Rolf Schmidt Date: Mon, 16 Aug 2021 16:52:36 +0200 Subject: [PATCH] Fixes #3672 - MessageBird integration --- .rubocop/todo.rspec.yml | 3 + .rubocop/todo.yml | 5 +- Gemfile | 1 + Gemfile.lock | 2 + app/controllers/channels_sms_controller.rb | 2 + app/models/channel/driver/sms/base.rb | 68 +++++++ .../channel/driver/sms/massenversand.rb | 25 ++- app/models/channel/driver/sms/message_bird.rb | 110 ++++++++++ app/models/channel/driver/sms/twilio.rb | 105 ++++------ script/messagebird.rb | 6 + .../channel/driver/sms/message_bird_spec.rb | 68 +++++++ .../integration/message_bird_sms_spec.rb | 188 ++++++++++++++++++ test/data/message_bird/inbound_sms1.json | 20 ++ test/data/message_bird/inbound_sms2.json | 20 ++ test/data/message_bird/inbound_sms3.json | 20 ++ 15 files changed, 562 insertions(+), 81 deletions(-) create mode 100644 app/models/channel/driver/sms/base.rb create mode 100644 app/models/channel/driver/sms/message_bird.rb create mode 100644 script/messagebird.rb create mode 100644 spec/models/channel/driver/sms/message_bird_spec.rb create mode 100644 spec/requests/integration/message_bird_sms_spec.rb create mode 100644 test/data/message_bird/inbound_sms1.json create mode 100644 test/data/message_bird/inbound_sms2.json create mode 100644 test/data/message_bird/inbound_sms3.json diff --git a/.rubocop/todo.rspec.yml b/.rubocop/todo.rspec.yml index c9c01c984..71ba50ab6 100644 --- a/.rubocop/todo.rspec.yml +++ b/.rubocop/todo.rspec.yml @@ -284,6 +284,7 @@ RSpec/ExampleLength: - 'spec/requests/integration/smime_spec.rb' - 'spec/requests/integration/telegram_spec.rb' - 'spec/requests/integration/twilio_sms_spec.rb' + - 'spec/requests/integration/message_bird_sms_spec.rb' - 'spec/requests/integration/user_device_spec.rb' - 'spec/requests/knowledge_base/answer_attachments_cloning_spec.rb' - 'spec/requests/links_spec.rb' @@ -315,6 +316,7 @@ RSpec/ExpectActual: - 'spec/requests/integration/monitoring_spec.rb' - 'spec/requests/integration/object_manager_attributes_spec.rb' - 'spec/requests/integration/twilio_sms_spec.rb' + - 'spec/requests/integration/message_bird_sms_spec.rb' - 'spec/requests/integration/user_device_spec.rb' - 'spec/requests/organization_spec.rb' - 'spec/requests/ticket/article_attachments_spec.rb' @@ -565,6 +567,7 @@ RSpec/MultipleExpectations: - 'spec/requests/integration/smime_spec.rb' - 'spec/requests/integration/telegram_spec.rb' - 'spec/requests/integration/twilio_sms_spec.rb' + - 'spec/requests/integration/message_bird_sms_spec.rb' - 'spec/requests/integration/user_device_spec.rb' - 'spec/requests/links_spec.rb' - 'spec/requests/long_polling_spec.rb' diff --git a/.rubocop/todo.yml b/.rubocop/todo.yml index 2d30d7496..c841e288d 100644 --- a/.rubocop/todo.yml +++ b/.rubocop/todo.yml @@ -105,8 +105,6 @@ Metrics/AbcSize: - 'app/models/channel/driver/imap.rb' - 'app/models/channel/driver/pop3.rb' - 'app/models/channel/driver/sendmail.rb' - - 'app/models/channel/driver/sms/massenversand.rb' - - 'app/models/channel/driver/sms/twilio.rb' - 'app/models/channel/driver/smtp.rb' - 'app/models/channel/driver/twitter.rb' - 'app/models/channel/email_build.rb' @@ -509,7 +507,6 @@ Metrics/CyclomaticComplexity: - 'app/models/channel/driver/facebook.rb' - 'app/models/channel/driver/imap.rb' - 'app/models/channel/driver/pop3.rb' - - 'app/models/channel/driver/sms/twilio.rb' - 'app/models/channel/driver/smtp.rb' - 'app/models/channel/driver/twitter.rb' - 'app/models/channel/email_build.rb' @@ -743,7 +740,6 @@ Metrics/PerceivedComplexity: - 'app/models/channel/driver/facebook.rb' - 'app/models/channel/driver/imap.rb' - 'app/models/channel/driver/pop3.rb' - - 'app/models/channel/driver/sms/twilio.rb' - 'app/models/channel/driver/smtp.rb' - 'app/models/channel/driver/twitter.rb' - 'app/models/channel/email_build.rb' @@ -921,6 +917,7 @@ Style/OptionalBooleanParameter: - 'app/models/channel/driver/facebook.rb' - 'app/models/channel/driver/sendmail.rb' - 'app/models/channel/driver/sms/massenversand.rb' + - 'app/models/channel/driver/sms/message_bird.rb' - 'app/models/channel/driver/sms/twilio.rb' - 'app/models/channel/driver/smtp.rb' - 'app/models/channel/driver/telegram.rb' diff --git a/Gemfile b/Gemfile index b3ada3b46..f87a9edf5 100644 --- a/Gemfile +++ b/Gemfile @@ -127,6 +127,7 @@ gem 'icalendar-recurrence' gem 'telephone_number' # feature - SMS +gem 'messagebird-rest' gem 'twilio-ruby', require: false # feature - ordering diff --git a/Gemfile.lock b/Gemfile.lock index 4e98d9475..4265b478c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -313,6 +313,7 @@ GEM marcel (1.0.1) memoizable (0.4.2) thread_safe (~> 0.3, >= 0.3.1) + messagebird-rest (2.0.0) method_source (1.0.0) mime-types (3.3.1) mime-types-data (~> 3.2015) @@ -692,6 +693,7 @@ DEPENDENCIES koala libv8 mail! + messagebird-rest mime-types mini_racer (= 0.2.9) mysql2 diff --git a/app/controllers/channels_sms_controller.rb b/app/controllers/channels_sms_controller.rb index b82c8fdd9..7a8edb11d 100644 --- a/app/controllers/channels_sms_controller.rb +++ b/app/controllers/channels_sms_controller.rb @@ -98,6 +98,8 @@ class ChannelsSmsController < ApplicationController list = [] Dir.glob(Rails.root.join('app/models/channel/driver/sms/*.rb')).each do |path| filename = File.basename(path) + next if !Channel.driver_class("sms/#{filename}").const_defined?(:NAME) + list.push Channel.driver_class("sms/#{filename}").definition end list diff --git a/app/models/channel/driver/sms/base.rb b/app/models/channel/driver/sms/base.rb new file mode 100644 index 000000000..d4c27e962 --- /dev/null +++ b/app/models/channel/driver/sms/base.rb @@ -0,0 +1,68 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +class Channel::Driver::Sms::Base + def user_by_cti(mobile) + _from_comment, preferences = Cti::CallerId.get_comment_preferences(mobile, 'from') + user = nil + if preferences && preferences['from'] && preferences['from'][0] && preferences['from'][0]['level'] == 'known' && preferences['from'][0]['object'] == 'User' + user = User.find_by(id: preferences['from'][0]['o_id']) + end + user + end + + def user_by_mobile(mobile) + User.where(mobile: mobile).order(:updated_at).first || user_by_cti(mobile) || User.create!( + firstname: mobile, + mobile: mobile, + ) + end + + def article_type_sms + Ticket::Article::Type.find_by(name: 'sms') + end + + def closed_ids + Ticket::State.where(name: %w[closed merged removed]).pluck(:id) + end + + def ensure_ticket_followup_state(ticket) + return if !ticket + + new_state = Ticket::State.find_by(default_create: true) + return if ticket.state_id == new_state.id + + ticket.state = Ticket::State.find_by(default_follow_up: true) + ticket.save! + end + + def find_open_sms_ticket(user) + ticket = Ticket.where(customer_id: user.id, create_article_type_id: article_type_sms.id).where.not(state_id: closed_ids).order(:updated_at).first + ensure_ticket_followup_state(ticket) + ticket + end + + def ensure_group!(channel) + raise Exceptions::UnprocessableEntity, 'Group needed in channel definition!' if channel.group_id.blank? + + group = Group.find_by(id: channel.group_id) + raise Exceptions::UnprocessableEntity, 'Group is invalid!' if !group + end + + def cut_title(title) + if title.length > 40 + title = "#{title[0, 40]}..." + end + title + end + + def process_ticket(attr, channel, user) + ticket = find_open_sms_ticket(user) + if !ticket + ensure_group!(channel) + + ticket = create_ticket(attr, channel, user) + end + + create_article(attr, channel, ticket) + end +end diff --git a/app/models/channel/driver/sms/massenversand.rb b/app/models/channel/driver/sms/massenversand.rb index f1841c5f6..4839192ce 100644 --- a/app/models/channel/driver/sms/massenversand.rb +++ b/app/models/channel/driver/sms/massenversand.rb @@ -10,17 +10,7 @@ class Channel::Driver::Sms::Massenversand Rails.logger.info "Backend sending Massenversand SMS to #{attr[:recipient]}" begin - url = build_url(options, attr) - - if Setting.get('developer_mode') != true - response = Faraday.get(url).body - - if !response.match?('OK') - message = "Received non-OK response from gateway URL '#{url}'" - Rails.logger.error "#{message}: #{response.inspect}" - raise message - end - end + send_create(options, attr) true rescue => e @@ -31,6 +21,19 @@ class Channel::Driver::Sms::Massenversand end end + def send_create(options, attr) + url = build_url(options, attr) + + return if Setting.get('developer_mode') + + response = Faraday.get(url).body + return if response.match?('OK') + + message = "Received non-OK response from gateway URL '#{url}'" + Rails.logger.error "#{message}: #{response.inspect}" + raise message + end + def self.definition { name: 'Massenversand', diff --git a/app/models/channel/driver/sms/message_bird.rb b/app/models/channel/driver/sms/message_bird.rb new file mode 100644 index 000000000..be885eb24 --- /dev/null +++ b/app/models/channel/driver/sms/message_bird.rb @@ -0,0 +1,110 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +class Channel::Driver::Sms::MessageBird < Channel::Driver::Sms::Base + NAME = 'sms/message_bird'.freeze + + def fetchable?(_channel) + false + end + + def send(options, attr, _notification = false) + Rails.logger.info "Sending SMS to recipient #{attr[:recipient]}" + + return true if Setting.get('import_mode') + + Rails.logger.info "Backend sending MessageBird SMS to #{attr[:recipient]}" + begin + send_create(options, attr) + true + rescue => e + Rails.logger.debug { "MessageBird error: #{e.inspect}" } + raise e + end + end + + def send_create(options, attr) + return if Setting.get('developer_mode') + + api(options).message_create(options[:sender], attr[:recipient], attr[:message]) + end + + def process(_options, attr, channel) + Rails.logger.info "Receiving SMS frim recipient #{attr['originator']}" + + # prevent already created articles + if attr['message_id'].present? && Ticket::Article.exists?(message_id: attr['message_id']) + return [:json, {}] + end + + # find sender + user = user_by_mobile(attr['originator']) + UserInfo.current_user_id = user.id + + process_ticket(attr, channel, user) + + [:json, {}] + end + + def create_ticket(attr, channel, user) + title = cut_title(attr['incomingMessage']) + ticket = Ticket.new( + group_id: channel.group_id, + title: title, + state_id: Ticket::State.find_by(default_create: true).id, + priority_id: Ticket::Priority.find_by(default_create: true).id, + customer_id: user.id, + preferences: { + channel_id: channel.id, + sms: { + originator: attr['originator'], + recipient: attr['recipient'], + } + } + ) + ticket.save! + ticket + end + + def create_article(attr, channel, ticket) + Ticket::Article.create!( + ticket_id: ticket.id, + type: article_type_sms, + sender: Ticket::Article::Sender.find_by(name: 'Customer'), + body: attr['incomingMessage'], + from: attr['originator'], + to: attr['recipient'], + message_id: attr['message_id'], + content_type: 'text/plain', + preferences: { + channel_id: channel.id, + sms: { + reference: attr['reference'], + } + } + ) + end + + def self.definition + { + name: 'message_bird', + adapter: 'sms/message_bird', + account: [ + { name: 'options::webhook_token', display: 'Webhook Token', tag: 'input', type: 'text', limit: 200, null: false, default: Digest::MD5.hexdigest(rand(999_999_999_999).to_s), disabled: true, readonly: true }, + { name: 'options::token', display: 'Token', tag: 'input', type: 'text', limit: 255, null: false }, + { name: 'options::sender', display: 'Sender', tag: 'input', type: 'text', limit: 200, null: false, placeholder: '+491710000000' }, + { name: 'group_id', display: 'Destination Group', tag: 'select', null: false, relation: 'Group', nulloption: true, filter: { active: true } }, + ], + notification: [ + { name: 'options::token', display: 'Token', tag: 'input', type: 'text', limit: 255, null: false }, + { name: 'options::sender', display: 'Sender', tag: 'input', type: 'text', limit: 200, null: false, placeholder: '+491710000000' }, + ], + } + end + + private + + def api(options) + require 'messagebird' + @api ||= ::MessageBird::Client.new(options[:token]) + end +end diff --git a/app/models/channel/driver/sms/twilio.rb b/app/models/channel/driver/sms/twilio.rb index 144fbe4b8..aac66f396 100644 --- a/app/models/channel/driver/sms/twilio.rb +++ b/app/models/channel/driver/sms/twilio.rb @@ -1,6 +1,6 @@ # Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ -class Channel::Driver::Sms::Twilio +class Channel::Driver::Sms::Twilio < Channel::Driver::Sms::Base NAME = 'sms/twilio'.freeze def fetchable?(_channel) @@ -14,15 +14,7 @@ class Channel::Driver::Sms::Twilio Rails.logger.info "Backend sending Twilio SMS to #{attr[:recipient]}" begin - if Setting.get('developer_mode') != true - result = api(options).messages.create( - from: options[:sender], - to: attr[:recipient], - body: attr[:message], - ) - - raise result.error_message if result&.error_code&.positive? - end + send_create(options, attr) true rescue => e @@ -31,6 +23,18 @@ class Channel::Driver::Sms::Twilio end end + def send_create(options, attr) + return if Setting.get('developer_mode') + + result = api(options).messages.create( + from: options[:sender], + to: attr[:recipient], + body: attr[:message], + ) + + raise result.error_message if result&.error_code&.positive? + end + def process(_options, attr, channel) Rails.logger.info "Receiving SMS frim recipient #{attr[:From]}" @@ -40,65 +44,37 @@ class Channel::Driver::Sms::Twilio return ['application/xml; charset=UTF-8;', Twilio::TwiML::MessagingResponse.new.to_s] end - # find sender - user = User.where(mobile: attr[:From]).order(:updated_at).first - if !user - _from_comment, preferences = Cti::CallerId.get_comment_preferences(attr[:From], 'from') - if preferences && preferences['from'] && preferences['from'][0] && preferences['from'][0]['level'] == 'known' && preferences['from'][0]['object'] == 'User' - user = User.find_by(id: preferences['from'][0]['o_id']) - end - end - if !user - user = User.create!( - firstname: attr[:From], - mobile: attr[:From], - ) - end - + user = user_by_mobile(attr[:From]) UserInfo.current_user_id = user.id - # find ticket - article_type_sms = Ticket::Article::Type.find_by(name: 'sms') - state_ids = Ticket::State.where(name: %w[closed merged removed]).pluck(:id) - ticket = Ticket.where(customer_id: user.id, create_article_type_id: article_type_sms.id).where.not(state_id: state_ids).order(:updated_at).first - if ticket - new_state = Ticket::State.find_by(default_create: true) - if ticket.state_id != new_state.id - ticket.state = Ticket::State.find_by(default_follow_up: true) - ticket.save! - end - else - if channel.group_id.blank? - raise Exceptions::UnprocessableEntity, 'Group needed in channel definition!' - end + process_ticket(attr, channel, user) - group = Group.find_by(id: channel.group_id) - if !group - raise Exceptions::UnprocessableEntity, 'Group is invalid!' - end + require 'twilio-ruby' # Only load this gem when it is really used + ['application/xml; charset=UTF-8;', Twilio::TwiML::MessagingResponse.new.to_s] + end - title = attr[:Body] - if title.length > 40 - title = "#{title[0, 40]}..." - end - ticket = Ticket.new( - group_id: channel.group_id, - title: title, - state_id: Ticket::State.find_by(default_create: true).id, - priority_id: Ticket::Priority.find_by(default_create: true).id, - customer_id: user.id, - preferences: { - channel_id: channel.id, - sms: { - AccountSid: attr['AccountSid'], - From: attr['From'], - To: attr['To'], - } + def create_ticket(attr, channel, user) + title = cut_title(attr[:Body]) + ticket = Ticket.new( + group_id: channel.group_id, + title: title, + state_id: Ticket::State.find_by(default_create: true).id, + priority_id: Ticket::Priority.find_by(default_create: true).id, + customer_id: user.id, + preferences: { + channel_id: channel.id, + sms: { + AccountSid: attr['AccountSid'], + From: attr['From'], + To: attr['To'], } - ) - ticket.save! - end + } + ) + ticket.save! + ticket + end + def create_article(attr, channel, ticket) Ticket::Article.create!( ticket_id: ticket.id, type: article_type_sms, @@ -117,9 +93,6 @@ class Channel::Driver::Sms::Twilio } } ) - - require 'twilio-ruby' # Only load this gem when it is really used - ['application/xml; charset=UTF-8;', Twilio::TwiML::MessagingResponse.new.to_s] end def self.definition diff --git a/script/messagebird.rb b/script/messagebird.rb new file mode 100644 index 000000000..a76539158 --- /dev/null +++ b/script/messagebird.rb @@ -0,0 +1,6 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +require 'messagebird' + +client = MessageBird::Client.new('OSR2zUFd14Nd5snb8zmEQYoBx') +client.message_create('Zammad GmbH', '+4917670333748', 'This is a test messageNEW', reference: 'Foobar') diff --git a/spec/models/channel/driver/sms/message_bird_spec.rb b/spec/models/channel/driver/sms/message_bird_spec.rb new file mode 100644 index 000000000..1b0fe23f5 --- /dev/null +++ b/spec/models/channel/driver/sms/message_bird_spec.rb @@ -0,0 +1,68 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +require 'rails_helper' +require 'messagebird' + +RSpec.describe Channel::Driver::Sms::MessageBird do + it 'passes' do + channel = create_channel + + stub_request(:post, url_to_mock) + .to_return(body: mocked_response_success) + + api = channel.driver_instance.new + expect(api.send(channel.options, { recipient: '+37060010000', message: message_body })).to be true + end + + it 'fails' do + channel = create_channel + + stub_request(:post, url_to_mock) + .to_return(status: 400, body: mocked_response_failure) + + api = channel.driver_instance.new + + expect { api.send(channel.options, { recipient: 'asd', message: message_body }) }.to raise_exception(::MessageBird::ServerException) + end + + private + + def create_channel + FactoryBot.create(:channel, + options: { + adapter: 'sms/message_bird', + sender: sender_number, + token: token + }, + created_by_id: 1, + updated_by_id: 1) + end + + # api parameters + + def url_to_mock + 'https://rest.messagebird.com/messages' + end + + def message_body + 'Test' + end + + def sender_number + '+15005550006' + end + + def token + '2345r4erfdvc4wedxv3efds' + end + + # mocked responses + + def mocked_response_success + '{"id":"1e8cc35873d14fe4ab18bd97a412121","href":"https://rest.messagebird.com/messages/1e8cc35873d14fe4ab18bd121212f971a","direction":"mt","type":"sms","originator":"Zammad GmbH","body":"This is a test messageNEW","reference":"Foobar","validity":null,"gateway":10,"typeDetails":{},"datacoding":"plain","mclass":1,"scheduledDatetime":null,"createdDatetime":"2021-07-22T13:25:03+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":0,"totalDeliveryFailedCount":0,"items":[{"recipient":491234,"status":"sent","statusDatetime":"2021-07-22T13:25:03+00:00","messagePartCount":1}]}}' + end + + def mocked_response_failure + '{"errors":[{"code":9,"description":"no (correct) recipients found","parameter":"recipient"}]}' + end +end diff --git a/spec/requests/integration/message_bird_sms_spec.rb b/spec/requests/integration/message_bird_sms_spec.rb new file mode 100644 index 000000000..c238643ad --- /dev/null +++ b/spec/requests/integration/message_bird_sms_spec.rb @@ -0,0 +1,188 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +require 'rails_helper' + +RSpec.describe 'Message Bird SMS', type: :request do + + describe 'request handling' do + + let(:agent) do + create(:agent, groups: Group.all) + end + + it 'does basic call' do + + # configure twilio channel + group_id = Group.find_by(name: 'Users').id + + UserInfo.current_user_id = 1 + channel = create( + :channel, + area: 'Sms::Account', + options: { + adapter: 'sms/message_bird', + webhook_token: 'f409460e50f76d331fdac8ba7b7963b6', + token: '223', + sender: '333', + }, + group_id: nil, + ) + + # process inbound sms + post '/api/v1/sms_webhook', params: read_message('inbound_sms1'), as: :json + expect(response).to have_http_status(:not_found) + + post '/api/v1/sms_webhook/not_existing', params: read_message('inbound_sms1'), as: :json + expect(response).to have_http_status(:not_found) + + post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms1'), as: :json + expect(response).to have_http_status(:unprocessable_entity) + expect(json_response['error']).to eq('Can\'t use Channel::Driver::Sms::MessageBird: #') + + channel.group_id = Group.first.id + channel.save! + + post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms1'), as: :json + expect(response).to have_http_status(:ok) + + ticket = Ticket.last + article = Ticket::Article.last + customer = User.last + expect(ticket.articles.count).to eq(1) + expect(ticket.title).to eq('Ldfhxhcuffufuf. Fifififig. Fifififiif F...') + expect(ticket.state.name).to eq('new') + expect(ticket.group_id).to eq(group_id) + expect(ticket.customer_id).to eq(customer.id) + expect(ticket.created_by_id).to eq(customer.id) + expect(article.from).to eq('+491710000000') + expect(article.to).to eq('+4915700000000') + expect(article.cc).to be_nil + expect(article.subject).to be_nil + expect(article.body).to eq('Ldfhxhcuffufuf. Fifififig. Fifififiif Fifififiif Fifififiif Fifififiif Fifififiif') + expect(article.created_by_id).to eq(customer.id) + expect(article.sender.name).to eq('Customer') + expect(article.type.name).to eq('sms') + + post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms2'), as: :json + expect(response).to have_http_status(:ok) + + ticket.reload + expect(ticket.articles.count).to eq(2) + expect(ticket.state.name).to eq('new') + + article = Ticket::Article.last + expect(article.from).to eq('+491710000000') + expect(article.to).to eq('+4915700000000') + expect(article.cc).to be_nil + expect(article.subject).to be_nil + expect(article.body).to eq('Follow-up') + expect(article.sender.name).to eq('Customer') + expect(article.type.name).to eq('sms') + expect(article.created_by_id).to eq(customer.id) + + # check duplicate callbacks + post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms2'), as: :json + expect(response).to have_http_status(:ok) + + ticket.reload + expect(ticket.articles.count).to eq(2) + expect(ticket.state.name).to eq('new') + expect(article.id).to eq(Ticket::Article.last.id) + + # new ticket need to be create + ticket.state = Ticket::State.find_by(name: 'closed') + ticket.save! + + post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms3'), as: :json + expect(response).to have_http_status(:ok) + + ticket.reload + expect(ticket.articles.count).to eq(2) + expect(ticket.id).not_to eq(Ticket.last.id) + expect(ticket.state.name).to eq('closed') + + ticket = Ticket.last + article = Ticket::Article.last + customer = User.last + expect(ticket.articles.count).to eq(1) + expect(ticket.title).to eq('new 2') + expect(ticket.group_id).to eq(group_id) + expect(ticket.customer_id).to eq(customer.id) + expect(ticket.created_by_id).to eq(customer.id) + expect(article.from).to eq('+491710000000') + expect(article.to).to eq('+4915700000000') + expect(article.cc).to be_nil + expect(article.subject).to be_nil + expect(article.body).to eq('new 2') + expect(article.created_by_id).to eq(customer.id) + expect(article.sender.name).to eq('Customer') + expect(article.type.name).to eq('sms') + + # reply by agent + params = { + ticket_id: ticket.id, + body: 'some test', + type: 'sms', + } + authenticated_as(agent) + post '/api/v1/ticket_articles', params: params, as: :json + expect(response).to have_http_status(:created) + expect(json_response).to be_a_kind_of(Hash) + expect(json_response['subject']).to be_nil + expect(json_response['body']).to eq('some test') + expect(json_response['content_type']).to eq('text/plain') + expect(json_response['updated_by_id']).to eq(agent.id) + expect(json_response['created_by_id']).to eq(agent.id) + + stub_request(:post, 'https://rest.messagebird.com/messages') + .to_return(status: 200, body: mocked_response_success, headers: {}) + + expect(article.preferences[:delivery_retry]).to be_nil + expect(article.preferences[:delivery_status]).to be_nil + + TransactionDispatcher.commit + Scheduler.worker(true) + + article = Ticket::Article.find(json_response['id']) + expect(article.preferences[:delivery_retry]).to eq(1) + expect(article.preferences[:delivery_status]).to eq('success') + end + + it 'does customer based on already existing mobile attibute' do + + customer = create( + :customer, + email: 'me@example.com', + mobile: '01710000000', + ) + TransactionDispatcher.commit + Scheduler.worker(true) + + UserInfo.current_user_id = 1 + create( + :channel, + area: 'Sms::Account', + options: { + adapter: 'sms/twilio', + webhook_token: 'f409460e50f76d331fdac8ba7b7963b6', + account_id: '111', + token: '223', + sender: '333', + }, + ) + + post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms1'), as: :json + expect(response).to have_http_status(:ok) + + expect(customer.id).to eq(User.last.id) + end + + def read_message(file) + JSON.parse(File.read(Rails.root.join('test', 'data', 'message_bird', "#{file}.json"))) + end + + def mocked_response_success + '{"id":"1e8cc35873d14fe4ab18bd97a412121","href":"https://rest.messagebird.com/messages/1e8cc35873d14fe4ab18bd121212f971a","direction":"mt","type":"sms","originator":"Zammad GmbH","body":"some test","reference":"Foobar","validity":null,"gateway":10,"typeDetails":{},"datacoding":"plain","mclass":1,"scheduledDatetime":null,"createdDatetime":"2021-07-22T13:25:03+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":0,"totalDeliveryFailedCount":0,"items":[{"recipient":491234,"status":"sent","statusDatetime":"2021-07-22T13:25:03+00:00","messagePartCount":1}]}}' + end + end +end diff --git a/test/data/message_bird/inbound_sms1.json b/test/data/message_bird/inbound_sms1.json new file mode 100644 index 000000000..7a7bf8d98 --- /dev/null +++ b/test/data/message_bird/inbound_sms1.json @@ -0,0 +1,20 @@ +{ + "body":"Ldfhxhcuffufuf. Fifififig. Fifififiif Fifififiif Fifififiif Fifififiif Fifififiif", + "createdDatetime":"", + "currentTime":"2021-07-22T10:06:00+02:00", + "date":"0", + "date_utc":"0", + "id":"", + "incomingMessage":"Ldfhxhcuffufuf. Fifififig. Fifififiif Fifififiif Fifififiif Fifififiif Fifififiif", + "message":"", + "message_id":"", + "originator":"+491710000000", + "payload":"Ldfhxhcuffufuf. Fifififig. Fifififiif Fifififiif Fifififiif Fifififiif Fifififiif", + "receiver":"", + "recipient":"+4915700000000", + "reference":"", + "sender":"", + "controller":"channels_sms", + "action":"webhook", + "token":"xxx" +} diff --git a/test/data/message_bird/inbound_sms2.json b/test/data/message_bird/inbound_sms2.json new file mode 100644 index 000000000..00031642c --- /dev/null +++ b/test/data/message_bird/inbound_sms2.json @@ -0,0 +1,20 @@ +{ + "body":"Follow-up", + "createdDatetime":"", + "currentTime":"2021-07-22T10:06:00+02:00", + "date":"0", + "date_utc":"0", + "id":"", + "incomingMessage":"Follow-up", + "message":"", + "message_id":"123", + "originator":"+491710000000", + "payload":"Follow-up", + "receiver":"", + "recipient":"+4915700000000", + "reference":"", + "sender":"", + "controller":"channels_sms", + "action":"webhook", + "token":"xxx" +} diff --git a/test/data/message_bird/inbound_sms3.json b/test/data/message_bird/inbound_sms3.json new file mode 100644 index 000000000..7ec309b45 --- /dev/null +++ b/test/data/message_bird/inbound_sms3.json @@ -0,0 +1,20 @@ +{ + "body":"new 2", + "createdDatetime":"", + "currentTime":"2021-07-22T10:06:00+02:00", + "date":"0", + "date_utc":"0", + "id":"", + "incomingMessage":"new 2", + "message":"", + "message_id":"", + "originator":"+491710000000", + "payload":"new 2", + "receiver":"", + "recipient":"+4915700000000", + "reference":"", + "sender":"", + "controller":"channels_sms", + "action":"webhook", + "token":"xxx" +}