Browse Source

Added sipgate integration.

Martin Edenhofer 9 years ago
parent
commit
fcdf62cd13

+ 156 - 0
app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee

@@ -0,0 +1,156 @@
+class Index extends App.ControllerIntegrationBase
+  featureIntegration: 'sipgate_integration'
+  featureName: 'sipgate.io'
+  featureConfig: 'sipgate_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.']
+  ]
+
+  render: =>
+    super
+    new Form(
+      el: @$('.js-form')
+    )
+
+    new App.HttpLog(
+      el: @$('.js-log')
+      facility: 'sipgate.io'
+    )
+
+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
+
+    # check authentication
+    return if !@authenticate()
+
+    @subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false)
+
+  currentConfig: ->
+    config = App.Setting.get('sipgate_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('sipgate_config', value)
+
+  render: =>
+    @config = @currentConfig()
+
+    @html App.view('integration/sipgate')(
+      config: @config
+    )
+
+  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()
+    @config.inbound.block_caller_ids.push {
+      caller_id: @cleanupInput(caller_id)
+      note: note
+    }
+    @setConfig(@config)
+    @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()
+    @config.outbound.routing_table.push {
+      dest: dest
+      caller_id: caller_id
+      note: note
+    }
+    @setConfig(@config)
+    @render()
+
+  removeInboundBlockCallerId: (e) =>
+    e.preventDefault()
+    @updateCurrentConfig()
+    element = $(e.currentTarget).closest('tr')
+    element.remove()
+
+  removeOutboundRouting: (e) =>
+    e.preventDefault()
+    @updateCurrentConfig()
+    element = $(e.currentTarget).closest('tr')
+    element.remove()
+
+class State
+  @current: ->
+    App.Setting.get('sipgate_integration')
+
+App.Config.set(
+  'IntegrationSipgate'
+  {
+    name: 'sipgate.io'
+    target: '#system/integration/sipgate'
+    description: 'VoIP services provide.'
+    controller: Index
+    state: State
+  }
+  'NavBarIntegrations'
+)

+ 7 - 6
app/assets/javascripts/app/controllers/widget/http_log.coffee

@@ -9,22 +9,23 @@ class App.HttpLog extends App.Controller
 
   fetch: =>
     @ajax(
-      id:    'http_logs'
-      type:  'GET'
-      url:   "#{@apiPath}/http_logs/#{@facility}"
+      id:   'http_logs'
+      type: 'GET'
+      url:  "#{@apiPath}/http_logs/#{@facility}"
       data:
         limit: @limit || 50
       processData: true
       success: (data) =>
-        @records = data
-        @render()
+        if !@records[0] || (data[0] && @records[0] && data[0].updated_at isnt @records[0].updated_at)
+          @records = data
+          @render()
+        @delay(@fetch, 20000)
     )
 
   render: =>
     @html App.view('widget/http_log')(
       records: @records
     )
-    #@delay(message, 2000)
 
   show: (e) =>
     e.preventDefault()

+ 78 - 0
app/assets/javascripts/app/views/integration/sipgate.jst.eco

@@ -0,0 +1,78 @@
+<form>
+
+  <h2><%- @T('Inbound') %></h2>
+
+  <p><%- @T('Blocked caller ids based on sender caller id.') %>
+
+  <div class="settings-entry">
+    <table class="settings-list js-inboundBlockCallerId" style="width: 100%;">
+      <thead>
+        <tr>
+          <th width="50%"><%- @T('Caller id to block') %>
+          <th width="40%"><%- @T('Note') %>
+          <th width="10%"><%- @T('Action') %>
+      </thead>
+      <tbody>
+<% for row in @config.inbound.block_caller_ids: %>
+        <tr class="js-row">
+          <td class="settings-list-control-cell"><input name="caller_id" value="<%= row.caller_id %>" class="form-control form-control--small js-summary">
+          <td class="settings-list-control-cell"><input name="note" value="<%= row.note %>" class="form-control form-control--small js-summary">
+          <td class="settings-list-row-control"><div class="btn btn--text js-remove"><%- @Icon('trash') %> <%- @T('Remove') %></div>
+<% end %>
+        <tr>
+          <td class="settings-list-control-cell"><input name="caller_id" value="" placeholder="4930609854189" class="form-control form-control--small js-summary">
+          <td class="settings-list-control-cell"><input name="note" value="" placeholder="<%- @Ti('my onw note') %>" class="form-control form-control--small js-summary">
+          <td class="settings-list-row-control"><div class="btn btn--text btn--create js-add"><%- @Icon('plus-small') %> <%- @T('Add') %></div>
+      </tbody>
+    </table>
+  </div>
+
+  <h2><%- @T('Outbound') %></h2>
+
+  <p><%- @T('Set caller id of outbound calls based on destination caller id.') %>
+
+  <div class="settings-entry js-outboundRouting">
+    <table class="settings-list" style="width: 100%;">
+      <thead>
+        <tr>
+          <th width="30%"><%- @T('Destination caller id') %>
+          <th width="30%"><%- @T('Set outbound caller id') %>
+          <th width="30%"><%- @T('Note') %>
+          <th width="10%"><%- @T('Action') %>
+      </thead>
+      <tbody>
+<% for row in @config.outbound.routing_table: %>
+        <tr class="js-row">
+          <td class="settings-list-control-cell"><input name="dest" value="<%= row.dest %>" class="form-control form-control--small js-summary">
+          <td class="settings-list-control-cell"><input name="caller_id" value="<%= row.caller_id %>" class="form-control form-control--small js-summary">
+          <td class="settings-list-control-cell"><input name="note" value="<%= row.note %>" class="form-control form-control--small js-summary">
+          <td class="settings-list-row-control"><div class="btn btn--text js-remove"><%- @Icon('trash') %> <%- @T('Remove') %></div>
+<% end %>
+        <tr>
+          <td class="settings-list-control-cell"><input name="dest" value="" placeholder="49* or 3230123456789" class="form-control form-control--small js-summary">
+          <td class="settings-list-control-cell"><input name="caller_id" value="" placeholder="4930609854189" class="form-control form-control--small js-summary">
+          <td class="settings-list-control-cell"><input name="note" value="" placeholder="<%- @Ti('my onw note') %>" class="form-control form-control--small js-summary">
+          <td class="settings-list-row-control"><div class="btn btn--text btn--create js-add"><%- @Icon('plus-small') %> <%- @T('Add') %></div>
+      </tbody>
+    </table>
+  </div>
+
+  <p><%- @T('Default caller id.') %>
+
+  <div class="settings-entry">
+    <table class="settings-list" style="width: 100%;">
+      <thead>
+        <tr>
+          <th width="50%"><%- @T('Default caller id') %>
+          <th width="50%"><%- @T('Note') %>
+      </thead>
+      <tbody>
+        <tr>
+          <td class="settings-list-control-cell"><input name="default_caller_id" value="<%= @config.outbound.default_caller_id %>" placeholder="4930609854189" class="form-control form-control--small js-summary">
+          <td class="settings-list-row-control"><%- @T('Default caller id for outbound calls.') %>
+      </tbody>
+    </table>
+  </div>
+
+  <button type="submit" class="btn btn--primary"><%- @T('Save') %></button>
+</form>

+ 1 - 1
app/assets/javascripts/app/views/widget/http_log.jst.eco

@@ -1,6 +1,6 @@
 <hr>
 
-<%- @T('Recent logs') %>
+  <h2><%- @T('Recent logs') %></h2>
   <div class="settings-entry">
     <table class="settings-list" style="width: 100%;">
       <thead>

+ 60 - 1
app/controllers/application_controller.rb

@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
   helper_method :current_user,
                 :authentication_check,
                 :config_frontend,
+                :http_log_config,
                 :role?,
                 :model_create_render,
                 :model_update_render,
@@ -18,7 +19,7 @@ class ApplicationController < ActionController::Base
   before_action :cors_preflight_check
 
   after_action  :user_device_update, :set_access_control_headers
-  after_action  :trigger_events
+  after_action  :trigger_events, :http_log
 
   # For all responses in this controller, return the CORS access control headers.
   def set_access_control_headers
@@ -47,6 +48,10 @@ class ApplicationController < ActionController::Base
     false
   end
 
+  def http_log_config(config)
+    @http_log_support = config
+  end
+
   private
 
   # execute events
@@ -98,6 +103,60 @@ class ApplicationController < ActionController::Base
     session[:user_agent] = request.env['HTTP_USER_AGENT']
   end
 
+  # log http access
+  def http_log
+    return if !@http_log_support
+
+    # request
+    request_data = {
+      content: '',
+      content_type: request.headers['Content-Type'],
+      content_encoding: request.headers['Content-Encoding'],
+      source: request.headers['User-Agent'] || request.headers['Server'],
+    }
+    request.headers.each {|key, value|
+      next if key[0, 5] != 'HTTP_'
+      request_data[:content] += if key == 'HTTP_COOKIE'
+                                  "#{key}: xxxxx\n"
+                                else
+                                  "#{key}: #{value}\n"
+                                end
+    }
+    body = request.body.read
+    if body
+      request_data[:content] += "\n" + body
+    end
+    request_data[:content] = request_data[:content].slice(0, 8000)
+
+    # response
+    response_data = {
+      code: response.status = response.code,
+      content: '',
+      content_type: nil,
+      content_encoding: nil,
+      source: nil,
+    }
+    response.headers.each {|key, value|
+      response_data[:content] += "#{key}: #{value}\n"
+    }
+    body = response.body
+    if body
+      response_data[:content] += "\n" + body
+    end
+    response_data[:content] = response_data[:content].slice(0, 8000)
+    record = {
+      direction: 'in',
+      facility: @http_log_support[:facility],
+      url: url_for(only_path: false, overwrite_params: {}),
+      status: response.status,
+      ip: request.remote_ip,
+      request: request_data,
+      response: response_data,
+      method: request.method,
+    }
+    HttpLog.create(record)
+  end
+
   # user device recent action update
   def user_device_update
 

+ 115 - 0
app/controllers/integration/sipgate_controller.rb

@@ -0,0 +1,115 @@
+require 'builder'
+
+class Integration::SipgateController < ApplicationController
+  before_action { http_log_config facility: 'sipgate.io' }
+
+  # notify about inbound call / block inbound call
+  def in
+    return if feature_disabled
+
+    config = Setting.get('sipgate_config')
+    config_inbound = config[:inbound]
+    block_caller_ids = config_inbound[:block_caller_ids]
+
+    if params['event'] == 'newCall'
+
+      # check if call need to be blocked
+      block_caller_ids.each {|item|
+        next unless item[:caller_id] == params['from']
+        xml = Builder::XmlMarkup.new(indent: 2)
+        xml.instruct!
+        content = xml.Response('onHangup' => in_url, 'onAnswer' => in_url) do
+          xml.Reject('reason' => 'busy')
+        end
+
+        send_data content, type: 'application/xml; charset=UTF-8;'
+
+        params['Reject'] = 'busy'
+        Sessions.broadcast(
+          event: 'sipgate.io',
+          data: params
+        )
+        return true
+      }
+    end
+
+    xml = Builder::XmlMarkup.new(indent: 2)
+    xml.instruct!
+    content = xml.Response('onHangup' => in_url, 'onAnswer' => in_url)
+
+    send_data content, type: 'application/xml; charset=UTF-8;'
+
+    # search for caller
+    Sessions.broadcast(
+      event: 'sipgate.io',
+      data: params
+    )
+
+  end
+
+  # set caller id of outbound call
+  def out
+    return if feature_disabled
+
+    config = Setting.get('sipgate_config')
+    config_outbound = config[:outbound][:routing_table]
+    default_caller_id = config[:outbound][:default_caller_id]
+
+    xml = Builder::XmlMarkup.new(indent: 2)
+    xml.instruct!
+
+    # set callerId
+    content = nil
+    to      = params[:to]
+    if to
+      config_outbound.each {|row|
+        dest = row[:dest].gsub(/\*/, '.+?')
+        next if to !~ /^#{dest}$/
+        content = xml.Response('onHangup' => in_url, 'onAnswer' => in_url) do
+          xml.Dial(callerId: row[:caller_id]) { xml.Number(params[:to]) }
+        end
+        break
+      }
+      if !content && default_caller_id
+        content = xml.Response('onHangup' => in_url, 'onAnswer' => in_url) do
+          xml.Dial(callerId: default_caller_id) { xml.Number(params[:to]) }
+        end
+      end
+    else
+      content = xml.Response('onHangup' => in_url, 'onAnswer' => in_url)
+    end
+
+    send_data content,
+              type: 'application/xml; charset=UTF-8;'
+
+    # notify about outbound call
+    Sessions.broadcast(
+      event: 'sipgate.io:out',
+      data: params
+    )
+  end
+
+  private
+
+  def feature_disabled
+    if !Setting.get('sipgate_integration')
+      render(
+        json: {},
+        status: :unauthorized
+      )
+      return true
+    end
+    false
+  end
+
+  def base_url
+    http_type = Setting.get('http_type')
+    fqdn      = Setting.get('fqdn')
+
+    "#{http_type}://#{fqdn}/api/v1/sipgate"
+  end
+
+  def in_url
+    "#{base_url}/in"
+  end
+end

+ 6 - 0
config/routes/integration_sipgate.rb

@@ -0,0 +1,6 @@
+Zammad::Application.routes.draw do
+
+  match '/api/v1/sipgate/in',     to: 'integration/sipgate#in', via: :post
+  match '/api/v1/sipgate/out',    to: 'integration/sipgate#out', via: :post
+
+end

+ 37 - 0
db/migrate/20160421000001_add_sipgate_integration.rb

@@ -0,0 +1,37 @@
+class AddSipgateIntegration < ActiveRecord::Migration
+  def up
+    Setting.create_if_not_exists(
+      title: 'sipgate.io integration',
+      name: 'sipgate_integration',
+      area: 'Integration::Switch',
+      description: 'Define if sipgate.io (http://www.sipgate.io) is enabled or not.',
+      options: {
+        form: [
+          {
+            display: '',
+            null: true,
+            name: 'sipgate_integration',
+            tag: 'boolean',
+            options: {
+              true  => 'yes',
+              false => 'no',
+            },
+          },
+        ],
+      },
+      state: false,
+      preferences: { prio: 1 },
+      frontend: false
+    )
+    Setting.create_if_not_exists(
+      title: 'sipgate.io config',
+      name: 'sipgate_config',
+      area: 'Integration::Sipgate',
+      description: 'Define the sipgate.io config.',
+      options: {},
+      state: {},
+      frontend: false,
+      preferences: { prio: 2 },
+    )
+  end
+end

+ 33 - 0
db/seeds.rb

@@ -1822,6 +1822,39 @@ Setting.create_if_not_exists(
   frontend: false,
   preferences: { prio: 2 },
 )
+Setting.create_if_not_exists(
+  title: 'sipgate.io integration',
+  name: 'sipgate_integration',
+  area: 'Integration::Switch',
+  description: 'Define if sipgate.io (http://www.sipgate.io) is enabled or not.',
+  options: {
+    form: [
+      {
+        display: '',
+        null: true,
+        name: 'sipgate_integration',
+        tag: 'boolean',
+        options: {
+          true  => 'yes',
+          false => 'no',
+        },
+      },
+    ],
+  },
+  state: false,
+  preferences: { prio: 1 },
+  frontend: false
+)
+Setting.create_if_not_exists(
+  title: 'sipgate.io config',
+  name: 'sipgate_config',
+  area: 'Integration::Sipgate',
+  description: 'Define the sipgate.io config.',
+  options: {},
+  state: {},
+  frontend: false,
+  preferences: { prio: 2 },
+)
 
 signature = Signature.create_if_not_exists(
   id: 1,

+ 184 - 0
test/integration/sipgate_controller_test.rb

@@ -0,0 +1,184 @@
+# encoding: utf-8
+require 'test_helper'
+require 'rexml/document'
+
+class SipgateControllerTest < ActionDispatch::IntegrationTest
+  setup do
+
+    Setting.create_or_update(
+      title: 'sipgate.io integration',
+      name: 'sipgate_integration',
+      area: 'Integration::Switch',
+      description: 'Define if sipgate.io (http://www.sipgate.io) is enabled or not.',
+      options: {
+        form: [
+          {
+            display: '',
+            null: true,
+            name: 'sipgate_integration',
+            tag: 'boolean',
+            options: {
+              true  => 'yes',
+              false => 'no',
+            },
+          },
+        ],
+      },
+      state: true,
+      preferences: { prio: 1 },
+      frontend: false
+    )
+    Setting.create_or_update(
+      title: 'sipgate.io config',
+      name: 'sipgate_config',
+      area: 'Integration::Sipgate',
+      description: 'Define the sipgate.io config.',
+      options: {},
+      state: {
+        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,
+          },
+        }
+      },
+      frontend: false,
+      preferences: { prio: 2 },
+    )
+
+  end
+
+  test 'basic call' do
+
+    # inbound - I
+    params = 'event=newCall&direction=in&from=4912347114711&to=4930600000000&callId=4991155921769858278&user%5B%5D=user+1&user%5B%5D=user+2'
+    post '/api/v1/sipgate/in', params
+    assert_response(200)
+    on_hangup = nil
+    on_answer = nil
+    content = @response.body
+    response = REXML::Document.new(content)
+    response.elements.each('Response') do |element|
+      on_hangup = element.attributes['onHangup']
+      on_answer = element.attributes['onAnswer']
+    end
+    assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_hangup)
+    assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_answer)
+
+    # inbound - II - block caller
+    params = 'event=newCall&direction=in&from=491715000000&to=4930600000000&callId=4991155921769858278&user%5B%5D=user+1&user%5B%5D=user+2'
+    post '/api/v1/sipgate/in', params
+    assert_response(200)
+    on_hangup = nil
+    on_answer = nil
+    content = @response.body
+    response = REXML::Document.new(content)
+    response.elements.each('Response') do |element|
+      on_hangup = element.attributes['onHangup']
+      on_answer = element.attributes['onAnswer']
+    end
+    assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_hangup)
+    assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_answer)
+    reason = nil
+    response.elements.each('Response/Reject') do |element|
+      reason = element.attributes['reason']
+    end
+    assert_equal('busy', reason)
+
+    # outbound - I - set default_caller_id
+    params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&callId=8621106404543334274&user%5B%5D=user+1'
+    post '/api/v1/sipgate/out', params
+    assert_response(200)
+    on_hangup = nil
+    on_answer = nil
+    caller_id = nil
+    number_to_dail = nil
+    content = @response.body
+    response = REXML::Document.new(content)
+    response.elements.each('Response') do |element|
+      on_hangup = element.attributes['onHangup']
+      on_answer = element.attributes['onAnswer']
+    end
+    response.elements.each('Response/Dial') do |element|
+      caller_id = element.attributes['callerId']
+    end
+    response.elements.each('Response/Dial/Number') do |element|
+      number_to_dail = element.text
+    end
+    assert_equal('4930777000000', caller_id)
+    assert_equal('4912347114711', number_to_dail)
+    assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_hangup)
+    assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_answer)
+
+    # outbound - II - set caller_id based on routing_table by explicite number
+    params = 'event=newCall&direction=out&from=4930600000000&to=491714000000&callId=8621106404543334274&user%5B%5D=user+1'
+    post '/api/v1/sipgate/out', params
+    assert_response(200)
+    on_hangup = nil
+    on_answer = nil
+    caller_id = nil
+    number_to_dail = nil
+    content = @response.body
+    response = REXML::Document.new(content)
+    response.elements.each('Response') do |element|
+      on_hangup = element.attributes['onHangup']
+      on_answer = element.attributes['onAnswer']
+    end
+    response.elements.each('Response/Dial') do |element|
+      caller_id = element.attributes['callerId']
+    end
+    response.elements.each('Response/Dial/Number') do |element|
+      number_to_dail = element.text
+    end
+    assert_equal('41715880339000', caller_id)
+    assert_equal('491714000000', number_to_dail)
+    assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_hangup)
+    assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_answer)
+
+    # outbound - III - set caller_id based on routing_table by 41*
+    params = 'event=newCall&direction=out&from=4930600000000&to=4147110000000&callId=8621106404543334274&user%5B%5D=user+1'
+    post '/api/v1/sipgate/out', params
+    assert_response(200)
+    on_hangup = nil
+    on_answer = nil
+    caller_id = nil
+    number_to_dail = nil
+    content = @response.body
+    response = REXML::Document.new(content)
+    response.elements.each('Response') do |element|
+      on_hangup = element.attributes['onHangup']
+      on_answer = element.attributes['onAnswer']
+    end
+    response.elements.each('Response/Dial') do |element|
+      caller_id = element.attributes['callerId']
+    end
+    response.elements.each('Response/Dial/Number') do |element|
+      number_to_dail = element.text
+    end
+    assert_equal('41715880339000', caller_id)
+    assert_equal('4147110000000', number_to_dail)
+    assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_hangup)
+    assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_answer)
+
+  end
+
+end