nexmo 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/README.md +71 -14
  2. data/lib/nexmo.rb +23 -15
  3. data/nexmo.gemspec +4 -3
  4. data/spec/nexmo_spec.rb +81 -41
  5. metadata +21 -6
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
- A simple wrapper for the [Nexmo](http://nexmo.com/) API
2
- =======================================================
1
+ A Ruby wrapper for the [Nexmo](http://nexmo.com/) API
2
+ =====================================================
3
3
 
4
4
 
5
5
  Installation
@@ -8,32 +8,89 @@ Installation
8
8
  $ gem install nexmo
9
9
 
10
10
 
11
- Quick Start
12
- -----------
11
+ Quick start (sending a message)
12
+ -------------------------------
13
13
 
14
- Use the `send_message!` for a "fire and forget" approach to sending a message:
14
+ First you need to load up the gem and construct a Nexmo::Client object
15
+ with your API credentials, like this:
15
16
 
16
17
  ```ruby
17
18
  require 'nexmo'
18
19
 
19
20
  nexmo = Nexmo::Client.new('...API KEY...', '...API SECRET...')
21
+ ```
22
+
23
+ Then you have a choice. For a "fire and forget" approach to sending a message,
24
+ use the `send_message!` method, like this:
20
25
 
26
+ ```ruby
21
27
  nexmo.send_message!({:to => '...NUMBER...', :from => 'Ruby', :text => 'Hello world'})
22
28
  ```
23
29
 
24
30
  This method call returns the message id if the message was sent successfully,
25
- or raises an exception if there was an error. For more robust error handling
26
- use the `send_message` method instead. This returns the HTTP response wrapped
27
- in a `Nexmo::Response` object.
31
+ or raises an exception if there was an error. If you need more robust error
32
+ handling use the `send_message` method instead, like this:
33
+
34
+ ```ruby
35
+ response = nexmo.send_message({:to => '...NUMBER...', :from => 'Ruby', :text => 'Hello world'})
36
+
37
+ if response.ok?
38
+ # do something with response.object
39
+ else
40
+ # handle the error
41
+ end
42
+ ```
43
+
44
+ This method call returns a `Nexmo::Response` object, which wraps the underlying
45
+ Net::HTTP response and adds a few convenience methods. Additional methods are
46
+ also provided for managing your account and messages.
47
+
48
+
49
+ Authenticating with OAuth (beta)
50
+ --------------------------------
51
+
52
+ If you are building an app that needs access to Nexmo resources on behalf of
53
+ other accounts then you will want to use OAuth to authenticate your requests.
54
+ Authorizing access and fetching access tokens can be achieved using the
55
+ [oauth gem](http://rubygems.org/gems/oauth) directly. You can then use an
56
+ OAuth::AccessToken object together with a Nexmo::Client object to make calls
57
+ to the API that are authenticated using OAuth. For example:
58
+
59
+ ```ruby
60
+ require 'nexmo'
61
+ require 'oauth'
62
+
63
+ nexmo = Nexmo::Client.new
64
+ nexmo.oauth_access_token = OAuth::AccessToken.new(consumer, token, secret)
65
+
66
+ response = nexmo.get_balance
67
+
68
+ if response.ok?
69
+ # do something with response.object
70
+ else
71
+ # handle the error
72
+ end
73
+ ```
74
+
75
+ The OAuth::Consumer object should be pointed at `rest.nexmo.com` with the `:scheme` option set to `:header`, like this:
76
+
77
+ ```ruby
78
+ OAuth::Consumer.new(consumer_key, consumer_secret, {
79
+ :site => 'https://rest.nexmo.com',
80
+ :scheme => :header
81
+ })
82
+ ```
83
+
84
+ Using the `:body` or `:query_string` authorization mechanisms is not supported.
28
85
 
29
86
 
30
- JSON Implementation
31
- -------------------
87
+ Switching JSON implementations
88
+ ------------------------------
32
89
 
33
- The "json" library is used by default. This is available in the Ruby 1.9
34
- standard library, and as a gem for Ruby 1.8. You can specify an alternate
35
- implementation that you wish to use explicitly when constructing a client
36
- object. For example, to use [multi_json](https://rubygems.org/gems/multi_json):
90
+ By default the "json" library is used to encode request bodies and decode response
91
+ bodies. This is available in the Ruby 1.9 standard library, and as a gem for Ruby 1.8.
92
+ You can specify an alternate implementation that you wish to use explicitly when
93
+ constructing a client object. For example, to use [multi_json](https://rubygems.org/gems/multi_json):
37
94
 
38
95
  ```ruby
39
96
  require 'nexmo'
@@ -5,7 +5,7 @@ require 'cgi'
5
5
 
6
6
  module Nexmo
7
7
  class Client
8
- def initialize(key, secret, options = {})
8
+ def initialize(key = ENV['NEXMO_API_KEY'], secret = ENV['NEXMO_API_SECRET'], options = {})
9
9
  @key, @secret = key, secret
10
10
 
11
11
  @json = options.fetch(:json) { JSON }
@@ -15,7 +15,7 @@ module Nexmo
15
15
  @http.use_ssl = true
16
16
  end
17
17
 
18
- attr_accessor :key, :secret, :http
18
+ attr_accessor :key, :secret, :http, :oauth_access_token
19
19
 
20
20
  def send_message(params)
21
21
  post('/sms/json', params)
@@ -40,49 +40,57 @@ module Nexmo
40
40
  end
41
41
 
42
42
  def get_balance
43
- get("/account/get-balance/#{key}/#{secret}")
43
+ get('/account/get-balance')
44
44
  end
45
45
 
46
46
  def get_country_pricing(country_code)
47
- get("/account/get-pricing/outbound/#{key}/#{secret}/#{country_code}")
47
+ get('/account/get-pricing/outbound', {:country => country_code})
48
48
  end
49
49
 
50
50
  def get_prefix_pricing(prefix)
51
- get("/account/get-prefix-pricing/outbound/#{key}/#{secret}/#{prefix}")
51
+ get('/account/get-prefix-pricing/outbound', {:prefix => prefix})
52
52
  end
53
53
 
54
54
  def get_account_numbers(params)
55
- get("/account/numbers/#{key}/#{secret}", params)
55
+ get('/account/numbers', params)
56
56
  end
57
57
 
58
58
  def number_search(country_code, params = {})
59
- get("/number/search/#{key}/#{secret}/#{country_code}", params)
59
+ get('/number/search', {:country => country_code}.merge(params))
60
60
  end
61
61
 
62
62
  def get_message(id)
63
- get("/search/message/#{key}/#{secret}/#{id}")
63
+ get('/search/message', {:id => id})
64
64
  end
65
65
 
66
66
  def get_message_rejections(params)
67
- get("/search/rejections/#{key}/#{secret}", params)
67
+ get('/search/rejections', params)
68
68
  end
69
69
 
70
70
  def search_messages(params)
71
- get("/search/messages/#{key}/#{secret}", Hash === params ? params : {:ids => Array(params)})
71
+ get('/search/messages', Hash === params ? params : {:ids => Array(params)})
72
72
  end
73
73
 
74
74
  private
75
75
 
76
76
  def get(path, params = {})
77
- Response.new(@http.get(request_uri(path, params)), :json => @json)
77
+ http_response = if oauth_access_token
78
+ oauth_access_token.get(request_uri(path, params))
79
+ else
80
+ @http.get(request_uri(path, params.merge(:api_key => @key, :api_secret => @secret)))
81
+ end
82
+
83
+ Response.new(http_response, :json => @json)
78
84
  end
79
85
 
80
86
  def post(path, params)
81
- Response.new(@http.post(path, encode(params), {'Content-Type' => 'application/json'}), :json => @json)
82
- end
87
+ http_response = if oauth_access_token
88
+ oauth_access_token.post(path, @json.dump(params), {'Content-Type' => 'application/json'})
89
+ else
90
+ @http.post(path, @json.dump(params.merge(:api_key => @key, :api_secret => @secret)), {'Content-Type' => 'application/json'})
91
+ end
83
92
 
84
- def encode(params)
85
- JSON.dump(params.merge(:username => @key, :password => @secret))
93
+ Response.new(http_response, :json => @json)
86
94
  end
87
95
 
88
96
  def request_uri(path, hash = {})
@@ -1,16 +1,17 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'nexmo'
3
- s.version = '1.0.0'
3
+ s.version = '1.1.0'
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.authors = ['Tim Craft']
6
6
  s.email = ['mail@timcraft.com']
7
7
  s.homepage = 'http://github.com/timcraft/nexmo'
8
- s.description = 'A simple wrapper for the Nexmo API'
8
+ s.description = 'A Ruby wrapper for the Nexmo API'
9
9
  s.summary = 'See description'
10
10
  s.files = Dir.glob('{lib,spec}/**/*') + %w(README.md nexmo.gemspec)
11
11
  s.add_development_dependency('rake', '>= 0.9.3')
12
- s.add_development_dependency('mocha', '~> 0.10.3')
12
+ s.add_development_dependency('mocha', '~> 0.13.2')
13
13
  s.add_development_dependency('multi_json', '~> 1.3.6')
14
+ s.add_development_dependency('oauth', '~> 0.4.7')
14
15
  s.require_path = 'lib'
15
16
 
16
17
  if RUBY_VERSION == '1.8.7'
@@ -1,53 +1,51 @@
1
1
  require 'minitest/autorun'
2
- require 'mocha'
2
+ require 'mocha/setup'
3
3
  require 'multi_json'
4
4
  require 'nexmo'
5
+ require 'oauth'
5
6
 
6
7
  describe 'Nexmo::Client' do
7
8
  before do
9
+ @json_encoded_body = regexp_matches(/\{".+?":".+?"(,".+?":".+?")+\}/)
10
+
11
+ @http_header_hash = has_entry('Content-Type', 'application/json')
12
+
13
+ @example_message_hash = {:from => 'ruby', :to => 'number', :text => 'Hey!'}
14
+
8
15
  @client = Nexmo::Client.new('key', 'secret')
9
16
  end
10
17
 
18
+ def expects_http_get(request_uri)
19
+ @client.http.expects(:get).with(has_equivalent_query_string(request_uri)).returns(stub)
20
+ end
21
+
11
22
  describe 'http method' do
12
23
  it 'returns a net http object that uses ssl' do
13
24
  @client.http.must_be_instance_of(Net::HTTP)
25
+
14
26
  @client.http.use_ssl?.must_equal(true)
15
27
  end
16
28
  end
17
29
 
18
30
  describe 'send_message method' do
19
31
  it 'posts to the sms json resource and returns a response object' do
20
- data = regexp_matches(/\{".+?":".+?"(,".+?":".+?")+\}/)
32
+ @client.http.expects(:post).with('/sms/json', @json_encoded_body, @http_header_hash).returns(stub)
21
33
 
22
- headers = has_entry('Content-Type', 'application/json')
23
-
24
- params = {:from => 'ruby', :to => 'number', :text => 'Hey!'}
25
-
26
- @client.http.expects(:post).with('/sms/json', data, headers).returns(stub)
27
-
28
- @client.send_message(params).must_be_instance_of(Nexmo::Response)
34
+ @client.send_message(@example_message_hash).must_be_instance_of(Nexmo::Response)
29
35
  end
30
36
  end
31
37
 
32
38
  describe 'send_message bang method' do
33
39
  before do
34
- @params = {:from => 'ruby', :to => 'number', :text => 'Hey!'}
35
-
36
- @http_response = mock()
37
- @http_response.stubs(:[]).with('Content-Type').returns('application/json;charset=utf-8')
38
- @http_response.stubs(:code).returns('200')
40
+ @http_response = stub(:code => '200', :[] => 'application/json;charset=utf-8')
39
41
  end
40
42
 
41
43
  it 'posts to the sms json resource and returns the message id' do
42
- data = regexp_matches(/\{".+?":".+?"(,".+?":".+?")+\}/)
43
-
44
- headers = has_entry('Content-Type', 'application/json')
45
-
46
44
  @http_response.stubs(:body).returns('{"messages":[{"status":0,"message-id":"id"}]}')
47
45
 
48
- @client.http.expects(:post).with('/sms/json', data, headers).returns(@http_response)
46
+ @client.http.expects(:post).with('/sms/json', @json_encoded_body, @http_header_hash).returns(@http_response)
49
47
 
50
- @client.send_message!(@params).must_equal('id')
48
+ @client.send_message!(@example_message_hash).must_equal('id')
51
49
  end
52
50
 
53
51
  it 'raises an exception if the response code is not expected' do
@@ -55,7 +53,7 @@ describe 'Nexmo::Client' do
55
53
 
56
54
  @client.http.stubs(:post).returns(@http_response)
57
55
 
58
- proc { @client.send_message!(@params) }.must_raise(Nexmo::Error)
56
+ proc { @client.send_message!(@example_message_hash) }.must_raise(Nexmo::Error)
59
57
  end
60
58
 
61
59
  it 'raises an exception if the response body contains an error' do
@@ -63,13 +61,13 @@ describe 'Nexmo::Client' do
63
61
 
64
62
  @client.http.stubs(:post).returns(@http_response)
65
63
 
66
- proc { @client.send_message!(@params) }.must_raise(Nexmo::Error)
64
+ proc { @client.send_message!(@example_message_hash) }.must_raise(Nexmo::Error)
67
65
  end
68
66
  end
69
67
 
70
68
  describe 'get_balance method' do
71
69
  it 'fetches the account balance resource and returns a response object' do
72
- @client.http.expects(:get).with('/account/get-balance/key/secret').returns(stub)
70
+ expects_http_get '/account/get-balance?api_key=key&api_secret=secret'
73
71
 
74
72
  @client.get_balance.must_be_instance_of(Nexmo::Response)
75
73
  end
@@ -77,7 +75,7 @@ describe 'Nexmo::Client' do
77
75
 
78
76
  describe 'get_country_pricing method' do
79
77
  it 'fetches the outbound pricing resource for the given country and returns a response object' do
80
- @client.http.expects(:get).with('/account/get-pricing/outbound/key/secret/CA').returns(stub)
78
+ expects_http_get '/account/get-pricing/outbound?api_key=key&api_secret=secret&country=CA'
81
79
 
82
80
  @client.get_country_pricing(:CA).must_be_instance_of(Nexmo::Response)
83
81
  end
@@ -85,7 +83,7 @@ describe 'Nexmo::Client' do
85
83
 
86
84
  describe 'get_prefix_pricing method' do
87
85
  it 'fetches the outbound pricing resource for the given prefix and returns a response object' do
88
- @client.http.expects(:get).with('/account/get-prefix-pricing/outbound/key/secret/44').returns(stub)
86
+ expects_http_get '/account/get-prefix-pricing/outbound?api_key=key&api_secret=secret&prefix=44'
89
87
 
90
88
  @client.get_prefix_pricing(44).must_be_instance_of(Nexmo::Response)
91
89
  end
@@ -93,7 +91,7 @@ describe 'Nexmo::Client' do
93
91
 
94
92
  describe 'get_account_numbers method' do
95
93
  it 'fetches the account numbers resource with the given parameters and returns a response object' do
96
- @client.http.expects(:get).with(has_equivalent_query_string('/account/numbers/key/secret?size=25&pattern=33')).returns(stub)
94
+ expects_http_get '/account/numbers?api_key=key&api_secret=secret&size=25&pattern=33'
97
95
 
98
96
  @client.get_account_numbers(:size => 25, :pattern => 33).must_be_instance_of(Nexmo::Response)
99
97
  end
@@ -101,7 +99,7 @@ describe 'Nexmo::Client' do
101
99
 
102
100
  describe 'number_search method' do
103
101
  it 'fetches the number search resource for the given country with the given parameters and returns a response object' do
104
- @client.http.expects(:get).with('/number/search/key/secret/CA?size=25').returns(stub)
102
+ expects_http_get '/number/search?api_key=key&api_secret=secret&country=CA&size=25'
105
103
 
106
104
  @client.number_search(:CA, :size => 25).must_be_instance_of(Nexmo::Response)
107
105
  end
@@ -109,7 +107,7 @@ describe 'Nexmo::Client' do
109
107
 
110
108
  describe 'get_message method' do
111
109
  it 'fetches the message search resource for the given message id and returns a response object' do
112
- @client.http.expects(:get).with('/search/message/key/secret/00A0B0C0').returns(stub)
110
+ expects_http_get '/search/message?api_key=key&api_secret=secret&id=00A0B0C0'
113
111
 
114
112
  @client.get_message('00A0B0C0').must_be_instance_of(Nexmo::Response)
115
113
  end
@@ -117,7 +115,7 @@ describe 'Nexmo::Client' do
117
115
 
118
116
  describe 'get_message_rejections method' do
119
117
  it 'fetches the message rejections resource with the given parameters and returns a response object' do
120
- @client.http.expects(:get).with('/search/rejections/key/secret?date=YYYY-MM-DD').returns(stub)
118
+ expects_http_get '/search/rejections?api_key=key&api_secret=secret&date=YYYY-MM-DD'
121
119
 
122
120
  @client.get_message_rejections(:date => 'YYYY-MM-DD').must_be_instance_of(Nexmo::Response)
123
121
  end
@@ -125,17 +123,59 @@ describe 'Nexmo::Client' do
125
123
 
126
124
  describe 'search_messages method' do
127
125
  it 'fetches the search messages resource with the given parameters and returns a response object' do
128
- @client.http.expects(:get).with(has_equivalent_query_string('/search/messages/key/secret?date=YYYY-MM-DD&to=1234567890')).returns(stub)
126
+ expects_http_get '/search/messages?api_key=key&api_secret=secret&date=YYYY-MM-DD&to=1234567890'
129
127
 
130
128
  @client.search_messages(:date => 'YYYY-MM-DD', :to => 1234567890).must_be_instance_of(Nexmo::Response)
131
129
  end
132
130
 
133
131
  it 'should encode a non hash argument as a list of ids' do
134
- @client.http.expects(:get).with(has_equivalent_query_string('/search/messages/key/secret?ids=id1&ids=id2')).returns(stub)
132
+ expects_http_get '/search/messages?api_key=key&api_secret=secret&ids=id1&ids=id2'
135
133
 
136
134
  @client.search_messages(%w(id1 id2))
137
135
  end
138
136
  end
137
+
138
+ describe 'when initialized with an oauth access token' do
139
+ before do
140
+ @oauth_consumer = OAuth::Consumer.new('key', 'secret', {:site => 'https://rest.nexmo.com', :scheme => :header})
141
+
142
+ @oauth_access_token = OAuth::AccessToken.new(@oauth_consumer, 'access_token', 'access_token_secret')
143
+
144
+ @client = Nexmo::Client.new
145
+
146
+ @client.oauth_access_token = @oauth_access_token
147
+
148
+ @client.http = mock()
149
+ end
150
+
151
+ it 'makes get requests through the access token and returns a response object' do
152
+ @oauth_access_token.expects(:get).with('/account/get-pricing/outbound?country=CA').returns(stub)
153
+
154
+ @client.get_country_pricing(:CA).must_be_instance_of(Nexmo::Response)
155
+ end
156
+
157
+ it 'makes post requests through the access token and returns a response object' do
158
+ @oauth_access_token.expects(:post).with('/sms/json', @json_encoded_body, @http_header_hash).returns(stub)
159
+
160
+ @client.send_message(@example_message_hash).must_be_instance_of(Nexmo::Response)
161
+ end
162
+ end
163
+
164
+ describe 'when initialized with a different json implementation' do
165
+ before do
166
+ @client = Nexmo::Client.new('key', 'secret', :json => MultiJson)
167
+ end
168
+
169
+ describe 'send_message method' do
170
+ it 'encodes the request body using the alternative json implementation' do
171
+ MultiJson.expects(:dump).with(instance_of(Hash))
172
+
173
+ @client.http.stubs(:post)
174
+
175
+ @client.send_message(@example_message_hash)
176
+ end
177
+ end
178
+ end
139
179
  end
140
180
 
141
181
  describe 'Nexmo::Response' do
@@ -187,20 +227,20 @@ describe 'Nexmo::Response' do
187
227
  @response.object.must_equal({'value' => 0})
188
228
  end
189
229
  end
190
- end
191
230
 
192
- describe 'Nexmo::Response initialized with a different json implementation' do
193
- before do
194
- @http_response = mock()
231
+ describe 'when initialized with a different json implementation' do
232
+ before do
233
+ @response = Nexmo::Response.new(@http_response, :json => MultiJson)
234
+ end
195
235
 
196
- @response = Nexmo::Response.new(@http_response, :json => MultiJson)
197
- end
236
+ describe 'object method' do
237
+ it 'decodes the response body using the alternative json implementation' do
238
+ MultiJson.expects(:load).with('{"value":0.0}')
198
239
 
199
- describe 'object method' do
200
- it 'decodes the response body as json and returns a hash' do
201
- @http_response.expects(:body).returns('{"value":0.0}')
240
+ @http_response.stubs(:body).returns('{"value":0.0}')
202
241
 
203
- @response.object.must_equal({'value' => 0})
242
+ @response.object
243
+ end
204
244
  end
205
245
  end
206
246
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nexmo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-16 00:00:00.000000000 Z
12
+ date: 2013-02-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -34,7 +34,7 @@ dependencies:
34
34
  requirements:
35
35
  - - ~>
36
36
  - !ruby/object:Gem::Version
37
- version: 0.10.3
37
+ version: 0.13.2
38
38
  type: :development
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +42,7 @@ dependencies:
42
42
  requirements:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
- version: 0.10.3
45
+ version: 0.13.2
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: multi_json
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -59,7 +59,23 @@ dependencies:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
61
  version: 1.3.6
62
- description: A simple wrapper for the Nexmo API
62
+ - !ruby/object:Gem::Dependency
63
+ name: oauth
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 0.4.7
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 0.4.7
78
+ description: A Ruby wrapper for the Nexmo API
63
79
  email:
64
80
  - mail@timcraft.com
65
81
  executables: []
@@ -95,4 +111,3 @@ signing_key:
95
111
  specification_version: 3
96
112
  summary: See description
97
113
  test_files: []
98
- has_rdoc: