instedd-pigeon 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,70 @@
1
+ module Pigeon
2
+ module Utils
3
+
4
+ def to_query(hash)
5
+ hash.map do |key, value|
6
+ "#{key.to_s}=#{CGI.escape(value.to_s)}"
7
+ end.join('&')
8
+ end
9
+
10
+ def handle_channel_error(error)
11
+ if error.is_a? RestClient::BadRequest
12
+ response = JSON.parse error.response.body
13
+ raise self.class.error_class.new response['summary'], Hash[response['properties'].map {|e| [e.keys[0], e.values[0]]}]
14
+ else
15
+ raise self.class.error_class.new error.message
16
+ end
17
+ end
18
+
19
+ def with_indifferent_access(hash)
20
+ if hash.respond_to? :with_indifferent_access
21
+ hash.with_indifferent_access
22
+ else
23
+ hash
24
+ end
25
+ end
26
+
27
+ def get(path)
28
+ resource = RestClient::Resource.new @url, @options
29
+ resource = resource[path].get
30
+ yield resource, nil
31
+ rescue => ex
32
+ yield nil, ex
33
+ end
34
+
35
+ def get_json(path)
36
+ get(path) do |response, error|
37
+ raise self.class.error_class.new error.message if error
38
+
39
+ elem = JSON.parse response.body
40
+ elem.map! { |x| with_indifferent_access x } if elem.is_a? Array
41
+ elem
42
+ end
43
+ end
44
+
45
+ def post(path, data)
46
+ resource = RestClient::Resource.new @url, @options
47
+ resource = resource[path].post(data)
48
+ yield resource, nil
49
+ rescue => ex
50
+ yield nil, ex
51
+ end
52
+
53
+ def put(path, data)
54
+ resource = RestClient::Resource.new @url, @options
55
+ resource = resource[path].put(data)
56
+ yield resource, nil
57
+ rescue => ex
58
+ yield nil, ex
59
+ end
60
+
61
+ def delete(path)
62
+ resource = RestClient::Resource.new @url, @options
63
+ resource = resource[path].delete
64
+ yield resource, nil
65
+ rescue => ex
66
+ yield nil, ex
67
+ end
68
+
69
+ end
70
+ end
@@ -1,10 +1,154 @@
1
- require 'verboice'
1
+ # Provides access to the Verboice Public API.
2
+ # Taken from verboice-api-ruby gem version 0.7.0
3
+ # See http://bitbucket.org/instedd/verboice-api-ruby
4
+ #
2
5
 
3
- class Verboice
4
- def self.from_config
5
- config = Pigeon.config
6
+ require 'net/http'
7
+ require 'json'
8
+ require 'rest_client'
9
+ require 'cgi'
10
+ require 'time'
11
+ require 'pigeon/errors'
12
+ require 'pigeon/utils'
13
+
14
+ module Pigeon
15
+ class Verboice
16
+ include Pigeon::Utils
17
+
18
+ def self.error_class
19
+ Pigeon::VerboiceException
20
+ end
21
+
22
+ def self.from_config
23
+ config = Pigeon.config
24
+
25
+ self.new config.verboice_host, config.verboice_account, config.verboice_password
26
+ end
27
+
28
+ # Creates an account-authenticated Verboice api access.
29
+ def initialize(url, account, password, default_channel = nil)
30
+ @url = url
31
+ @account = account
32
+ @password = password
33
+ @default_channel = default_channel
34
+ @options = {
35
+ :user => account,
36
+ :password => password,
37
+ :headers => {:content_type => 'application/json'},
38
+ }
39
+ end
40
+
41
+ def call address, options = {}
42
+ options = options.dup
43
+
44
+ args = {}
45
+ if channel = options.delete(:channel)
46
+ args[:channel] = channel
47
+ else
48
+ args[:channel] = @default_channel
49
+ end
50
+
51
+ args[:address] = address
52
+
53
+ if not_before = options.delete(:not_before)
54
+ args[:not_before] = not_before.iso8601
55
+ end
56
+
57
+ flow = options.delete(:flow)
58
+ callback_url = options.delete(:callback_url)
59
+
60
+ args.merge!(options)
61
+
62
+ if flow
63
+ post "/api/call?#{to_query args}", flow do |response, error|
64
+ raise ::Pigeon::VerboiceException.new error.message if error
65
+ JSON.parse response.body
66
+ end
67
+ else
68
+ args[:callback_url] = callback_url if callback_url
69
+ get_json "/api/call?#{to_query args}"
70
+ end
71
+ end
72
+
73
+ def call_state id
74
+ get_json "/api/calls/#{id}/state"
75
+ end
76
+
77
+ def call_redirect id
78
+ get_json "/api/calls/#{id}/redirect"
79
+ end
80
+
81
+ def channel(name)
82
+ get("/api/channels/#{name}.json") do |response, error|
83
+ handle_channel_error error if error
84
+
85
+ channel = JSON.parse response.body
86
+ with_indifferent_access channel
87
+ end
88
+ end
89
+
90
+ def create_channel(channel)
91
+ post "/api/channels.json", channel.to_json do |response, error|
92
+ handle_channel_error error if error
93
+
94
+ channel = JSON.parse response.body
95
+ with_indifferent_access channel
96
+ end
97
+ end
98
+
99
+ def update_channel(channel, name = channel['name'])
100
+ put "/api/channels/#{name}.json", channel.to_json do |response, error|
101
+ handle_channel_error error if error
102
+
103
+ channel = JSON.parse response.body
104
+ with_indifferent_access channel
105
+ end
106
+ end
107
+
108
+ # Deletes a channel given its name.
109
+ #
110
+ # Raises Verboice::Exception if something goes wrong.
111
+ def delete_channel(name)
112
+ delete "/api/channels/#{name}" do |response, error|
113
+ raise ::Pigeon::VerboiceException.new error.message if error
114
+
115
+ response
116
+ end
117
+ end
118
+
119
+ def list_channels()
120
+ get_json "/api/channels.json"
121
+ end
122
+
123
+ def schedules(project_id)
124
+ get_json "/api/projects/#{project_id}/schedules.json"
125
+ end
126
+
127
+ def schedule(project_id, name)
128
+ get_json "/api/projects/#{project_id}/schedules/#{name}.json"
129
+ end
130
+
131
+ def create_schedule(project_id, schedule)
132
+ post "/api/projects/#{project_id}/schedules", schedule.to_json do |response, error|
133
+ raise ::Pigeon::VerboiceException.new error.message if error
134
+ response
135
+ end
136
+ end
137
+
138
+ def update_schedule(project_id, name, schedule)
139
+ put "/api/projects/#{project_id}/schedules/#{name}", schedule.to_json do |response, error|
140
+ raise ::Pigeon::VerboiceException.new error.message if error
141
+ response
142
+ end
143
+ end
144
+
145
+ def delete_schedule(project_id, name)
146
+ delete "/api/projects/#{project_id}/schedules/#{name}" do |response, error|
147
+ raise ::Pigeon::VerboiceException.new error.message if error
148
+ response
149
+ end
150
+ end
6
151
 
7
- Verboice.new config.verboice_host, config.verboice_account, config.verboice_password
8
152
  end
9
153
  end
10
154
 
@@ -1,3 +1,3 @@
1
1
  module Pigeon
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
data/pigeon.gemspec CHANGED
@@ -12,10 +12,10 @@ Gem::Specification.new do |gem|
12
12
  gem.summary = %q{This gem handles creating, updating and destroying channels in Nuntium and Verboice for your Rails application.}
13
13
  gem.homepage = "https://bitbucket.org/instedd/pigeon"
14
14
 
15
- gem.add_dependency 'rails', '~> 3.2.12'
16
- gem.add_dependency 'nuntium_api', '~> 0.19'
17
- gem.add_dependency 'verboice', '0.7.0'
15
+ gem.add_dependency 'rails', '~> 3.2'
18
16
  gem.add_dependency 'twitter_oauth'
17
+ gem.add_dependency 'rest-client'
18
+ gem.add_dependency 'json'
19
19
 
20
20
  gem.files = `git ls-files`.split($/)
21
21
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -0,0 +1,208 @@
1
+ require 'spec_helper'
2
+
3
+ module Pigeon
4
+ describe Nuntium do
5
+ let(:url) { "http://example.com" }
6
+ let(:options) { {:user => "account/application", :password => "password", :headers => {:content_type => 'application/json'}} }
7
+ let(:api) { Nuntium.new url, "account", "application", "password" }
8
+
9
+ it "gets countries" do
10
+ should_receive_http_get '/api/countries.json', %([{"name": "Argentina", "iso2": "ar"}])
11
+
12
+ api.countries.should eq(['name' => 'Argentina', 'iso2' => 'ar'])
13
+ end
14
+
15
+ it "gets country" do
16
+ should_receive_http_get '/api/countries/ar.json', %({"name": "Argentina", "iso2": "ar"})
17
+
18
+ api.country('ar').should eq({'name' => 'Argentina', 'iso2' => 'ar'})
19
+ end
20
+
21
+ it "gets carriers" do
22
+ should_receive_http_get '/api/carriers.json', %([{"name": "Argentina", "iso2": "ar"}])
23
+
24
+ api.carriers.should eq(['name' => 'Argentina', 'iso2' => 'ar'])
25
+ end
26
+
27
+ it "gets carriers for a country" do
28
+ should_receive_http_get '/api/carriers.json?country_id=ar', %([{"name": "Argentina", "iso2": "ar"}])
29
+
30
+ api.carriers('ar').should eq(['name' => 'Argentina', 'iso2' => 'ar'])
31
+ end
32
+
33
+ it "gets carrier" do
34
+ should_receive_http_get '/api/carriers/ar.json', %({"name": "Argentina", "iso2": "ar"})
35
+
36
+ api.carrier('ar').should eq({'name' => 'Argentina', 'iso2' => 'ar'})
37
+ end
38
+
39
+ it "gets channels" do
40
+ should_receive_http_get '/api/channels.json', %([{"name": "Argentina", "configuration": [{"name": "foo", "value": "bar"}]}])
41
+
42
+ api.channels.should eq([{'name' => 'Argentina', 'configuration' => {'foo' => 'bar'}}])
43
+ end
44
+
45
+ it "gets channel" do
46
+ should_receive_http_get '/api/channels/Argentina.json', %({"name": "Argentina", "configuration": [{"name": "foo", "value": "bar"}]})
47
+
48
+ api.channel('Argentina').should eq({'name' => 'Argentina', 'configuration' => {'foo' => 'bar'}})
49
+ end
50
+
51
+ it "creates channel" do
52
+ channel_json = %({"name":"Argentina","configuration":[{"name":"foo","value":"bar"}]})
53
+ should_receive_http_post '/api/channels.json', channel_json, channel_json
54
+
55
+ channel = {'name' => 'Argentina', 'configuration' => {'foo' => 'bar'}}
56
+
57
+ api.create_channel(channel).should eq(channel)
58
+ end
59
+
60
+ it "updates channel", :focus => true do
61
+ channel_json = %({"name":"Argentina","configuration":[{"name":"foo","value":"bar"}]})
62
+ should_receive_http_put '/api/channels/Argentina.json', channel_json, channel_json
63
+
64
+ channel = {'name' => 'Argentina', 'configuration' => {'foo' => 'bar'}}
65
+
66
+ api.update_channel(channel).should eq(channel)
67
+ end
68
+
69
+ it "deletes channel", :focus => true do
70
+ should_receive_http_delete '/api/channels/Argentina'
71
+
72
+ api.delete_channel('Argentina')
73
+ end
74
+
75
+ it "gets candidate channels for ao", :focus => true do
76
+ should_receive_http_get "/api/candidate/channels.json?from=#{CGI.escape 'sms://1234'}&body=Hello", %([{"name":"Argentina","configuration":[{"value":"bar","name":"foo"}]}])
77
+
78
+ channels = api.candidate_channels_for_ao :from => 'sms://1234', :body => 'Hello'
79
+ channels.should eq([{'name' => 'Argentina', 'configuration' => {'foo' => 'bar'}}])
80
+ end
81
+
82
+ it "sends single ao" do
83
+ should_receive_http_get_with_headers "/account/application/send_ao?from=#{CGI.escape 'sms://1234'}&body=Hello", :x_nuntium_id => '1', :x_nuntium_guid => '2', :x_nuntium_token => '3'
84
+ response = api.send_ao :from => 'sms://1234', :body => 'Hello'
85
+ response.should eq("id" => '1', "guid" => '2', "token" => '3')
86
+ end
87
+
88
+ it "sends many aos" do
89
+ post_body = %([{"from":"sms://1234","body":"Hello"}])
90
+ should_receive_http_post_with_headers "/account/application/send_ao.json", post_body, :x_nuntium_token => '3'
91
+ response = api.send_ao [{:from => 'sms://1234', :body => 'Hello'}]
92
+ response.should eq("token" => '3')
93
+ end
94
+
95
+ it "gets ao" do
96
+ should_receive_http_get '/account/application/get_ao.json?token=foo', %([{"name": "Argentina", "iso2": "ar"}])
97
+
98
+ api.get_ao('foo').should eq([{'name' => 'Argentina', 'iso2' => 'ar'}])
99
+ end
100
+
101
+ it "gets custom attributes" do
102
+ should_receive_http_get '/api/custom_attributes?address=foo', %([{"name": "Argentina", "iso2": "ar"}])
103
+
104
+ api.get_custom_attributes('foo').should eq([{'name' => 'Argentina', 'iso2' => 'ar'}])
105
+ end
106
+
107
+ it "sets custom attributes" do
108
+ should_receive_http_post '/api/custom_attributes?address=foo', %({"application":"bar"}), ''
109
+
110
+ api.set_custom_attributes('foo', :application => :bar)
111
+ end
112
+
113
+ it "creates twitter friendship" do
114
+ should_receive_http_get '/api/channels/twit/twitter/friendships/create?user=foo&follow=true'
115
+
116
+ api.twitter_friendship_create 'twit', 'foo', true
117
+ end
118
+
119
+ it "authorizes twitter channel" do
120
+ should_receive_http_get '/api/channels/twit/twitter/authorize?callback=foo', "http://bar"
121
+
122
+ url = api.twitter_authorize 'twit', 'foo'
123
+ url.should eq("http://bar")
124
+ end
125
+
126
+ it "adds xmpp contact" do
127
+ should_receive_http_get "/api/channels/chan/xmpp/add_contact?jid=#{CGI.escape 'foo@bar.com'}"
128
+
129
+ api.xmpp_add_contact 'chan', 'foo@bar.com'
130
+ end
131
+
132
+ def should_receive_http_get(path, body = nil)
133
+ resource = mock 'resource'
134
+ RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
135
+
136
+ resource2 = mock 'resource2'
137
+ resource.should_receive(:[]).with(path).and_return(resource2)
138
+
139
+ resource3 = mock 'resource3'
140
+ resource2.should_receive(:get).and_return(resource3)
141
+
142
+ resource3.should_receive(:body).and_return(body) if body
143
+ end
144
+
145
+ def should_receive_http_get_with_headers(path, headers)
146
+ resource = mock 'resource'
147
+ RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
148
+
149
+ resource2 = mock 'resource2'
150
+ resource.should_receive(:[]).with(path).and_return(resource2)
151
+
152
+ resource3 = mock 'resource3'
153
+ resource2.should_receive(:get).and_return(resource3)
154
+
155
+ resource3.stub(:headers) { headers }
156
+ end
157
+
158
+ def should_receive_http_post(path, data, body)
159
+ resource = mock 'resource'
160
+ RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
161
+
162
+ resource2 = mock 'resource2'
163
+ resource.should_receive(:[]).with(path).and_return(resource2)
164
+
165
+ resource3 = mock 'resource3'
166
+ resource2.should_receive(:post).with(data).and_return(resource3)
167
+
168
+ resource3.stub(:body) { body }
169
+ end
170
+
171
+ def should_receive_http_post_with_headers(path, data, headers)
172
+ resource = mock 'resource'
173
+ RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
174
+
175
+ resource2 = mock 'resource2'
176
+ resource.should_receive(:[]).with(path).and_return(resource2)
177
+
178
+ resource3 = mock 'resource3'
179
+ resource2.should_receive(:post).with(data).and_return(resource3)
180
+
181
+ resource3.stub(:headers) { headers }
182
+ end
183
+
184
+ def should_receive_http_put(path, data, body)
185
+ resource = mock 'resource'
186
+ RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
187
+
188
+ resource2 = mock 'resource2'
189
+ resource.should_receive(:[]).with(path).and_return(resource2)
190
+
191
+ resource3 = mock 'resource3'
192
+ resource2.should_receive(:put).with(data).and_return(resource3)
193
+
194
+ resource3.should_receive(:body).and_return(body)
195
+ end
196
+
197
+ def should_receive_http_delete(path)
198
+ resource = mock 'resource'
199
+ RestClient::Resource.should_receive(:new).with(url, options).and_return(resource)
200
+
201
+ resource2 = mock 'resource2'
202
+ resource.should_receive(:[]).with(path).and_return(resource2)
203
+
204
+ resource3 = mock 'resource3'
205
+ resource2.should_receive(:delete)
206
+ end
207
+ end
208
+ end