asterisk-ajam 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZTg2MWYzN2QwNzg3ZjViMmMxYzFlNGNjZmM1Y2JmZTY4ZGI2MGEyNw==
5
+ data.tar.gz: !binary |-
6
+ ZTVmY2E0OTdlNzVhNDBmNDFjZjkwMmQ4NTRkN2U5NDBjZGMxOGU0ZQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MDViOGI3OTU3NTVkMTE5ZjY0NDcxMzc3N2FiYTA0NjZkMWI0ZTZmZGQwYjBk
10
+ ZWJhYTQzMzc5MmRiMzkwOWYxMWYzOGFiZjE4MDc1MmUwMjYxYzcwYWI3ZDg4
11
+ YjcwNjU5NzQzZWVlYTIyNmZiZDdiMGFjZmI5NTM2NDcyZGZkZjE=
12
+ data.tar.gz: !binary |-
13
+ ZDJiMmZiNTkxMWVlZDdhZjUxODM1NzRmOGI0OThmOGJjNzFkZGUzY2QzZjE0
14
+ NjlmNDY2NzQ3Y2UzMGM3MjJjZDMyNDI1NDYwYTcxZTg0MTc1MmU1NzIwNTc3
15
+ MjlhZjcxNDM5OTY5MjA0OTk2NzE1NGRlNDc1ODA2NDU2M2ZkNGM=
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'simplecov', :require => false, :group => :test
4
+ gem 'coveralls', :require => false
5
+ # Specify your gem's dependencies in asterisk-ajam.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Stas Kobzar
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # Asterisk::AJAM
2
+ [![Build Status](https://travis-ci.org/staskobzar/asterisk-ajam.png?branch=master)](https://travis-ci.org/staskobzar/asterisk-ajam)
3
+ [![Coverage Status](https://coveralls.io/repos/staskobzar/asterisk-ajam/badge.png?branch=master)](https://coveralls.io/r/staskobzar/asterisk-ajam?branch=master)
4
+
5
+ * * *
6
+ This is a very simple library that allows to comunicate with [Asterisk PBX](http://www.asterisk.org/) using [Asterisk Management Interface (AMI) actions](https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AMI+Actions).
7
+ Library communicates with AMI through HTTP(s) protocol. HTTP interface is provided by Asterisk PBX and called [AJAM](https://wiki.asterisk.org/wiki/pages/viewpage.action?pageId=4817256)
8
+
9
+ This library does not provide events interface to AMI. It only sends actions/commands and read responses back. If you need real AMI interface please, check [Adhearsion framework](http://adhearsion.com/) which is very powerful and provides a lot of functionality.
10
+
11
+ This library can use HTTTP Authentication username/password for AJAM servers behind proxy protected with basic access authentication scheme and HTTPS (SSL).
12
+
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'asterisk-ajam'
19
+
20
+ And then execute:
21
+
22
+ $ bundle install
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install asterisk-ajam
27
+
28
+ ## Usage
29
+
30
+ You can check file [example/ajam.rb](https://github.com/staskobzar/asterisk-ajam/blob/master/example/ajam.rb) for some examples (comes with source).
31
+
32
+ Simple example:
33
+ ```ruby
34
+ require 'asterisk/ajam'
35
+ ajam = Asterisk::AJAM.connect :uri => 'http://127.0.0.1:8088/mxml',
36
+ :ami_user => 'admin',
37
+ :ami_password => 'passw0rd'
38
+ if ajam.connected?
39
+ ajam.command 'dialplan reload'
40
+ res = ajam.action_sippeers
41
+ res.list.each {|peer| puts peer['objectname']}
42
+ end
43
+ ```
44
+
45
+ List of argument options for ```Asterisk::AJAM.connect```:
46
+
47
+ * ```:uri``` URI of AJAM server. See [Asterisk documentation](https://wiki.asterisk.org/wiki/pages/viewpage.action?pageId=4817256) which explains how to configure AJAM server.
48
+ * ```:ami_user``` AMI user username
49
+ * ```:ami_password``` AMI user password
50
+ * ```:proxy_user``` proxy username for HTTP Authentication
51
+ * ```:proxy_pass``` proxy password for HTTP Authentication
52
+
53
+ If URI scheme is "https" then library enables SSL use. Please, note that current version does not enable verify mode of Net::HTTPS ruby library so it will accept any SSL certiificates without verifications.
54
+
55
+ ```Asterisk::AJAM.connect``` returns ```Asterisk::AJAM::Session``` class which provides several useful methods:
56
+
57
+ ```connected?``` returns true if successfully logged in AMI
58
+
59
+ ```command``` send command to AMI and read response. Example:
60
+ ```ruby
61
+ ajam.command "dialplan reload"
62
+ ajam.command "core restart when convenient"
63
+ ```
64
+
65
+ ```action_NAME``` sends action to AMI. NAME must be replaced with AMI action name (see: [Asterisk documentation](https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AMI+Actions)). Accepts hash as an argument. Example:
66
+ ```ruby
67
+ ajam.action_agents # no arguments
68
+ ajam.action_mailboxcount mailbox: "5555@default"
69
+ ajam.action_originate :channel => 'Local/local-chan@from-pstn',
70
+ :application => 'MusicOnHold',
71
+ :callerid => 'John Doe <5555555>'
72
+ ```
73
+
74
+ Actions and Commands return ```Asterisk::AJAM::Response``` class with several methods where response data can be found:
75
+
76
+ * list
77
+ * attribute
78
+ * data
79
+
80
+ ```ruby
81
+ res = ajam.action_meetmelist
82
+ pp res.list
83
+ pp res.attribute
84
+ pp res.data
85
+ ```
86
+
87
+ ## Using Apache proxy tutorial
88
+ As an additional security measure Apache proxy with HTTP Authentication can be used. Here is an example of Apache Virtual Host configuration as Proxy to AJAM server
89
+
90
+ ```
91
+ Listen 8888
92
+ NameVirtualHost *:8888
93
+ <VirtualHost *:8888>
94
+ <Proxy *>
95
+ Order deny,allow
96
+ Allow from all
97
+ AuthName "AJAM"
98
+ AuthType Basic
99
+ AuthUserFile /etc/httpd/.htpasswd
100
+ require valid-user
101
+ </Proxy>
102
+ ProxyPass / http://ajamhost.com:8088/
103
+ ProxyPassReverse / http://ajamhost.com:8088/
104
+ </VirtualHost>
105
+ ```
106
+
107
+ Generate password file:
108
+ ```
109
+ htpasswd -b -c /etc/httpd/.htpasswd admin passw0rd
110
+ ```
111
+
112
+ Connect to proxy:
113
+ ```ruby
114
+ ajam = Asterisk::AJAM.connect :uri => 'http://ajamproxy.com:8888/mxml',
115
+ :ami_user => 'admin',
116
+ :ami_password => 'amp111',
117
+ :proxy_user => 'admin',
118
+ :proxy_pass => 'passw0rd'
119
+ ```
120
+
121
+ ## Contributing
122
+
123
+ 1. Fork it
124
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
125
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
126
+ 4. Push to the branch (`git push origin my-new-feature`)
127
+ 5. Create new Pull Request
128
+
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+ require 'rdoc/task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.rspec_opts = "--color"
7
+ end
8
+
9
+ RSpec::Core::RakeTask.new(:spec_doc) do |t|
10
+ t.rspec_opts = "--color --format doc"
11
+ end
12
+
13
+ RSpec::Core::RakeTask.new(:spec_html) do |t|
14
+ t.rspec_opts = "--format html -o spec/reports/index.html"
15
+ end
16
+
17
+ Rake::RDocTask.new do |rd|
18
+ rd.main = "README.md"
19
+ rd.rdoc_files.include("README.md","lib/**/*.rb")
20
+ rd.title = 'Asterisk::AJAM: Ruby module for interacting with Asterisk management interface (AMI) via HTTP'
21
+ rd.rdoc_dir = "doc"
22
+ rd.options << '--line-numbers'
23
+ rd.options << '--all'
24
+ end
25
+
26
+ task :default => :spec
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'asterisk/ajam/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "asterisk-ajam"
8
+ spec.version = Asterisk::AJAM::VERSION
9
+ spec.authors = ["Stas Kobzar"]
10
+ spec.email = ["stas@modulis.ca"]
11
+ spec.description = %q{Ruby module for interacting with Asterisk management interface (AMI) via HTTP}
12
+ spec.summary = %q{AMI via HTTP}
13
+ spec.homepage = "https://github.com/staskobzar/asterisk-ajam"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.3"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "rdoc"
23
+ spec.add_development_dependency "rspec"
24
+ #spec.add_development_dependency "fakeweb"
25
+ spec.add_runtime_dependency "libxml-ruby"
26
+ end
data/example/ajam.rb ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ require 'asterisk/ajam'
3
+ require 'pp'
4
+ ajam = Asterisk::AJAM.connect :uri => 'http://127.0.0.1:8088/mxml',
5
+ :ami_user => 'admin',
6
+ :ami_password => 'amp111',
7
+ :proxy_user => 'admin',
8
+ :proxy_pass => 'admin'
9
+
10
+ pp ajam.connected?
11
+ # actions
12
+ res = ajam.action_sippeers
13
+ pp res.list
14
+
15
+ ajam = Asterisk::AJAM.connect :uri => 'https://127.0.0.1:9443/mxml',
16
+ :ami_user => 'admin',
17
+ :ami_password => 'amp111',
18
+ :use_ssl => true
19
+ res = ajam.action_corestatus
20
+ pp res.attribute
21
+ pp res.list
22
+
23
+ res = ajam.action_sipshowpeer peer: '5555'
24
+ pp res.attribute
25
+
26
+ # commands
27
+ res = ajam.command 'sip show peer 5555'
28
+ pp res.data
29
+
30
+ res = ajam.command 'dialplan reload'
31
+ pp res.data
@@ -0,0 +1,113 @@
1
+ require 'libxml'
2
+
3
+ #
4
+ # = asterisk/ajam/response.rb
5
+ #
6
+ module Asterisk
7
+ module AJAM
8
+ # Exception raised when HTTP response body is invalid
9
+ class InvalidHTTPBody < StandardError;end #:nodoc:
10
+
11
+ #
12
+ # Generic class to process and store responses from
13
+ # Asterisk AJAM server. Stores data from HTTP response
14
+ # and xml document received from Asterisk server
15
+ #
16
+ class Response
17
+
18
+ # HTTP response code
19
+ attr_reader :code
20
+
21
+ # AJAM session id
22
+ attr_reader :session_id
23
+
24
+ # Response eventlist from action (for example: sip peers)
25
+ attr_reader :list
26
+
27
+ # Response attributes
28
+ attr_reader :attribute
29
+
30
+ # Creates new Response class instance. Sets instance
31
+ # variables from HTTP Response (like code). Parses body.
32
+ def initialize(http)
33
+ raise ArgumentError,
34
+ "Expected Net::HTTP::Response. Got #{http.class}" unless http.is_a?(Net::HTTPResponse)
35
+ @attribute = []
36
+ @list = []
37
+ @code = http.code.to_i
38
+ return unless httpok?
39
+ parse_body http.body
40
+ set_session_id http
41
+ end
42
+
43
+ # HTTP request status
44
+ def httpok?
45
+ @code.eql? 200
46
+ end
47
+
48
+ # Is AJAM action/command successful
49
+ def success?
50
+ @success
51
+ end
52
+
53
+ # opaque_data from command response
54
+ def data
55
+ @data || @nodes
56
+ end
57
+
58
+ private
59
+ # Parses HTTP response body.
60
+ # Body should by xml string. Otherwise will raise
61
+ # exception InvalidHTTPBody
62
+ def parse_body xml
63
+ set_nodes xml
64
+ verify_response
65
+ set_eventlist
66
+ end
67
+
68
+ # parse xml body and set result to internal variable for farther processing
69
+ def set_nodes xml
70
+ raise InvalidHTTPBody,
71
+ "Empty response body" if xml.to_s.empty?
72
+ src = LibXML::XML::Parser.string(xml).parse
73
+ @nodes = src.root.find('response/generic').to_a
74
+ end
75
+
76
+ #
77
+ # Check if AJAM response is successfull and set internal variable
78
+ # @success
79
+ def verify_response
80
+ node = @nodes.shift
81
+ @success = ['success', 'follows'].include? node[:response].to_s.downcase
82
+ @attribute = node.attributes.to_h
83
+
84
+ set_command_data
85
+ end
86
+
87
+ # extract mansession_id from cookies
88
+ def set_session_id(http)
89
+ if /mansession_id=(['"])([^\1]+)\1/ =~ http['Set-Cookie']
90
+ @session_id = $2
91
+ end
92
+ end
93
+
94
+ # for reponses that contain eventlist of values set it to
95
+ # internal attributes
96
+ def set_eventlist
97
+ return unless @attribute['eventlist'].to_s.downcase.eql? 'start'
98
+ @nodes.each do |node|
99
+ next if node['eventlist'].eql? 'Complete'
100
+ @list << node.attributes.to_h
101
+ end
102
+ end
103
+
104
+ def set_command_data
105
+ if @attribute.has_key?('opaque_data')
106
+ @data = @attribute['opaque_data']
107
+ @attribute.delete 'opaque_data'
108
+ end
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,158 @@
1
+ require 'net/https' # this will also include net/http and uri
2
+ #
3
+ # = asterisk/ajam/session.rb
4
+ #
5
+ module Asterisk
6
+ module AJAM
7
+ #
8
+ # == Session class for Asterisk AJAM
9
+ # Used to controll connection to Astersik AJAM via HTTP protocol
10
+ #
11
+
12
+ # Session errors
13
+ # This class extends StandardError and raises when problem with
14
+ # AJAM URL found, for ex: missing scheme or hostname
15
+ # TODO: should be also used for HTTPS connections
16
+ class InvalidURI < StandardError; end #:nodoc:
17
+
18
+ # This class extends StandardError and raises when problems
19
+ # with loggin AJAM server found or when missing importent
20
+ # parameters like username or password.
21
+ class InvalidAMILogin < StandardError; end #:nodoc:
22
+
23
+ # Class extends StandardError and is raised when trying to
24
+ # send command to not AJAM server where not logged in
25
+ class NotLoggedIn < StandardError; end #:nodoc:
26
+
27
+ # This class establish connection to AJAM server using TCP connection
28
+ # and HTTP protocol.
29
+ class Session
30
+
31
+ # Asterisk AJAM server URI
32
+ # URI class instance
33
+ attr_accessor :uri
34
+
35
+ # AMI user name
36
+ attr_accessor :ami_user
37
+
38
+ # AMI password
39
+ attr_accessor :ami_password
40
+
41
+ # http access user
42
+ attr_accessor :proxy_user
43
+
44
+ # http access password
45
+ attr_accessor :proxy_pass
46
+
47
+ # Create new Asterisk AJAM session without initializing
48
+ # TCP network connection
49
+ def initialize(options={})
50
+ self.uri = options[:uri]
51
+ @ami_user = options[:ami_user]
52
+ @ami_password = options[:ami_password]
53
+ @proxy_pass = options[:proxy_pass]
54
+ @proxy_user = options[:proxy_user]
55
+ @use_ssl = options[:use_ssl]
56
+ end
57
+
58
+ # login action. Also stores session identificator for
59
+ # sending many actions within same session
60
+ def login
61
+ ami_user_valid?
62
+ ami_pass_valid?
63
+ send_action :login, username: @ami_user, secret: @ami_password
64
+ self
65
+ end
66
+
67
+ # send AMI command
68
+ def command(command)
69
+ action_command command: command
70
+ end
71
+
72
+ # Verify if session esteblished connection and set session id
73
+ def connected?
74
+ /^[0-9a-z]{8}$/.match(@response.session_id).is_a? MatchData
75
+ end
76
+
77
+ private
78
+ # handling action_ methods
79
+ def method_missing(method, *args)
80
+ method = method.id2name
81
+ raise NoMethodError,
82
+ "Undefined method #{method}" unless /^action_\w+$/.match(method)
83
+ raise NotLoggedIn, "Not logged in" unless connected?
84
+ send_action method.sub(/^action_/,'').to_sym, *args
85
+ end
86
+
87
+ # send action to Asterisk AJAM server
88
+ def send_action(action, params={})
89
+ set_params Hash[action: action].merge params
90
+ @response = http_send_action
91
+ end
92
+
93
+ # Send HTTP request to AJAM server using "#uri"
94
+ def http_send_action
95
+ http = http_inst
96
+ req = http_post
97
+ Response.new http.request req
98
+ end
99
+
100
+ # create new Net::HTTP instance
101
+ def http_inst
102
+ http = Net::HTTP.new(@uri.host, @uri.port)
103
+ if @uri.scheme.downcase.eql? 'https'
104
+ http.use_ssl = true
105
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
106
+ end
107
+ http
108
+ end
109
+
110
+ # create new Net::HTTP::Post instance
111
+ def http_post
112
+ req = Net::HTTP::Post.new @uri.request_uri, request_headers
113
+ req.set_form_data params
114
+ req.basic_auth @proxy_user, @proxy_pass if @proxy_pass && @proxy_user
115
+ req
116
+ end
117
+
118
+ # Post parameters
119
+ def params
120
+ @params
121
+ end
122
+
123
+ # set AJAM POST parameters
124
+ def set_params(params)
125
+ @params = params
126
+ end
127
+
128
+ # verifies if AMI username is set and not empty
129
+ def ami_user_valid?
130
+ raise InvalidAMILogin,
131
+ "Missing AMI username" if @ami_user.to_s.empty?
132
+ end
133
+
134
+ # verifies if AMI password (secret) is set and not empty
135
+ def ami_pass_valid?
136
+ raise InvalidAMILogin,
137
+ "Missing AMI user pass" if @ami_password.to_s.empty?
138
+ end
139
+
140
+ # setup AJAM URI
141
+ def uri=(uri)
142
+ @uri = URI.parse uri
143
+ raise InvalidURI,
144
+ "No AJAM URI given" unless @uri
145
+ raise InvalidURI,
146
+ "Unsupported uri.scheme" unless %w/http https/.include? @uri.scheme
147
+ end
148
+
149
+ # initialize request headers for Net::HTTPRequest class
150
+ def request_headers
151
+ return nil unless @response
152
+ Hash[
153
+ 'Cookie' => %Q!mansession_id="#{@response.session_id}"!
154
+ ]
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,5 @@
1
+ module Asterisk
2
+ module AJAM
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ require 'asterisk/ajam/version'
2
+ require 'asterisk/ajam/session'
3
+ require 'asterisk/ajam/response'
4
+
5
+ module Asterisk
6
+
7
+ module AJAM
8
+ def self.connect(options)
9
+ Session.new(options).login
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ module Asterisk
4
+ module AJAM
5
+ describe Response do
6
+ # stub successful login
7
+ let(:res_login_ok) do
8
+ http = get_simple_httpok
9
+ http.stub(:body => get_body_success_login)
10
+ http
11
+ end
12
+ # stub failed login
13
+ let(:res_login_fail) do
14
+ http = get_simple_httpok
15
+ http.stub(:body => get_body_failed_login)
16
+ http
17
+ end
18
+
19
+ #new
20
+ describe "#new" do
21
+ it "raises error if parameter is not Net::HTTPResponse instance" do
22
+ expect{
23
+ Response.new nil
24
+ }.to raise_error(ArgumentError)
25
+ end
26
+
27
+ it "raises error if empty response body" do
28
+ http = get_simple_httpok
29
+ http.stub(:body => '')
30
+ expect{
31
+ Response.new http
32
+ }.to raise_error(InvalidHTTPBody)
33
+ end
34
+
35
+ it "raises error if invalid xml response body" do
36
+ http = get_simple_httpok
37
+ http.stub(:body => '<a>this is not xml')
38
+ expect{
39
+ Response.new http
40
+ }.to raise_error(LibXML::XML::Error)
41
+ end
42
+
43
+ it "set session id" do
44
+ sid = "df901b5f"
45
+ res = res_login_ok
46
+ res.stub(:[]).with('Set-Cookie').and_return(%Q[mansession_id="#{sid}"; Version=1; Max-Age=60])
47
+ response = Response.new res
48
+ response.session_id.should eql(sid)
49
+ end
50
+ end
51
+
52
+ #httpok?
53
+ describe "#httpok?" do
54
+ it "returns true if successfully received response from AJAM" do
55
+ res = Response.new res_login_ok
56
+ res.should be_httpok
57
+ end
58
+ it "returns false if response from AJAM anything but 200 OK" do
59
+ res = Response.new get_http_unauth
60
+ res.should_not be_httpok
61
+ end
62
+ end
63
+
64
+ #success?
65
+ describe "#success?" do
66
+ it "returns true if AJAM response is successful" do
67
+ res = Response.new res_login_ok
68
+ res.should be_success
69
+ end
70
+ it "returns false if AJAM response id error" do
71
+ res = Response.new res_login_fail
72
+ res.should_not be_success
73
+ end
74
+ end
75
+
76
+ describe "actions" do
77
+ it "set recieved response list to class attributes" do
78
+ http = get_simple_httpok
79
+ http.stub(:body => get_body_sippeers)
80
+ res = Response.new http
81
+ res.list.first.should include('event' => 'PeerEntry', 'objectname' => '5555')
82
+ res.list.last.should include('objectname' => '8903')
83
+ end
84
+
85
+ it "when response has single result then it stored in :response" do
86
+ http = get_simple_httpok
87
+ http.stub(:body => get_body_corestatus)
88
+ res = Response.new http
89
+ res.attribute['corestartuptime'].should eql('17:13:00')
90
+ end
91
+ end
92
+
93
+ describe "#command" do
94
+ it "set data variable with response from AJAM" do
95
+ http = get_simple_httpok
96
+ http.stub(:body => cmd_body_dialplan_reload)
97
+ res = Response.new http
98
+ res.data.should match(/Dialplan reloaded/)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ module Asterisk
4
+ module AJAM
5
+ describe Session do
6
+ let(:uri_http){'http://ajam.asterisk.com:8088/mxml'}
7
+ let(:manses_id){"84d22b60"}
8
+ let(:http) do
9
+ http = get_simple_httpok
10
+ http.stub(:body => get_body_success_login)
11
+ http.stub(:[]).with('Set-Cookie').and_return(%Q|mansession_id="#{manses_id}"; Version=1; Max-Age=60|)
12
+ http
13
+ end
14
+ subject{Session.new uri: uri_http, ami_user: 'admin', ami_password: 'passw0rd'}
15
+ before(:each, :logginok => true) do
16
+ Net::HTTP.stub(:new).and_return(double('Net::HTTP', :request => http, :use_ssl= => true))
17
+ end
18
+
19
+ describe "#new" do
20
+ it "set AJAM uri" do
21
+ URI.stub(:parse => URI(uri_http))
22
+ URI.should_receive(:parse).with(uri_http)
23
+ Session.new uri: uri_http
24
+ end
25
+
26
+ it "raises error if URI missing " do
27
+ expect{
28
+ Session.new
29
+ }.to raise_error(URI::InvalidURIError)
30
+ end
31
+
32
+ it "raises error if scheme is not http or https" do
33
+ uri = "ftp://127.0.0.1/path"
34
+ expect{
35
+ Session.new uri: uri
36
+ }.to raise_error(InvalidURI)
37
+ end
38
+ end
39
+
40
+ describe "#login", :logginok => true do
41
+ it "raises InvalidAMILogin if empty AMI username" do
42
+ subject.ami_user = nil
43
+ expect{
44
+ subject.login
45
+ }.to raise_error(InvalidAMILogin)
46
+ end
47
+
48
+ it "raises InvalidAMILogin if empty AMI password" do
49
+ subject.ami_password = nil
50
+ expect{
51
+ subject.login
52
+ }.to raise_error(InvalidAMILogin)
53
+ end
54
+
55
+ it "should return Session class instance on success" , :mock_login => true do
56
+ response = subject.login
57
+ response.should be_kind_of Session
58
+ end
59
+
60
+ end
61
+
62
+ describe "#connected?", :logginok => true do
63
+ it "returns true when successfuly logged in", :mock_login => true do
64
+ subject.login
65
+ subject.should be_connected
66
+ end
67
+ end
68
+
69
+ describe "action methods" do
70
+ before(:each) {subject.stub(:connected?).and_return(true)}
71
+ it "when call action without parameters then expects send_action with action name" do
72
+ subject.should_receive(:send_action).once.with(:sippeers)
73
+ subject.action_sippeers
74
+ end
75
+
76
+ it "when call action with parameters then expects send_action with hash" do
77
+ params = Hash[family: 'user', key: 'extension', val: '5555']
78
+ subject.should_receive(:send_action).with(:dbput, params)
79
+ subject.action_dbput params
80
+ end
81
+
82
+ it "uses cookies when sending action", :logginok => true do
83
+ subject.login
84
+ Net::HTTP::Post.should_receive(:new).
85
+ with(anything(),hash_including("Cookie"=>%Q!mansession_id="#{manses_id}"!)).
86
+ and_return(double('Net::HTTPResponse', :set_form_data => true))
87
+ subject.action_corestatus
88
+ end
89
+ end
90
+
91
+ describe "#command" do
92
+ before(:each) {subject.stub(:connected?).and_return(true)}
93
+ it "sends command to action method" do
94
+ http = get_simple_httpok
95
+ http.stub(:body => cmd_body_sip_show_peers)
96
+ http.stub(:[]).and_return(%Q|mansession_id="#{manses_id}"; Version=1; Max-Age=60|)
97
+ http.stub(:set_form_data).with(anything())
98
+ http.stub(:use_ssl=)
99
+ http.stub(:request => http)
100
+ Net::HTTP.stub(:new => http)
101
+ Net::HTTP::Post.stub(:new).and_return(double('Net::HTTP::Post', :set_form_data => true))
102
+ res = subject.command 'sip show peers'
103
+ res.data.should match(/88888\s+\(Unspecified\)/)
104
+ end
105
+ end
106
+
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,83 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+ Coveralls.wear!
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ SimpleCov::Formatter::HTMLFormatter,
6
+ Coveralls::SimpleCov::Formatter
7
+ ]
8
+ SimpleCov.start do
9
+ add_filter "spec/"
10
+ end
11
+ require 'asterisk/ajam'
12
+
13
+ # this fixture create valid HTTPOK response
14
+ def get_simple_httpok
15
+ Net::HTTPOK.new 1.1, 200, 'OK'
16
+ end
17
+
18
+ def get_http_unauth
19
+ Net::HTTPUnauthorized.new 1.1, 401, 'Unauthorized request'
20
+ end
21
+
22
+ def get_body_success_login
23
+ %q{<ajax-response>
24
+ <response type='object' id='unknown'><generic response='Success' message='Authentication accepted' /></response>
25
+ </ajax-response>
26
+ }
27
+ end
28
+
29
+ def get_body_failed_login
30
+ %q{
31
+ <ajax-response>
32
+ <response type='object' id='unknown'><generic response='Error' message='Authentication failed' /></response>
33
+ </ajax-response>
34
+ }
35
+ end
36
+
37
+ def get_body_sippeers
38
+ %q{<ajax-response>
39
+ <response type='object' id='unknown'><generic response='Success' actionid='321321321' eventlist='start' message='Peer status list will follow' /></response>
40
+ <response type='object' id='unknown'><generic event='PeerEntry' actionid='321321321' channeltype='SIP' objectname='5555' chanobjecttype='peer' ipaddress='-none-' ipport='0' dynamic='yes' forcerport='no' videosupport='no' textsupport='no' acl='yes' status='UNKNOWN' realtimedevice='no' /></response>
41
+ <response type='object' id='unknown'><generic event='PeerEntry' actionid='321321321' channeltype='SIP' objectname='8734' chanobjecttype='peer' ipaddress='-none-' ipport='0' dynamic='yes' forcerport='yes' videosupport='no' textsupport='no' acl='yes' status='UNKNOWN' realtimedevice='no' /></response>
42
+ <response type='object' id='unknown'><generic event='PeerEntry' actionid='321321321' channeltype='SIP' objectname='8801' chanobjecttype='peer' ipaddress='-none-' ipport='0' dynamic='yes' forcerport='yes' videosupport='no' textsupport='no' acl='yes' status='UNKNOWN' realtimedevice='no' /></response>
43
+ <response type='object' id='unknown'><generic event='PeerEntry' actionid='321321321' channeltype='SIP' objectname='8803' chanobjecttype='peer' ipaddress='-none-' ipport='0' dynamic='yes' forcerport='yes' videosupport='no' textsupport='no' acl='yes' status='UNKNOWN' realtimedevice='no' /></response>
44
+ <response type='object' id='unknown'><generic event='PeerEntry' actionid='321321321' channeltype='SIP' objectname='88888' chanobjecttype='peer' ipaddress='-none-' ipport='0' dynamic='yes' forcerport='yes' videosupport='no' textsupport='no' acl='yes' status='UNKNOWN' realtimedevice='no' /></response>
45
+ <response type='object' id='unknown'><generic event='PeerEntry' actionid='321321321' channeltype='SIP' objectname='8901' chanobjecttype='peer' ipaddress='-none-' ipport='0' dynamic='yes' forcerport='yes' videosupport='no' textsupport='no' acl='yes' status='UNKNOWN' realtimedevice='no' /></response>
46
+ <response type='object' id='unknown'><generic event='PeerEntry' actionid='321321321' channeltype='SIP' objectname='8902' chanobjecttype='peer' ipaddress='-none-' ipport='0' dynamic='yes' forcerport='yes' videosupport='no' textsupport='no' acl='yes' status='UNKNOWN' realtimedevice='no' /></response>
47
+ <response type='object' id='unknown'><generic event='PeerEntry' actionid='321321321' channeltype='SIP' objectname='8903' chanobjecttype='peer' ipaddress='-none-' ipport='0' dynamic='yes' forcerport='yes' videosupport='no' textsupport='no' acl='yes' status='UNKNOWN' realtimedevice='no' /></response>
48
+ <response type='object' id='unknown'><generic event='PeerlistComplete' eventlist='Complete' listitems='8' actionid='321321321' /></response>
49
+ </ajax-response>
50
+ }
51
+ end
52
+
53
+ def get_body_corestatus
54
+ %q{<ajax-response>
55
+ <response type='object' id='unknown'><generic response='Success' corestartupdate='2013-05-25' corestartuptime='17:13:00' corereloaddate='2013-05-25' corereloadtime='17:13:00' corecurrentcalls='0' /></response>
56
+ </ajax-response>
57
+ }
58
+ end
59
+
60
+ def cmd_body_dialplan_reload
61
+ %q{<ajax-response>
62
+ <response type='object' id='unknown'><generic response='Follows' privilege='Command' opaque_data='Dialplan reloaded.
63
+ --END COMMAND--' /></response>
64
+ </ajax-response>
65
+ }
66
+ end
67
+
68
+ def cmd_body_sip_show_peers
69
+ %q{<ajax-response>
70
+ <response type='object' id='unknown'><generic response='Follows' privilege='Command' opaque_data='Name/username Host Dyn Forcerport ACL Port Status
71
+ 5555 (Unspecified) D A 0 UNKNOWN
72
+ 8734 (Unspecified) D N A 0 UNKNOWN
73
+ 8801 (Unspecified) D N A 0 UNKNOWN
74
+ 8803 (Unspecified) D N A 0 UNKNOWN
75
+ 88888 (Unspecified) D N A 0 UNKNOWN
76
+ 8901 (Unspecified) D N A 0 UNKNOWN
77
+ 8902 (Unspecified) D N A 0 UNKNOWN
78
+ 8903 (Unspecified) D N A 0 UNKNOWN
79
+ 8 sip peers [Monitored: 0 online, 8 offline Unmonitored: 0 online, 0 offline]
80
+ --END COMMAND--' /></response>
81
+ </ajax-response>
82
+ }
83
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: asterisk-ajam
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Stas Kobzar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rdoc
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: libxml-ruby
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Ruby module for interacting with Asterisk management interface (AMI)
84
+ via HTTP
85
+ email:
86
+ - stas@modulis.ca
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - .travis.yml
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - asterisk-ajam.gemspec
98
+ - example/ajam.rb
99
+ - lib/asterisk/ajam.rb
100
+ - lib/asterisk/ajam/response.rb
101
+ - lib/asterisk/ajam/session.rb
102
+ - lib/asterisk/ajam/version.rb
103
+ - spec/asterisk/ajam/response_spec.rb
104
+ - spec/asterisk/ajam/session_spec.rb
105
+ - spec/spec_helper.rb
106
+ homepage: https://github.com/staskobzar/asterisk-ajam
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.0.3
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: AMI via HTTP
130
+ test_files:
131
+ - spec/asterisk/ajam/response_spec.rb
132
+ - spec/asterisk/ajam/session_spec.rb
133
+ - spec/spec_helper.rb