From 271102057d4e4fb6681995e31a398fba368308c2 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 15 Oct 2018 11:47:59 +0200 Subject: [PATCH] Initial Placetel integration. --- .../controllers/_integration/placetel.coffee | 158 ++++++ .../javascripts/app/controllers/cti.coffee | 1 + .../app/views/integration/cti.jst.eco | 4 +- .../app/views/integration/placetel.jst.eco | 97 ++++ .../app/views/integration/sipgate.jst.eco | 4 +- app/controllers/cti_controller.rb | 5 + .../integration/placetel_controller.rb | 167 ++++++ config/routes/integration_placetel.rb | 5 + .../20180921000001_setting_add_placetel.rb | 71 +++ db/seeds/settings.rb | 62 +++ script/build/cleanup.sh | 1 + spec/requests/integration/placetel_spec.rb | 514 ++++++++++++++++++ 12 files changed, 1085 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/app/controllers/_integration/placetel.coffee create mode 100644 app/assets/javascripts/app/views/integration/placetel.jst.eco create mode 100644 app/controllers/integration/placetel_controller.rb create mode 100644 config/routes/integration_placetel.rb create mode 100644 db/migrate/20180921000001_setting_add_placetel.rb create mode 100644 spec/requests/integration/placetel_spec.rb diff --git a/app/assets/javascripts/app/controllers/_integration/placetel.coffee b/app/assets/javascripts/app/controllers/_integration/placetel.coffee new file mode 100644 index 000000000..8c337700e --- /dev/null +++ b/app/assets/javascripts/app/controllers/_integration/placetel.coffee @@ -0,0 +1,158 @@ +class Index extends App.ControllerIntegrationBase + featureIntegration: 'placetel_integration' + featureName: 'Placetel' + featureConfig: 'placetel_config' + description: [ + ['This service shows you contacts of incoming calls and a caller list in realtime.'] + ['Also caller id of outbound calls can be changed.'] + ] + events: + 'click .js-select': 'selectAll' + 'change .js-switch input': 'switch' + + render: => + super + new Form( + el: @$('.js-form') + ) + + new App.HttpLog( + el: @$('.js-log') + facility: 'placetel' + ) + +class Form extends App.Controller + events: + 'submit form': 'update' + 'click .js-inboundBlockCallerId .js-add': 'addInboundBlockCallerId' + 'click .js-outboundRouting .js-add': 'addOutboundRouting' + 'click .js-inboundBlockCallerId .js-remove': 'removeInboundBlockCallerId' + 'click .js-outboundRouting .js-remove': 'removeOutboundRouting' + + constructor: -> + super + @render() + + currentConfig: -> + config = App.Setting.get('placetel_config') + if !config.outbound + config.outbound = {} + if !config.outbound.routing_table + config.outbound.routing_table = [] + if !config.inbound + config.inbound = {} + if !config.inbound.block_caller_ids + config.inbound.block_caller_ids = [] + config + + setConfig: (value) -> + App.Setting.set('placetel_config', value, {notify: true}) + + render: => + @config = @currentConfig() + + @html App.view('integration/placetel')( + config: @config + placetel_token: App.Setting.get('placetel_token') + ) + + updateCurrentConfig: => + config = @config + cleanupInput = @cleanupInput + + # default caller_id + default_caller_id = @$('input[name=default_caller_id]').val() + config.outbound.default_caller_id = cleanupInput(default_caller_id) + + # routing table + config.outbound.routing_table = [] + @$('.js-outboundRouting .js-row').each(-> + dest = cleanupInput($(@).find('input[name="dest"]').val()) + caller_id = cleanupInput($(@).find('input[name="caller_id"]').val()) + note = $(@).find('input[name="note"]').val() + config.outbound.routing_table.push { + dest: dest + caller_id: caller_id + note: note + } + ) + + # blocked caller ids + config.inbound.block_caller_ids = [] + @$('.js-inboundBlockCallerId .js-row').each(-> + caller_id = $(@).find('input[name="caller_id"]').val() + note = $(@).find('input[name="note"]').val() + config.inbound.block_caller_ids.push { + caller_id: cleanupInput(caller_id) + note: note + } + ) + + @config = config + + update: (e) => + e.preventDefault() + @updateCurrentConfig() + @setConfig(@config) + + cleanupInput: (value) -> + return value if !value + value.replace(/\s/g, '').trim() + + addInboundBlockCallerId: (e) => + e.preventDefault() + @updateCurrentConfig() + element = $(e.currentTarget).closest('tr') + caller_id = element.find('input[name="caller_id"]').val() + note = element.find('input[name="note"]').val() + return if _.isEmpty(caller_id) || _.isEmpty(note) + @config.inbound.block_caller_ids.push { + caller_id: @cleanupInput(caller_id) + note: note + } + @render() + + addOutboundRouting: (e) => + e.preventDefault() + @updateCurrentConfig() + element = $(e.currentTarget).closest('tr') + dest = @cleanupInput(element.find('input[name="dest"]').val()) + caller_id = @cleanupInput(element.find('input[name="caller_id"]').val()) + note = element.find('input[name="note"]').val() + return if _.isEmpty(caller_id) || _.isEmpty(dest) || _.isEmpty(note) + @config.outbound.routing_table.push { + dest: dest + caller_id: caller_id + note: note + } + @render() + + removeInboundBlockCallerId: (e) => + e.preventDefault() + @updateCurrentConfig() + element = $(e.currentTarget).closest('tr') + element.remove() + @updateCurrentConfig() + + removeOutboundRouting: (e) => + e.preventDefault() + @updateCurrentConfig() + element = $(e.currentTarget).closest('tr') + element.remove() + @updateCurrentConfig() + +class State + @current: -> + App.Setting.get('placetel_integration') + +App.Config.set( + 'IntegrationPlacetel' + { + name: 'Placetel' + target: '#system/integration/placetel' + description: 'VoIP service provider with realtime push.' + controller: Index + state: State + } + 'NavBarIntegrations' +) diff --git a/app/assets/javascripts/app/controllers/cti.coffee b/app/assets/javascripts/app/controllers/cti.coffee index 5ae318b38..02ebe3387 100644 --- a/app/assets/javascripts/app/controllers/cti.coffee +++ b/app/assets/javascripts/app/controllers/cti.coffee @@ -99,6 +99,7 @@ class App.CTI extends App.Controller featureActive: => return true if @Config.get('sipgate_integration') return true if @Config.get('cti_integration') + return true if @Config.get('placetel_integration') false render: -> diff --git a/app/assets/javascripts/app/views/integration/cti.jst.eco b/app/assets/javascripts/app/views/integration/cti.jst.eco index d8ed35186..18346d10a 100644 --- a/app/assets/javascripts/app/views/integration/cti.jst.eco +++ b/app/assets/javascripts/app/views/integration/cti.jst.eco @@ -40,7 +40,7 @@ <% end %> - +
<%- @Icon('plus-small') %> <%- @T('Add') %>
@@ -70,7 +70,7 @@ - +
<%- @Icon('plus-small') %> <%- @T('Add') %>
diff --git a/app/assets/javascripts/app/views/integration/placetel.jst.eco b/app/assets/javascripts/app/views/integration/placetel.jst.eco new file mode 100644 index 000000000..ae209bed2 --- /dev/null +++ b/app/assets/javascripts/app/views/integration/placetel.jst.eco @@ -0,0 +1,97 @@ +
+ +

Placetel <%- @T('Settings') %>

+ +

<%- @T('You need to configure the Zammad endpoints in the %s', 'Placetel') %>:

+ +

+ + + + + + + +
<%- @T('Type') %> + <%- @T('URL') %> +
<%- @T('Endpoint') %> + +
+
+ +

<%- @T('Inbound') %>

+ +

<%- @T('Blocked caller ids based on sender caller id.') %> + +

+ + + + + +<% for row in @config.inbound.block_caller_ids: %> + + + +
<%- @T('Caller id to block') %> + <%- @T('Note') %> + <%- @T('Action') %> +
+ +
<%- @Icon('trash') %> <%- @T('Remove') %>
+<% end %> +
+ +
<%- @Icon('plus-small') %> <%- @T('Add') %>
+
+
+ + +
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/integration/sipgate.jst.eco b/app/assets/javascripts/app/views/integration/sipgate.jst.eco index 937377a86..7ea57d452 100644 --- a/app/assets/javascripts/app/views/integration/sipgate.jst.eco +++ b/app/assets/javascripts/app/views/integration/sipgate.jst.eco @@ -43,7 +43,7 @@ <% end %> - +
<%- @Icon('plus-small') %> <%- @T('Add') %>
@@ -73,7 +73,7 @@ - +
<%- @Icon('plus-small') %> <%- @T('Add') %>
diff --git a/app/controllers/cti_controller.rb b/app/controllers/cti_controller.rb index 2fdc71b4d..cbfa90e24 100644 --- a/app/controllers/cti_controller.rb +++ b/app/controllers/cti_controller.rb @@ -15,6 +15,11 @@ class CtiController < ApplicationController name: 'sipgate.io', enabled: Setting.get('sipgate_integration'), url: '#system/integration/sipgate', + }, + { + name: 'Placetel', + enabled: Setting.get('placetel_integration'), + url: '#system/integration/placetel', } ] diff --git a/app/controllers/integration/placetel_controller.rb b/app/controllers/integration/placetel_controller.rb new file mode 100644 index 000000000..58c0cc215 --- /dev/null +++ b/app/controllers/integration/placetel_controller.rb @@ -0,0 +1,167 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ +require 'builder' + +class Integration::PlacetelController < ApplicationController + skip_before_action :verify_csrf_token + before_action :check_configured, :check_token + + # notify about inbound call / block inbound call + def event + + local_params = ActiveSupport::HashWithIndifferentAccess.new(params.permit!.to_h) + + # do placetel event mapping + if local_params['event'] == 'IncomingCall' + local_params['direction'] = 'in' + local_params['event'] = 'newCall' + elsif local_params['event'] == 'HungUp' + local_params['event'] = 'hangup' + elsif local_params['event'] == 'CallAccepted' + local_params['event'] = 'answer' + end + + if local_params['direction'].blank? + entry = Cti::Log.find_by(call_id: params[:call_id]) + if entry + local_params['direction'] = entry.direction + end + end + + if local_params['type'] == 'missed' + local_params['cause'] = 'cancel' + elsif local_params['type'] == 'voicemail' + local_params['cause'] = 'voicemail' + elsif local_params['type'] == 'blocked' + local_params['cause'] = 'blocked' + elsif local_params['type'] == 'accepted' + local_params['cause'] = 'normalClearing' + end + + if local_params['direction'] == 'in' + if local_params['event'] == 'newCall' + config_inbound = config_integration[:inbound] || {} + block_caller_ids = config_inbound[:block_caller_ids] || [] + + # check if call need to be blocked + block_caller_ids.each do |item| + next unless item[:caller_id] == local_params['from'] + + xml = Builder::XmlMarkup.new(indent: 2) + xml.instruct! + content = xml.Response() do + xml.Reject('reason' => 'busy') + end + send_data content, type: 'application/xml; charset=UTF-8;' + + #local_params['Reject'] = 'busy' + local_params['comment'] = 'reject, busy' + if local_params['user'] + local_params['comment'] = "#{local_params['user']} -> reject, busy" + end + Cti::Log.process(local_params) + return true + end + end + + Cti::Log.process(local_params) + + xml = Builder::XmlMarkup.new(indent: 2) + xml.instruct! + content = xml.Response() + send_data content, type: 'application/xml; charset=UTF-8;' + return true + elsif local_params['direction'] == 'out' + config_outbound = config_integration[:outbound] + routing_table = nil + default_caller_id = nil + if config_outbound.present? + routing_table = config_outbound[:routing_table] + default_caller_id = config_outbound[:default_caller_id] + end + + xml = Builder::XmlMarkup.new(indent: 2) + xml.instruct! + + # set callerId + content = nil + to = local_params[:to] + from = nil + if to && routing_table.present? + routing_table.each do |row| + dest = row[:dest].gsub(/\*/, '.+?') + next if to !~ /^#{dest}$/ + + from = row[:caller_id] + content = xml.Response() do + xml.Dial(callerId: from) { xml.Number(params[:to]) } + end + break + end + if !content && default_caller_id.present? + from = default_caller_id + content = xml.Response() do + xml.Dial(callerId: default_caller_id) { xml.Number(params[:to]) } + end + end + else + content = xml.Response() + end + send_data(content, type: 'application/xml; charset=UTF-8;') + + if from.present? + local_params['from'] = from + end + Cti::Log.process(local_params) + return true + end + response_error('Invalid direction!') + end + + private + + def check_token + if Setting.get('placetel_token') != params[:token] + response_unauthorized('Invalid token, please contact your admin!') + return + end + + true + end + + def check_configured + http_log_config facility: 'placetel' + + if !Setting.get('placetel_integration') + response_error('Feature is disable, please contact your admin to enable it!') + return + end + if config_integration.blank? || config_integration[:inbound].blank? || config_integration[:outbound].blank? + response_error('Feature not configured, please contact your admin!') + return + end + + true + end + + def xml_error(error, code) + xml = Builder::XmlMarkup.new(indent: 2) + xml.instruct! + content = xml.Response() do + xml.Error(error) + end + send_data content, type: 'application/xml; charset=UTF-8;', status: code + end + + def config_integration + @config_integration ||= Setting.get('placetel_config') + end + + def response_error(error) + xml_error(error, 422) + end + + def response_unauthorized(error) + xml_error(error, 401) + end + +end diff --git a/config/routes/integration_placetel.rb b/config/routes/integration_placetel.rb new file mode 100644 index 000000000..d2cde16bd --- /dev/null +++ b/config/routes/integration_placetel.rb @@ -0,0 +1,5 @@ +Zammad::Application.routes.draw do + + match '/api/v1/placetel/:token', to: 'integration/placetel#event', via: :post + +end diff --git a/db/migrate/20180921000001_setting_add_placetel.rb b/db/migrate/20180921000001_setting_add_placetel.rb new file mode 100644 index 000000000..20ffbcc71 --- /dev/null +++ b/db/migrate/20180921000001_setting_add_placetel.rb @@ -0,0 +1,71 @@ +class SettingAddPlacetel < ActiveRecord::Migration[5.1] + def change + + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + + Setting.create_if_not_exists( + title: 'Placetel integration', + name: 'placetel_integration', + area: 'Integration::Switch', + description: 'Defines if Placetel (http://www.placetel.de) is enabled or not.', + options: { + form: [ + { + display: '', + null: true, + name: 'placetel_integration', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + state: false, + preferences: { + prio: 1, + trigger: ['menu:render', 'cti:reload'], + authentication: true, + permission: ['admin.integration'], + }, + frontend: true + ) + Setting.create_if_not_exists( + title: 'Placetel config', + name: 'placetel_config', + area: 'Integration::Placetel', + description: 'Defines the Placetel config.', + options: {}, + state: { 'outbound' => { 'routing_table' => [], 'default_caller_id' => '' }, 'inbound' => { 'block_caller_ids' => [] } }, + preferences: { + prio: 2, + permission: ['admin.integration'], + }, + frontend: false, + ) + Setting.create_if_not_exists( + title: 'PLACETEL Token', + name: 'placetel_token', + area: 'Integration::Placetel', + description: 'Token for placetel.', + options: { + form: [ + { + display: '', + null: false, + name: 'placetel_token', + tag: 'input', + }, + ], + }, + state: ENV['PLACETEL_TOKEN'] || SecureRandom.urlsafe_base64(20), + preferences: { + permission: ['admin.integration'], + }, + frontend: false + ) + + end +end diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb index c7cd1075e..fd26be282 100644 --- a/db/seeds/settings.rb +++ b/db/seeds/settings.rb @@ -3996,6 +3996,68 @@ Setting.create_if_not_exists( }, frontend: false ) +Setting.create_if_not_exists( + title: 'Placetel integration', + name: 'placetel_integration', + area: 'Integration::Switch', + description: 'Defines if Placetel (http://www.placetel.de) is enabled or not.', + options: { + form: [ + { + display: '', + null: true, + name: 'placetel_integration', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + state: false, + preferences: { + prio: 1, + trigger: ['menu:render', 'cti:reload'], + authentication: true, + permission: ['admin.integration'], + }, + frontend: true +) +Setting.create_if_not_exists( + title: 'Placetel config', + name: 'placetel_config', + area: 'Integration::Placetel', + description: 'Defines the Placetel config.', + options: {}, + state: { 'outbound' => { 'routing_table' => [], 'default_caller_id' => '' }, 'inbound' => { 'block_caller_ids' => [] } }, + preferences: { + prio: 2, + permission: ['admin.integration'], + }, + frontend: false, +) +Setting.create_if_not_exists( + title: 'PLACETEL Token', + name: 'placetel_token', + area: 'Integration::Placetel', + description: 'Token for placetel.', + options: { + form: [ + { + display: '', + null: false, + name: 'placetel_token', + tag: 'input', + }, + ], + }, + state: ENV['PLACETEL_TOKEN'] || SecureRandom.urlsafe_base64(20), + preferences: { + permission: ['admin.integration'], + }, + frontend: false +) Setting.create_if_not_exists( title: 'Clearbit integration', name: 'clearbit_integration', diff --git a/script/build/cleanup.sh b/script/build/cleanup.sh index a4287ae7f..20449769d 100755 --- a/script/build/cleanup.sh +++ b/script/build/cleanup.sh @@ -5,3 +5,4 @@ set -ex rm app/assets/javascripts/app/controllers/layout_ref.coffee rm -rf app/assets/javascripts/app/views/layout_ref/ rm app/assets/javascripts/app/controllers/karma.coffee +rm app/assets/javascripts/app/controllers/_integration/placetel.coffee diff --git a/spec/requests/integration/placetel_spec.rb b/spec/requests/integration/placetel_spec.rb new file mode 100644 index 000000000..2aa6cbf6d --- /dev/null +++ b/spec/requests/integration/placetel_spec.rb @@ -0,0 +1,514 @@ +require 'rails_helper' + +RSpec.describe 'Integration Placetel', type: :request do + + let(:agent_user) do + create(:agent_user) + end + let!(:customer_user1) do + create( + :customer_user, + login: 'ticket-caller_id_cti-customer1@example.com', + firstname: 'CallerId', + lastname: 'Customer1', + phone: '+49 99999 222222', + fax: '+49 99999 222223', + mobile: '+01114100300', + note: 'Phone at home: +49 99999 222224', + ) + end + let!(:customer_user2) do + create( + :customer_user, + login: 'ticket-caller_id_cti-customer2@example.com', + firstname: 'CallerId', + lastname: 'Customer2', + phone: '+49 99999 222222 2', + ) + end + let!(:customer_user3) do + create( + :customer_user, + login: 'ticket-caller_id_cti-customer3@example.com', + firstname: 'CallerId', + lastname: 'Customer3', + phone: '+49 99999 222222 2', + ) + end + + before(:each) do + Cti::Log.destroy_all + + Setting.set('placetel_integration', true) + Setting.set('placetel_config', { + outbound: { + routing_table: [ + { + dest: '41*', + caller_id: '41715880339000', + }, + { + dest: '491714000000', + caller_id: '41715880339000', + }, + ], + default_caller_id: '4930777000000', + }, + inbound: { + block_caller_ids: [ + { + caller_id: '491715000000', + note: 'some note', + } + ], + notify_user_ids: { + 2 => true, + 4 => false, + }, + } + }) + + Cti::CallerId.rebuild + end + + describe 'request handling' do + + it 'does token check' do + params = 'event=IncomingCall&from=01114100300&to=030600000000&call_id=4991155921769858278-1' + post '/api/v1/placetel/not_existing_token', params: params + expect(response).to have_http_status(401) + + error = nil + local_response = REXML::Document.new(response.body) + local_response.elements.each('Response/Error') do |element| + error = element.text + end + expect(error).to eq('Invalid token, please contact your admin!') + end + + it 'does basic call' do + token = Setting.get('placetel_token') + + # inbound - I + params = 'event=IncomingCall&from=01114100300&to=030600000000&call_id=4991155921769858278-1' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + + local_response = REXML::Document.new(response.body) + expect(local_response.elements.count).to eq(1) + expect(local_response.elements.first.to_s).to eq('') + + # inbound - II - block caller + params = 'event=IncomingCall&from=491715000000&to=030600000000&call_id=4991155921769858278-2' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + + local_response = REXML::Document.new(response.body) + reason = nil + local_response.elements.each('Response/Reject') do |element| + reason = element.attributes['reason'] + end + expect(reason).to eq('busy') + + # outbound - I - set default_caller_id + params = 'event=newCall&direction=out&from=030600000000&to=01114100300&call_id=8621106404543334274-3' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + + caller_id = nil + number_to_dail = nil + lcoal_response = REXML::Document.new(response.body) + lcoal_response.elements.each('Response/Dial') do |element| + caller_id = element.attributes['callerId'] + end + lcoal_response.elements.each('Response/Dial/Number') do |element| + number_to_dail = element.text + end + expect(caller_id).to eq('4930777000000') + expect(number_to_dail).to eq('01114100300') + + # outbound - II - set caller_id based on routing_table by explicite number + params = 'event=newCall&direction=out&from=030600000000&to=491714000000&call_id=8621106404543334274-4' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + + caller_id = nil + number_to_dail = nil + lcoal_response = REXML::Document.new(response.body) + lcoal_response.elements.each('Response/Dial') do |element| + caller_id = element.attributes['callerId'] + end + lcoal_response.elements.each('Response/Dial/Number') do |element| + number_to_dail = element.text + end + expect(caller_id).to eq('41715880339000') + expect(number_to_dail).to eq('491714000000') + + # outbound - III - set caller_id based on routing_table by 41* + params = 'event=newCall&direction=out&from=030600000000&to=4147110000000&call_id=8621106404543334274-5' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + + caller_id = nil + number_to_dail = nil + lcoal_response = REXML::Document.new(response.body) + lcoal_response.elements.each('Response/Dial') do |element| + caller_id = element.attributes['callerId'] + end + lcoal_response.elements.each('Response/Dial/Number') do |element| + number_to_dail = element.text + end + expect(caller_id).to eq('41715880339000') + expect(number_to_dail).to eq('4147110000000') + + # no config + Setting.set('placetel_config', {}) + params = 'event=IncomingCall&from=01114100300&to=030600000000&call_id=4991155921769858278-6' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(422) + + error = nil + local_response = REXML::Document.new(response.body) + local_response.elements.each('Response/Error') do |element| + error = element.text + end + expect(error).to eq('Feature not configured, please contact your admin!') + end + + it 'does log call' do + token = Setting.get('placetel_token') + + # outbound - I - new call + params = 'event=newCall&direction=out&from=030600000000&to=01114100300&call_id=1234567890-1' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-1') + expect(log).to be_truthy + expect(log.from).to eq('4930777000000') + expect(log.to).to eq('01114100300') + expect(log.direction).to eq('out') + #expect(log.from_comment).to eq('user 1') + expect(log.to_comment).to eq('CallerId Customer1') + expect(log.comment).to be_nil + expect(log.state).to eq('newCall') + expect(log.done).to eq(true) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_nil + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_nil + expect(log.duration_talking_time).to be_nil + + # outbound - I - hangup by agent + params = 'event=HungUp&call_id=1234567890-1&type=missed' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-1') + expect(log).to be_truthy + expect(log.from).to eq('4930777000000') + expect(log.to).to eq('01114100300') + expect(log.direction).to eq('out') + #expect(log.from_comment).to eq('user 1') + expect(log.to_comment).to eq('CallerId Customer1') + expect(log.comment).to eq('cancel') + expect(log.state).to eq('hangup') + expect(log.done).to eq(true) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_nil + expect(log.end_at).to be_truthy + expect(log.duration_waiting_time).to be_truthy + expect(log.duration_talking_time).to be_nil + + # outbound - II - new call + params = 'event=newCall&direction=out&from=030600000000&to=01114100300&call_id=1234567890-2' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-2') + expect(log).to be_truthy + expect(log.from).to eq('4930777000000') + expect(log.to).to eq('01114100300') + expect(log.direction).to eq('out') + # expect(log.from_comment).to eq('user 1') + expect(log.to_comment).to eq('CallerId Customer1') + expect(log.comment).to be_nil + expect(log.state).to eq('newCall') + expect(log.done).to eq(true) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_nil + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_nil + expect(log.duration_talking_time).to be_nil + + # outbound - II - answer by customer + params = 'event=CallAccepted&call_id=1234567890-2&from=030600000000&to=01114100300' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-2') + expect(log).to be_truthy + expect(log.from).to eq('4930777000000') + expect(log.to).to eq('01114100300') + expect(log.direction).to eq('out') + # expect(log.from_comment).to eq('user 1') + expect(log.to_comment).to eq('CallerId Customer1') + expect(log.comment).to be_nil + expect(log.state).to eq('answer') + expect(log.done).to eq(true) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_truthy + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_truthy + expect(log.duration_talking_time).to be_nil + + # outbound - II - hangup by customer + params = 'event=HungUp&call_id=1234567890-2&type=accepted&from=030600000000&to=01114100300' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-2') + expect(log).to be_truthy + expect(log.from).to eq('4930777000000') + expect(log.to).to eq('01114100300') + expect(log.direction).to eq('out') + # expect(log.from_comment).to eq('user 1') + expect(log.to_comment).to eq('CallerId Customer1') + expect(log.comment).to eq('normalClearing') + expect(log.state).to eq('hangup') + expect(log.done).to eq(true) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_truthy + expect(log.end_at).to be_truthy + expect(log.duration_waiting_time).to be_truthy + expect(log.duration_talking_time).to be_truthy + + # inbound - I - new call + params = 'event=IncomingCall&to=030600000000&from=01114100300&call_id=1234567890-3' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-3') + expect(log).to be_truthy + expect(log.to).to eq('030600000000') + expect(log.from).to eq('01114100300') + expect(log.direction).to eq('in') + #expect(log.to_comment).to eq('user 1') + expect(log.from_comment).to eq('CallerId Customer1') + expect(log.comment).to be_nil + expect(log.state).to eq('newCall') + expect(log.done).to eq(false) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_nil + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_nil + expect(log.duration_talking_time).to be_nil + + # inbound - I - answer by customer + params = 'event=CallAccepted&call_id=1234567890-3&to=030600000000&from=01114100300' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-3') + expect(log).to be_truthy + expect(log.to).to eq('030600000000') + expect(log.from).to eq('01114100300') + expect(log.direction).to eq('in') + #expect(log.to_comment).to eq('user 1') + expect(log.from_comment).to eq('CallerId Customer1') + expect(log.comment).to be_nil + expect(log.state).to eq('answer') + expect(log.done).to eq(true) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_truthy + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_truthy + expect(log.duration_talking_time).to be_nil + + # inbound - I - hangup by customer + params = 'event=HungUp&call_id=1234567890-3&type=accepted&to=030600000000&from=01114100300' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-3') + expect(log).to be_truthy + expect(log.to).to eq('030600000000') + expect(log.from).to eq('01114100300') + expect(log.direction).to eq('in') + #expect(log.to_comment).to eq('user 1') + expect(log.from_comment).to eq('CallerId Customer1') + expect(log.comment).to eq('normalClearing') + expect(log.state).to eq('hangup') + expect(log.done).to eq(true) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_truthy + expect(log.end_at).to be_truthy + expect(log.duration_waiting_time).to be_truthy + expect(log.duration_talking_time).to be_truthy + + # inbound - II - new call + params = 'event=IncomingCall&to=030600000000&from=01114100300&call_id=1234567890-4' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-4') + expect(log).to be_truthy + expect(log.to).to eq('030600000000') + expect(log.from).to eq('01114100300') + expect(log.direction).to eq('in') + #expect(log.to_comment).to eq('user 1,user 2') + expect(log.from_comment).to eq('CallerId Customer1') + expect(log.comment).to be_nil + expect(log.state).to eq('newCall') + expect(log.done).to eq(false) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_nil + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_nil + expect(log.duration_talking_time).to be_nil + + # inbound - II - answer by voicemail + params = 'event=CallAccepted&call_id=1234567890-4&to=030600000000&from=01114100300&user=voicemail' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-4') + expect(log).to be_truthy + expect(log.to).to eq('030600000000') + expect(log.from).to eq('01114100300') + expect(log.direction).to eq('in') + expect(log.to_comment).to eq('voicemail') + expect(log.from_comment).to eq('CallerId Customer1') + expect(log.comment).to be_nil + expect(log.state).to eq('answer') + expect(log.done).to eq(true) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_truthy + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_truthy + expect(log.duration_talking_time).to be_nil + + # inbound - II - hangup by customer + params = 'event=HungUp&call_id=1234567890-4&type=accepted&to=030600000000&from=01114100300' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-4') + expect(log).to be_truthy + expect(log.to).to eq('030600000000') + expect(log.from).to eq('01114100300') + expect(log.direction).to eq('in') + expect(log.to_comment).to eq('voicemail') + expect(log.from_comment).to eq('CallerId Customer1') + expect(log.comment).to eq('normalClearing') + expect(log.state).to eq('hangup') + expect(log.done).to eq(false) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_truthy + expect(log.end_at).to be_truthy + expect(log.duration_waiting_time).to be_truthy + expect(log.duration_talking_time).to be_truthy + + # inbound - III - new call + params = 'event=IncomingCall&to=030600000000&from=01114100300&call_id=1234567890-5' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-5') + expect(log).to be_truthy + expect(log.to).to eq('030600000000') + expect(log.from).to eq('01114100300') + expect(log.direction).to eq('in') + #expect(log.to_comment).to eq('user 1,user 2') + expect(log.from_comment).to eq('CallerId Customer1') + expect(log.comment).to be_nil + expect(log.state).to eq('newCall') + expect(log.done).to eq(false) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_nil + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_nil + expect(log.duration_talking_time).to be_nil + + # inbound - III - hangup by customer + params = 'event=HungUp&call_id=1234567890-5&type=accepted&to=030600000000&from=01114100300' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-5') + expect(log).to be_truthy + expect(log.to).to eq('030600000000') + expect(log.from).to eq('01114100300') + expect(log.direction).to eq('in') + #expect(log.to_comment).to eq('user 1,user 2') + expect(log.from_comment).to eq('CallerId Customer1') + expect(log.comment).to eq('normalClearing') + expect(log.state).to eq('hangup') + expect(log.done).to eq(false) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_nil + expect(log.end_at).to be_truthy + expect(log.duration_waiting_time).to be_truthy + expect(log.duration_talking_time).to be_nil + + # inbound - IV - new call + params = 'event=IncomingCall&to=030600000000&from=49999992222222&call_id=1234567890-6' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-6') + expect(log).to be_truthy + expect(log.to).to eq('030600000000') + expect(log.from).to eq('49999992222222') + expect(log.direction).to eq('in') + #expect(log.to_comment).to eq('user 1,user 2') + expect(log.from_comment).to eq('CallerId Customer3,CallerId Customer2') + expect(log.preferences['to']).to be_falsey + expect(log.preferences['from']).to be_truthy + expect(log.comment).to be_nil + expect(log.state).to eq('newCall') + expect(log.done).to eq(false) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_nil + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_nil + expect(log.duration_talking_time).to be_nil + + # inbound - IV - new call + params = 'event=IncomingCall&to=030600000000&from=anonymous&call_id=1234567890-7' + post "/api/v1/placetel/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-7') + expect(log).to be_truthy + expect(log.to).to eq('030600000000') + expect(log.from).to eq('anonymous') + expect(log.direction).to eq('in') + #expect(log.to_comment).to eq('user 1,user 2') + expect(log.from_comment).to be_nil + expect(log.preferences['to']).to be_falsey + expect(log.preferences['from']).to be_falsey + expect(log.comment).to be_nil + expect(log.state).to eq('newCall') + expect(log.done).to eq(false) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_nil + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_nil + expect(log.duration_talking_time).to be_nil + + # get caller list + get '/api/v1/cti/log' + expect(response).to have_http_status(401) + + authenticated_as(agent_user) + get '/api/v1/cti/log', as: :json + expect(response).to have_http_status(200) + expect(json_response['list']).to be_a_kind_of(Array) + expect(json_response['list'].count).to eq(7) + expect(json_response['assets']).to be_truthy + expect(json_response['assets']['User']).to be_truthy + expect(json_response['assets']['User'][customer_user2.id.to_s]).to be_truthy + expect(json_response['assets']['User'][customer_user3.id.to_s]).to be_truthy + expect(json_response['list'][0]['call_id']).to eq('1234567890-7') + expect(json_response['list'][1]['call_id']).to eq('1234567890-6') + expect(json_response['list'][2]['call_id']).to eq('1234567890-5') + expect(json_response['list'][3]['call_id']).to eq('1234567890-4') + expect(json_response['list'][4]['call_id']).to eq('1234567890-3') + expect(json_response['list'][5]['call_id']).to eq('1234567890-2') + expect(json_response['list'][5]['state']).to eq('hangup') + expect(json_response['list'][5]['from']).to eq('4930777000000') + #expect(json_response['list'][5]['from_comment']).to eq('user 1') + expect(json_response['list'][5]['to']).to eq('01114100300') + expect(json_response['list'][5]['to_comment']).to eq('CallerId Customer1') + expect(json_response['list'][5]['comment']).to eq('normalClearing') + expect(json_response['list'][5]['state']).to eq('hangup') + expect(json_response['list'][6]['call_id']).to eq('1234567890-1') + end + end +end