nexmo 1.0.0 → 1.1.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.
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: