Browse Source

Refactored all controller mini tests and migrated them to rspec request specs

Rolf Schmidt 6 years ago
parent
commit
29b616d61d

+ 2 - 10
.gitlab-ci.yml

@@ -78,7 +78,6 @@ test:unit:mysql:
     - rake db:migrate
     - rake db:seed
     - rake test:units
-    - rake test:controllers
     - ruby -I test/ test/integration/object_manager_test.rb
     - ruby -I test/ test/integration/object_manager_attributes_controller_test.rb
     - ruby -I test/ test/integration/package_test.rb
@@ -96,7 +95,6 @@ test:unit:postgresql:
     - rake db:migrate
     - rake db:seed
     - rake test:units
-    - rake test:controllers
     - ruby -I test/ test/integration/object_manager_test.rb
     - ruby -I test/ test/integration/object_manager_attributes_controller_test.rb
     - ruby -I test/ test/integration/package_test.rb
@@ -242,11 +240,8 @@ test:integration:es_mysql:
     - rake db:migrate
     - ruby -I test/ test/integration/elasticsearch_active_test.rb
     - ruby -I test/ test/integration/elasticsearch_test.rb
-    - ruby -I test/ test/controllers/search_controller_test.rb
     - ruby -I test/ test/integration/report_test.rb
-    - ruby -I test/ test/controllers/form_controller_test.rb
-    - ruby -I test/ test/controllers/users_controller_test.rb
-    - ruby -I test/ test/controllers/organizations_controller_test.rb
+    - bundle exec rspec --tag searchindex
     - rake db:drop
 
 test:integration:es_postgresql:
@@ -262,11 +257,8 @@ test:integration:es_postgresql:
     - rake db:migrate
     - ruby -I test/ test/integration/elasticsearch_active_test.rb
     - ruby -I test/ test/integration/elasticsearch_test.rb
-    - ruby -I test/ test/controllers/search_controller_test.rb
     - ruby -I test/ test/integration/report_test.rb
-    - ruby -I test/ test/controllers/form_controller_test.rb
-    - ruby -I test/ test/controllers/users_controller_test.rb
-    - ruby -I test/ test/controllers/organizations_controller_test.rb
+    - bundle exec rspec --tag searchindex
     - rake db:drop
 
 test:integration:zendesk_mysql:

+ 2 - 2
spec/factories/email_address.rb

@@ -1,7 +1,7 @@
 FactoryBot.define do
   factory :email_address do
-    email         'zammad@localhost'
-    realname      'zammad'
+    sequence(:email) { |n| "zammad#{n}@localhost.com" }
+    sequence(:realname) { |n| "zammad#{n}" }
     channel_id    1
     created_by_id 1
     updated_by_id 1

+ 10 - 0
spec/factories/ticket/time_accounting.rb

@@ -0,0 +1,10 @@
+FactoryBot.define do
+  factory :ticket_time_accounting, class: Ticket::TimeAccounting do
+    ticket_id { FactoryBot.create(:ticket).id }
+    ticket_article_id { FactoryBot.create(:ticket_article).id }
+    time_unit 200
+    created_by_id 1
+    created_at Time.zone.now
+    updated_at Time.zone.now
+  end
+end

+ 164 - 0
spec/requests/api_auth_on_behalf_of_spec.rb

@@ -0,0 +1,164 @@
+require 'rails_helper'
+
+RSpec.describe 'Api Auth On Behalf Of', type: :request do
+
+  let(:admin_user) do
+    create(:admin_user, groups: Group.all)
+  end
+  let(:agent_user) do
+    create(:agent_user)
+  end
+  let(:customer_user) do
+    create(:customer_user)
+  end
+
+  describe 'request handling' do
+
+    it 'does X-On-Behalf-Of auth - ticket create admin for customer by id' do
+      params = {
+        title: 'a new ticket #3',
+        group: 'Users',
+        priority: '2 normal',
+        state: 'new',
+        customer_id: customer_user.id,
+        article: {
+          body: 'some test 123',
+        },
+      }
+      authenticated_as(admin_user, on_behalf_of: customer_user.id)
+      post '/api/v1/tickets', params: params, as: :json
+      expect(response).to have_http_status(201)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(customer_user.id).to eq(json_response['created_by_id'])
+    end
+
+    it 'does X-On-Behalf-Of auth - ticket create admin for customer by login' do
+      ActivityStream.cleanup(1.year)
+
+      params = {
+        title: 'a new ticket #3',
+        group: 'Users',
+        priority: '2 normal',
+        state: 'new',
+        customer_id: customer_user.id,
+        article: {
+          body: 'some test 123',
+        },
+      }
+      authenticated_as(admin_user, on_behalf_of: customer_user.login)
+      post '/api/v1/tickets', params: params, as: :json
+      expect(response).to have_http_status(201)
+      json_response_ticket = json_response
+      expect(json_response_ticket).to be_a_kind_of(Hash)
+      expect(customer_user.id).to eq(json_response_ticket['created_by_id'])
+
+      authenticated_as(admin_user)
+      get '/api/v1/activity_stream?full=true', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      json_response_activity = json_response
+      expect(json_response_activity).to be_a_kind_of(Hash)
+
+      ticket_created = nil
+      json_response_activity['record_ids'].each do |record_id|
+        activity_stream = ActivityStream.find(record_id)
+        next if activity_stream.object.name != 'Ticket'
+        next if activity_stream.o_id != json_response_ticket['id'].to_i
+        ticket_created = activity_stream
+      end
+
+      expect(ticket_created).to be_truthy
+      expect(customer_user.id).to eq(ticket_created.created_by_id)
+
+      get '/api/v1/activity_stream', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      json_response_activity = json_response
+      expect(json_response_activity).to be_a_kind_of(Array)
+
+      ticket_created = nil
+      json_response_activity.each do |record|
+        activity_stream = ActivityStream.find(record['id'])
+        next if activity_stream.object.name != 'Ticket'
+        next if activity_stream.o_id != json_response_ticket['id']
+        ticket_created = activity_stream
+      end
+
+      expect(ticket_created).to be_truthy
+      expect(customer_user.id).to eq(ticket_created.created_by_id)
+    end
+
+    it 'does X-On-Behalf-Of auth - ticket create admin for customer by email' do
+      params = {
+        title: 'a new ticket #3',
+        group: 'Users',
+        priority: '2 normal',
+        state: 'new',
+        customer_id: customer_user.id,
+        article: {
+          body: 'some test 123',
+        },
+      }
+      authenticated_as(admin_user, on_behalf_of: customer_user.email)
+      post '/api/v1/tickets', params: params, as: :json
+      expect(response).to have_http_status(201)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(customer_user.id).to eq(json_response['created_by_id'])
+    end
+
+    it 'does X-On-Behalf-Of auth - ticket create admin for unknown' do
+      params = {
+        title: 'a new ticket #3',
+        group: 'Users',
+        priority: '2 normal',
+        state: 'new',
+        customer_id: customer_user.id,
+        article: {
+          body: 'some test 123',
+        },
+      }
+      authenticated_as(admin_user, on_behalf_of: 99_449_494_949)
+      post '/api/v1/tickets', params: params, as: :json
+      expect(response).to have_http_status(401)
+      expect(@response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq("No such user '99449494949'")
+    end
+
+    it 'does X-On-Behalf-Of auth - ticket create customer for admin' do
+      params = {
+        title: 'a new ticket #3',
+        group: 'Users',
+        priority: '2 normal',
+        state: 'new',
+        customer_id: customer_user.id,
+        article: {
+          body: 'some test 123',
+        },
+      }
+      authenticated_as(customer_user, on_behalf_of: admin_user.email)
+      post '/api/v1/tickets', params: params, as: :json
+      expect(response).to have_http_status(401)
+      expect(@response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq("Current user has no permission to use 'X-On-Behalf-Of'!")
+    end
+
+    it 'does X-On-Behalf-Of auth - ticket create admin for customer by email but no permitted action' do
+      params = {
+        title: 'a new ticket #3',
+        group: 'secret1234',
+        priority: '2 normal',
+        state: 'new',
+        customer_id: customer_user.id,
+        article: {
+          body: 'some test 123',
+        },
+      }
+      authenticated_as(admin_user, on_behalf_of: customer_user.email)
+      post '/api/v1/tickets', params: params, as: :json
+      expect(response).to have_http_status(422)
+      expect(@response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('No lookup value found for \'group\': "secret1234"')
+    end
+  end
+end

+ 383 - 0
spec/requests/api_auth_spec.rb

@@ -0,0 +1,383 @@
+require 'rails_helper'
+
+RSpec.describe 'Api Auth', type: :request do
+
+  let(:admin_user) do
+    create(:admin_user)
+  end
+  let(:agent_user) do
+    create(:agent_user)
+  end
+  let(:customer_user) do
+    create(:customer_user)
+  end
+
+  describe 'request handling' do
+
+    it 'does basic auth - admin' do
+
+      Setting.set('api_password_access', false)
+      authenticated_as(admin_user)
+      get '/api/v1/sessions', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('API password access disabled!')
+
+      Setting.set('api_password_access', true)
+      get '/api/v1/sessions', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(response.header['Access-Control-Allow-Origin']).to eq('*')
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response).to be_truthy
+    end
+
+    it 'does basic auth - agent' do
+
+      Setting.set('api_password_access', false)
+      authenticated_as(agent_user)
+      get '/api/v1/tickets', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('API password access disabled!')
+
+      Setting.set('api_password_access', true)
+      get '/api/v1/tickets', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(response.header['Access-Control-Allow-Origin']).to eq('*')
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+    end
+
+    it 'does basic auth - customer' do
+
+      Setting.set('api_password_access', false)
+      authenticated_as(customer_user)
+      get '/api/v1/tickets', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('API password access disabled!')
+
+      Setting.set('api_password_access', true)
+      get '/api/v1/tickets', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(response.header['Access-Control-Allow-Origin']).to eq('*')
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+    end
+
+    it 'does token auth - admin' do
+
+      admin_token = create(
+        :token,
+        action:      'api',
+        persistent:  true,
+        user_id:     admin_user.id,
+        preferences: {
+          permission: ['admin.session'],
+        },
+      )
+
+      authenticated_as(admin_user, token: admin_token)
+
+      Setting.set('api_token_access', false)
+      get '/api/v1/sessions', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('API token access disabled!')
+
+      Setting.set('api_token_access', true)
+      get '/api/v1/sessions', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(response.header['Access-Control-Allow-Origin']).to eq('*')
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response).to be_truthy
+
+      admin_token.preferences[:permission] = ['admin.session_not_existing']
+      admin_token.save!
+
+      get '/api/v1/sessions', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('Not authorized (token)!')
+
+      admin_token.preferences[:permission] = []
+      admin_token.save!
+
+      get '/api/v1/sessions', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('Not authorized (token)!')
+
+      admin_user.active = false
+      admin_user.save!
+
+      get '/api/v1/sessions', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('User is inactive!')
+
+      admin_token.preferences[:permission] = ['admin.session']
+      admin_token.save!
+
+      get '/api/v1/sessions', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('User is inactive!')
+
+      admin_user.active = true
+      admin_user.save!
+
+      get '/api/v1/sessions', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response).to be_truthy
+
+      get '/api/v1/roles', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('Not authorized (token)!')
+
+      admin_token.preferences[:permission] = ['admin.session_not_existing', 'admin.role']
+      admin_token.save!
+
+      get '/api/v1/roles', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+
+      admin_token.preferences[:permission] = ['ticket.agent']
+      admin_token.save!
+
+      get '/api/v1/organizations', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+
+      name = "some org name #{rand(999_999_999)}"
+      post '/api/v1/organizations', params: { name: name }, as: :json
+      expect(response).to have_http_status(201)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['name']).to eq(name)
+      expect(json_response).to be_truthy
+
+      name = "some org name #{rand(999_999_999)} - 2"
+      put "/api/v1/organizations/#{json_response['id']}", params: { name: name }, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['name']).to eq(name)
+      expect(json_response).to be_truthy
+
+      admin_token.preferences[:permission] = ['admin.organization']
+      admin_token.save!
+
+      get '/api/v1/organizations', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+
+      name = "some org name #{rand(999_999_999)}"
+      post '/api/v1/organizations', params: { name: name }, as: :json
+      expect(response).to have_http_status(201)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['name']).to eq(name)
+      expect(json_response).to be_truthy
+
+      name = "some org name #{rand(999_999_999)} - 2"
+      put "/api/v1/organizations/#{json_response['id']}", params: { name: name }, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['name']).to eq(name)
+      expect(json_response).to be_truthy
+
+      admin_token.preferences[:permission] = ['admin']
+      admin_token.save!
+
+      get '/api/v1/organizations', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+
+      name = "some org name #{rand(999_999_999)}"
+      post '/api/v1/organizations', params: { name: name }, as: :json
+      expect(response).to have_http_status(201)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['name']).to eq(name)
+      expect(json_response).to be_truthy
+
+      name = "some org name #{rand(999_999_999)} - 2"
+      put "/api/v1/organizations/#{json_response['id']}", params: { name: name }, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['name']).to eq(name)
+      expect(json_response).to be_truthy
+
+    end
+
+    it 'does token auth - agent' do
+
+      agent_token = create(
+        :token,
+        action:     'api',
+        persistent: true,
+        user_id:    agent_user.id,
+      )
+
+      authenticated_as(agent_user, token: agent_token)
+
+      Setting.set('api_token_access', false)
+      get '/api/v1/tickets', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('API token access disabled!')
+
+      Setting.set('api_token_access', true)
+      get '/api/v1/tickets', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(response.header['Access-Control-Allow-Origin']).to eq('*')
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+
+      get '/api/v1/organizations', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+
+      name = "some org name #{rand(999_999_999)}"
+      post '/api/v1/organizations', params: { name: name }, as: :json
+      expect(response).to have_http_status(401)
+
+    end
+
+    it 'does token auth - customer' do
+
+      customer_token = create(
+        :token,
+        action:     'api',
+        persistent: true,
+        user_id:    customer_user.id,
+      )
+
+      authenticated_as(customer_user, token: customer_token)
+
+      Setting.set('api_token_access', false)
+      get '/api/v1/tickets', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('API token access disabled!')
+
+      Setting.set('api_token_access', true)
+      get '/api/v1/tickets', params: {}, as: :json
+      expect(response.header['Access-Control-Allow-Origin']).to eq('*')
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+
+      get '/api/v1/organizations', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+
+      name = "some org name #{rand(999_999_999)}"
+      post '/api/v1/organizations', params: { name: name }, as: :json
+      expect(response).to have_http_status(401)
+    end
+
+    it 'does token auth - invalid user - admin' do
+
+      admin_token = create(
+        :token,
+        action:     'api',
+        persistent: true,
+        user_id:    admin_user.id,
+      )
+
+      authenticated_as(admin_user, token: admin_token)
+
+      admin_user.active = false
+      admin_user.save!
+
+      Setting.set('api_token_access', false)
+      get '/api/v1/sessions', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('API token access disabled!')
+
+      Setting.set('api_token_access', true)
+      get '/api/v1/sessions', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('User is inactive!')
+    end
+
+    it 'does token auth - expired' do
+
+      Setting.set('api_token_access', true)
+
+      admin_token = create(
+        :token,
+        action:     'api',
+        persistent: true,
+        user_id:    admin_user.id,
+        expires_at: Time.zone.today
+      )
+
+      authenticated_as(admin_user, token: admin_token)
+
+      get '/api/v1/tickets', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('Not authorized (token expired)!')
+
+      admin_token.reload
+      expect(admin_token.last_used_at).to be_within(1.second).of(Time.zone.now)
+    end
+
+    it 'does token auth - not expired' do
+
+      Setting.set('api_token_access', true)
+
+      admin_token = create(
+        :token,
+        action:     'api',
+        persistent: true,
+        user_id:    admin_user.id,
+        expires_at: Time.zone.tomorrow
+      )
+
+      authenticated_as(admin_user, token: admin_token)
+
+      get '/api/v1/tickets', params: {}, as: :json
+      expect(response).to have_http_status(200)
+      expect(response.header['Access-Control-Allow-Origin']).to eq('*')
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+
+      admin_token.reload
+      expect(admin_token.last_used_at).to be_within(1.second).of(Time.zone.now)
+    end
+
+    it 'does session auth - admin' do
+      create(:admin_user, login: 'api-admin@example.com', password: 'adminpw')
+
+      post '/api/v1/signin', params: { username: 'api-admin@example.com', password: 'adminpw', fingerprint: '123456789' }
+      expect(response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(response).to have_http_status(201)
+
+      get '/api/v1/sessions', params: {}
+      expect(response).to have_http_status(200)
+      expect(response.header.key?('Access-Control-Allow-Origin')).to be_falsey
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response).to be_truthy
+    end
+  end
+end

+ 112 - 0
spec/requests/basic_spec.rb

@@ -0,0 +1,112 @@
+require 'rails_helper'
+
+RSpec.describe 'Basics', type: :request do
+
+  describe 'request handling' do
+
+    it 'does json requests' do
+
+      # 404
+      get '/not_existing_url', as: :json
+      expect(response).to have_http_status(404)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('No route matches [GET] /not_existing_url')
+
+      # 401
+      get '/api/v1/organizations', as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('authentication failed')
+
+      # 422
+      get '/tests/unprocessable_entity', as: :json
+      expect(response).to have_http_status(422)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('some error message')
+
+      # 401
+      get '/tests/not_authorized', as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('some error message')
+
+      # 401
+      get '/tests/ar_not_found', as: :json
+      expect(response).to have_http_status(404)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('some error message')
+
+      # 500
+      get '/tests/standard_error', as: :json
+      expect(response).to have_http_status(500)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('some error message')
+
+      # 422
+      get '/tests/argument_error', as: :json
+      expect(response).to have_http_status(422)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('some error message')
+    end
+
+    it 'does html requests' do
+
+      # 404
+      get '/not_existing_url'
+      expect(response).to have_http_status(404)
+      expect(response.body).to match(/<html/)
+      expect(response.body).to match(%r{<title>404: Not Found</title>})
+      expect(response.body).to match(%r{<h1>404: Requested Ressource was not found.</h1>})
+      expect(response.body).to match(%r{No route matches \[GET\] /not_existing_url})
+
+      # 401
+      get '/api/v1/organizations'
+      expect(response).to have_http_status(401)
+      expect(response.body).to match(/<html/)
+      expect(response.body).to match(%r{<title>401: Unauthorized</title>})
+      expect(response.body).to match(%r{<h1>401: Unauthorized</h1>})
+      expect(response.body).to match(/authentication failed/)
+
+      # 422
+      get '/tests/unprocessable_entity'
+      expect(response).to have_http_status(422)
+      expect(response.body).to match(/<html/)
+      expect(response.body).to match(%r{<title>422: Unprocessable Entity</title>})
+      expect(response.body).to match(%r{<h1>422: The change you wanted was rejected.</h1>})
+      expect(response.body).to match(/some error message/)
+
+      # 401
+      get '/tests/not_authorized'
+      expect(response).to have_http_status(401)
+      expect(response.body).to match(/<html/)
+      expect(response.body).to match(%r{<title>401: Unauthorized</title>})
+      expect(response.body).to match(%r{<h1>401: Unauthorized</h1>})
+      expect(response.body).to match(/some error message/)
+
+      # 401
+      get '/tests/ar_not_found'
+      expect(response).to have_http_status(404)
+      expect(response.body).to match(/<html/)
+      expect(response.body).to match(%r{<title>404: Not Found</title>})
+      expect(response.body).to match(%r{<h1>404: Requested Ressource was not found.</h1>})
+      expect(response.body).to match(/some error message/)
+
+      # 500
+      get '/tests/standard_error'
+      expect(response).to have_http_status(500)
+      expect(response.body).to match(/<html/)
+      expect(response.body).to match(%r{<title>500: Something went wrong</title>})
+      expect(response.body).to match(%r{<h1>500: We're sorry, but something went wrong.</h1>})
+      expect(response.body).to match(/some error message/)
+
+      # 422
+      get '/tests/argument_error'
+      expect(response).to have_http_status(422)
+      expect(response.body).to match(/<html/)
+      expect(response.body).to match(%r{<title>422: Unprocessable Entity</title>})
+      expect(response.body).to match(%r{<h1>422: The change you wanted was rejected.</h1>})
+      expect(response.body).to match(/some error message/)
+    end
+  end
+
+end

+ 63 - 0
spec/requests/calendar_spec.rb

@@ -0,0 +1,63 @@
+require 'rails_helper'
+
+RSpec.describe 'Calendars', type: :request do
+
+  let(:admin_user) do
+    create(:admin_user)
+  end
+
+  describe 'request handling' do
+
+    it 'does calendar index with nobody' do
+      get '/api/v1/calendars', as: :json
+      expect(response).to have_http_status(401)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('authentication failed')
+
+      get '/api/v1/calendars_init', as: :json
+      expect(response).to have_http_status(401)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('authentication failed')
+    end
+
+    it 'does calendar index with admin' do
+      authenticated_as(admin_user)
+      get '/api/v1/calendars', as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+      expect(json_response.count).to eq(1)
+
+      get '/api/v1/calendars?expand=true', as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Array)
+      expect(json_response).to be_truthy
+      expect(json_response.count).to eq(1)
+
+      get '/api/v1/calendars?full=true', as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response).to be_truthy
+      expect(json_response['record_ids']).to be_truthy
+      expect(json_response['record_ids'].count).to eq(1)
+      expect(json_response['assets']).to be_truthy
+      expect(json_response['assets']).to be_present
+
+      # index
+      get '/api/v1/calendars_init', as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['record_ids']).to be_truthy
+      expect(json_response['ical_feeds']).to be_truthy
+      expect(json_response['ical_feeds']['http://www.google.com/calendar/ical/da.danish%23holiday%40group.v.calendar.google.com/public/basic.ics']).to eq('Denmark')
+      expect(json_response['ical_feeds']['http://www.google.com/calendar/ical/de.austrian%23holiday%40group.v.calendar.google.com/public/basic.ics']).to eq('Austria')
+      expect(json_response['timezones']).to be_truthy
+      expect(json_response['timezones']['Africa/Johannesburg']).to eq(2)
+      expect(json_response['timezones']['America/Sitka']).to eq(-8)
+      expect(json_response['assets']).to be_truthy
+    end
+  end
+
+end

+ 221 - 0
spec/requests/form_spec.rb

@@ -0,0 +1,221 @@
+require 'rails_helper'
+
+RSpec.describe 'Form', type: :request, searchindex: true do
+
+  before(:each) do
+    rebuild_searchindex
+  end
+
+  describe 'request handling' do
+
+    it 'does get config call' do
+      post '/api/v1/form_config', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('Not authorized')
+    end
+
+    it 'does get config call' do
+      Setting.set('form_ticket_create', true)
+      post '/api/v1/form_config', params: {}, as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('Not authorized')
+
+    end
+
+    it 'does get config call & do submit' do
+      Setting.set('form_ticket_create', true)
+      fingerprint = SecureRandom.hex(40)
+      post '/api/v1/form_config', params: { fingerprint: fingerprint }, as: :json
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['enabled']).to eq(true)
+      expect(json_response['endpoint']).to eq('http://zammad.example.com/api/v1/form_submit')
+      expect(json_response['token']).to be_truthy
+      token = json_response['token']
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: 'invalid' }, as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('Not authorized')
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token }, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+
+      expect(json_response['errors']).to be_truthy
+      expect(json_response['errors']['name']).to eq('required')
+      expect(json_response['errors']['email']).to eq('required')
+      expect(json_response['errors']['title']).to eq('required')
+      expect(json_response['errors']['body']).to eq('required')
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token, email: 'some' }, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+
+      expect(json_response['errors']).to be_truthy
+      expect(json_response['errors']['name']).to eq('required')
+      expect(json_response['errors']['email']).to eq('invalid')
+      expect(json_response['errors']['title']).to eq('required')
+      expect(json_response['errors']['body']).to eq('required')
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token, name: 'Bob Smith', email: 'discard@znuny.com', title: 'test', body: 'hello' }, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+
+      expect(json_response['errors']).to be_falsey
+      expect(json_response['ticket']).to be_truthy
+      expect(json_response['ticket']['id']).to be_truthy
+      expect(json_response['ticket']['number']).to be_truthy
+
+      travel 5.hours
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token, name: 'Bob Smith', email: 'discard@znuny.com', title: 'test', body: 'hello' }, as: :json
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+
+      expect(json_response['errors']).to be_falsey
+      expect(json_response['ticket']).to be_truthy
+      expect(json_response['ticket']['id']).to be_truthy
+      expect(json_response['ticket']['number']).to be_truthy
+
+      travel 20.hours
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token, name: 'Bob Smith', email: 'discard@znuny.com', title: 'test', body: 'hello' }, as: :json
+      expect(response).to have_http_status(401)
+
+    end
+
+    it 'does get config call & do submit' do
+      Setting.set('form_ticket_create', true)
+      fingerprint = SecureRandom.hex(40)
+      post '/api/v1/form_config', params: { fingerprint: fingerprint }, as: :json
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['enabled']).to eq(true)
+      expect(json_response['endpoint']).to eq('http://zammad.example.com/api/v1/form_submit')
+      expect(json_response['token']).to be_truthy
+      token = json_response['token']
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: 'invalid' }, as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('Not authorized')
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token }, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+
+      expect(json_response['errors']).to be_truthy
+      expect(json_response['errors']['name']).to eq('required')
+      expect(json_response['errors']['email']).to eq('required')
+      expect(json_response['errors']['title']).to eq('required')
+      expect(json_response['errors']['body']).to eq('required')
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token, email: 'some' }, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+
+      expect(json_response['errors']).to be_truthy
+      expect(json_response['errors']['name']).to eq('required')
+      expect(json_response['errors']['email']).to eq('invalid')
+      expect(json_response['errors']['title']).to eq('required')
+      expect(json_response['errors']['body']).to eq('required')
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token, name: 'Bob Smith', email: 'somebody@example.com', title: 'test', body: 'hello' }, as: :json
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+
+      expect(json_response['errors']).to be_truthy
+      expect(json_response['errors']['email']).to eq('invalid')
+
+    end
+
+    it 'does limits' do
+      skip('No ES configured') if !SearchIndexBackend.enabled?
+
+      Setting.set('form_ticket_create', true)
+      fingerprint = SecureRandom.hex(40)
+      post '/api/v1/form_config', params: { fingerprint: fingerprint }, as: :json
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['enabled']).to eq(true)
+      expect(json_response['endpoint']).to eq('http://zammad.example.com/api/v1/form_submit')
+      expect(json_response['token']).to be_truthy
+      token = json_response['token']
+
+      (1..20).each do |count|
+        travel 10.seconds
+        post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token, name: 'Bob Smith', email: 'discard@znuny.com', title: "test#{count}", body: 'hello' }, as: :json
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_a_kind_of(Hash)
+
+        expect(json_response['errors']).to be_falsey
+        expect(json_response['errors']).to be_falsey
+        expect(json_response['ticket']).to be_truthy
+        expect(json_response['ticket']['id']).to be_truthy
+        expect(json_response['ticket']['number']).to be_truthy
+        Scheduler.worker(true)
+        sleep 1 # wait until elasticsearch is index
+      end
+
+      sleep 10 # wait until elasticsearch is index
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token, name: 'Bob Smith', email: 'discard@znuny.com', title: 'test-last', body: 'hello' }, as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to be_truthy
+
+      @headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json', 'REMOTE_ADDR' => '1.2.3.5' }
+
+      (1..20).each do |count|
+        travel 10.seconds
+        post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token, name: 'Bob Smith', email: 'discard@znuny.com', title: "test-2-#{count}", body: 'hello' }, as: :json
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_a_kind_of(Hash)
+
+        expect(json_response['errors']).to be_falsey
+        expect(json_response['ticket']).to be_truthy
+        expect(json_response['ticket']['id']).to be_truthy
+        expect(json_response['ticket']['number']).to be_truthy
+        Scheduler.worker(true)
+        sleep 1 # wait until elasticsearch is index
+      end
+
+      sleep 10 # wait until elasticsearch is index
+
+      post '/api/v1/form_submit', params: { fingerprint: fingerprint, token: token, name: 'Bob Smith', email: 'discard@znuny.com', title: 'test-2-last', body: 'hello' }, as: :json
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to be_truthy
+    end
+
+    it 'does customer_ticket_create false disables form' do
+      Setting.set('form_ticket_create', false)
+      Setting.set('customer_ticket_create', true)
+
+      fingerprint = SecureRandom.hex(40)
+
+      post '/api/v1/form_config', params: { fingerprint: fingerprint }, as: :json
+
+      token = json_response['token']
+      params = {
+        fingerprint: fingerprint,
+        token: token,
+        name: 'Bob Smith',
+        email: 'discard@znuny.com',
+        title: 'test',
+        body: 'hello'
+      }
+
+      post '/api/v1/form_submit', params: params, as: :json
+
+      expect(response).to have_http_status(401)
+    end
+  end
+end

+ 237 - 0
spec/requests/integration/check_mk_spec.rb

@@ -0,0 +1,237 @@
+require 'rails_helper'
+
+RSpec.describe 'Integration Check MK', type: :request do
+
+  before(:each) do
+    token = SecureRandom.urlsafe_base64(16)
+    Setting.set('check_mk_token', token)
+    Setting.set('check_mk_integration', true)
+  end
+
+  describe 'request handling' do
+    it 'does fail without a token' do
+      post '/api/v1/integration/check_mk/', params: {}
+      expect(response).to have_http_status(404)
+    end
+
+    it 'does fail with invalid token and feature enabled' do
+      post '/api/v1/integration/check_mk/invalid_token', params: {}
+      expect(response).to have_http_status(422)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('Invalid token!')
+    end
+
+    it 'does create and close a ticket' do
+      params = {
+        event_id: '123',
+        state: 'down',
+        host: 'some host',
+        service: 'some service',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).to be_truthy
+      expect(json_response['ticket_id']).to be_truthy
+      expect(json_response['ticket_number']).to be_truthy
+
+      ticket = Ticket.find(json_response['ticket_id'])
+      expect(ticket.state.name).to eq('new')
+      expect(ticket.articles.count).to eq(1)
+
+      params = {
+        event_id: '123',
+        state: 'up',
+        host: 'some host',
+        service: 'some service',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).not_to be_empty
+      expect(json_response['ticket_ids']).to include(ticket.id)
+
+      ticket.reload
+      expect(ticket.state.name).to eq('closed')
+      expect(ticket.articles.count).to eq(2)
+    end
+
+    it 'does double create and auto close' do
+      params = {
+        event_id: '123',
+        state: 'down',
+        host: 'some host',
+        service: 'some service',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).to be_truthy
+      expect(json_response['ticket_id']).to be_truthy
+      expect(json_response['ticket_number']).to be_truthy
+
+      ticket = Ticket.find(json_response['ticket_id'])
+      expect(ticket.state.name).to eq('new')
+      expect(ticket.articles.count).to eq(1)
+
+      params = {
+        event_id: '123',
+        state: 'down',
+        host: 'some host',
+        service: 'some service',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).to eq('ticket already open, added note')
+      expect(json_response['ticket_ids']).to include(ticket.id)
+
+      ticket.reload
+      expect(ticket.state.name).to eq('new')
+      expect(ticket.articles.count).to eq(2)
+
+      params = {
+        event_id: '123',
+        state: 'up',
+        host: 'some host',
+        service: 'some service',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).to be_truthy
+      expect(json_response['ticket_ids']).to include(ticket.id)
+
+      ticket.reload
+      expect(ticket.state.name).to eq('closed')
+      expect(ticket.articles.count).to eq(3)
+    end
+
+    it 'does ticket close which get ignored' do
+      params = {
+        event_id: '123',
+        state: 'up',
+        host: 'some host',
+        service: 'some service',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).to eq('no open tickets found, ignore action')
+    end
+
+    it 'does double create and no auto close' do
+      Setting.set('check_mk_auto_close', false)
+      params = {
+        event_id: '123',
+        state: 'down',
+        host: 'some host',
+        service: 'some service',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).to be_truthy
+      expect(json_response['ticket_id']).to be_truthy
+      expect(json_response['ticket_number']).to be_truthy
+
+      ticket = Ticket.find(json_response['ticket_id'])
+      expect(ticket.state.name).to eq('new')
+      expect(ticket.articles.count).to eq(1)
+
+      params = {
+        event_id: '123',
+        state: 'down',
+        host: 'some host',
+        service: 'some service',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).to eq('ticket already open, added note')
+      expect(json_response['ticket_ids']).to include(ticket.id)
+
+      ticket.reload
+      expect(ticket.state.name).to eq('new')
+      expect(ticket.articles.count).to eq(2)
+
+      params = {
+        event_id: '123',
+        state: 'up',
+        host: 'some host',
+        service: 'some service',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).to eq('ticket already open, added note')
+      expect(json_response['ticket_ids']).to include(ticket.id)
+
+      ticket.reload
+      expect(ticket.state.name).to eq('new')
+      expect(ticket.articles.count).to eq(3)
+    end
+
+    it 'does double create and auto close - host only' do
+      params = {
+        event_id: '123',
+        state: 'down',
+        host: 'some host',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).to be_truthy
+      expect(json_response['ticket_id']).to be_truthy
+      expect(json_response['ticket_number']).to be_truthy
+
+      ticket = Ticket.find(json_response['ticket_id'])
+      expect(ticket.state.name).to eq('new')
+      expect(ticket.articles.count).to eq(1)
+
+      params = {
+        event_id: '123',
+        state: 'down',
+        host: 'some host',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).to eq('ticket already open, added note')
+      expect(json_response['ticket_ids']).to include(ticket.id)
+
+      ticket.reload
+      expect(ticket.state.name).to eq('new')
+      expect(ticket.articles.count).to eq(2)
+
+      params = {
+        event_id: '123',
+        state: 'up',
+        host: 'some host',
+      }
+      post "/api/v1/integration/check_mk/#{Setting.get('check_mk_token')}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['result']).to be_truthy
+      expect(json_response['ticket_ids']).to include(ticket.id)
+
+      ticket.reload
+      expect(ticket.state.name).to eq('closed')
+      expect(ticket.articles.count).to eq(3)
+    end
+  end
+
+end

+ 477 - 0
spec/requests/integration/cti_spec.rb

@@ -0,0 +1,477 @@
+require 'rails_helper'
+
+RSpec.describe 'Integration CTI', 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: '+4912347114711',
+      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('cti_integration', true)
+    Setting.set('cti_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=newCall&direction=in&from=4912347114711&to=4930600000000&call_id=4991155921769858278-1&user%5B%5D=user+1&user%5B%5D=user+2'
+      post '/api/v1/cti/not_existing_token', params: params
+      expect(response).to have_http_status(401)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('Invalid token, please contact your admin!')
+    end
+
+    it 'does basic call' do
+      token = Setting.get('cti_token')
+
+      # inbound - I
+      params = 'event=newCall&direction=in&from=4912347114711&to=4930600000000&call_id=4991155921769858278-1&user%5B%5D=user+1&user%5B%5D=user+2'
+      post "/api/v1/cti/#{token}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response).to be_blank
+
+      # inbound - II - block caller
+      params = 'event=newCall&direction=in&from=491715000000&to=4930600000000&call_id=4991155921769858278-2&user%5B%5D=user+1&user%5B%5D=user+2'
+      post "/api/v1/cti/#{token}", params: params
+      expect(response).to have_http_status(200)
+
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['action']).to eq('reject')
+      expect(json_response['reason']).to eq('busy')
+
+      # outbound - I - set default_caller_id
+      params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&call_id=8621106404543334274-3&user%5B%5D=user+1'
+      post "/api/v1/cti/#{token}", params: params
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['action']).to eq('dial')
+      expect(json_response['number']).to eq('4912347114711')
+      expect(json_response['caller_id']).to eq('4930777000000')
+
+      # outbound - II - set caller_id based on routing_table by explicite number
+      params = 'event=newCall&direction=out&from=4930600000000&to=491714000000&call_id=8621106404543334274-4&user%5B%5D=user+1'
+      post "/api/v1/cti/#{token}", params: params
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['action']).to eq('dial')
+      expect(json_response['number']).to eq('491714000000')
+      expect(json_response['caller_id']).to eq('41715880339000')
+
+      # outbound - III - set caller_id based on routing_table by 41*
+      params = 'event=newCall&direction=out&from=4930600000000&to=4147110000000&call_id=8621106404543334274-5&user%5B%5D=user+1'
+      post "/api/v1/cti/#{token}", params: params
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['action']).to eq('dial')
+      expect(json_response['number']).to eq('4147110000000')
+      expect(json_response['caller_id']).to eq('41715880339000')
+
+      # no config
+      Setting.set('cti_config', {})
+      params = 'event=newCall&direction=in&from=4912347114711&to=4930600000000&call_id=4991155921769858278-6&user%5B%5D=user+1&user%5B%5D=user+2'
+      post "/api/v1/cti/#{token}", params: params
+      expect(response).to have_http_status(422)
+      expect(json_response).to be_a_kind_of(Hash)
+      expect(json_response['error']).to eq('Feature not configured, please contact your admin!')
+
+    end
+
+    it 'does log call' do
+      token = Setting.get('cti_token')
+
+      # outbound - I - new call
+      params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&call_id=1234567890-1&user%5B%5D=user+1'
+      post "/api/v1/cti/#{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('4912347114711')
+      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=hangup&direction=out&call_id=1234567890-1&cause=cancel'
+      post "/api/v1/cti/#{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('4912347114711')
+      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=4930600000000&to=4912347114711&call_id=1234567890-2&user%5B%5D=user+1'
+      post "/api/v1/cti/#{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('4912347114711')
+      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=answer&direction=out&call_id=1234567890-2&from=4930600000000&to=4912347114711'
+      post "/api/v1/cti/#{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('4912347114711')
+      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=hangup&direction=out&call_id=1234567890-2&cause=normalClearing&from=4930600000000&to=4912347114711'
+      post "/api/v1/cti/#{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('4912347114711')
+      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=newCall&direction=in&to=4930600000000&from=4912347114711&call_id=1234567890-3&user%5B%5D=user+1'
+      post "/api/v1/cti/#{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('4930600000000')
+      expect(log.from).to eq('4912347114711')
+      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=answer&direction=in&call_id=1234567890-3&to=4930600000000&from=4912347114711'
+      post "/api/v1/cti/#{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('4930600000000')
+      expect(log.from).to eq('4912347114711')
+      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=hangup&direction=in&call_id=1234567890-3&cause=normalClearing&to=4930600000000&from=4912347114711'
+      post "/api/v1/cti/#{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('4930600000000')
+      expect(log.from).to eq('4912347114711')
+      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=newCall&direction=in&to=4930600000000&from=4912347114711&call_id=1234567890-4&user%5B%5D=user+1,user+2'
+      post "/api/v1/cti/#{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('4930600000000')
+      expect(log.from).to eq('4912347114711')
+      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=answer&direction=in&call_id=1234567890-4&to=4930600000000&from=4912347114711&user=voicemail'
+      post "/api/v1/cti/#{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('4930600000000')
+      expect(log.from).to eq('4912347114711')
+      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=hangup&direction=in&call_id=1234567890-4&cause=normalClearing&to=4930600000000&from=4912347114711'
+      post "/api/v1/cti/#{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('4930600000000')
+      expect(log.from).to eq('4912347114711')
+      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=newCall&direction=in&to=4930600000000&from=4912347114711&call_id=1234567890-5&user%5B%5D=user+1,user+2'
+      post "/api/v1/cti/#{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('4930600000000')
+      expect(log.from).to eq('4912347114711')
+      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=hangup&direction=in&call_id=1234567890-5&cause=normalClearing&to=4930600000000&from=4912347114711'
+      post "/api/v1/cti/#{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('4930600000000')
+      expect(log.from).to eq('4912347114711')
+      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=newCall&direction=in&to=4930600000000&from=49999992222222&call_id=1234567890-6&user%5B%5D=user+1,user+2'
+      post "/api/v1/cti/#{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('4930600000000')
+      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=newCall&direction=in&to=4930600000000&from=anonymous&call_id=1234567890-7&user%5B%5D=user+1,user+2'
+      post "/api/v1/cti/#{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('4930600000000')
+      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('4912347114711')
+      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

Some files were not shown because too many files changed in this diff