instedd-pigeon 0.1.3 → 0.2.0

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.
@@ -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