nexmo 0.3.1 → 0.4.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 +22 -27
  2. data/lib/nexmo.rb +90 -14
  3. data/nexmo.gemspec +1 -1
  4. data/spec/nexmo_spec.rb +180 -32
  5. metadata +18 -7
data/README.md CHANGED
@@ -2,55 +2,50 @@ A simple wrapper for the [Nexmo](http://nexmo.com/) API
2
2
  =======================================================
3
3
 
4
4
 
5
- Installation
5
+ Requirements
6
6
  ------------
7
7
 
8
- Run `gem install nexmo` and `require 'nexmo'`,
9
- or do the gemfile/bundle thing if you're using Rails.
8
+ Ruby 1.9; Ruby 1.8 is not currently supported.
10
9
 
11
10
 
12
- Usage
13
- -----
11
+ Installation
12
+ ------------
14
13
 
15
- Construct a client object with your Nexmo API credentials:
14
+ gem install nexmo
16
15
 
17
- ```ruby
18
- nexmo = Nexmo::Client.new('...KEY...', '...SECRET...')
19
- ```
20
16
 
21
- The underlying HTTP object is easily accessible. For example, you may want
22
- to adjust the SSL verification when testing locally:
17
+ Quick Start
18
+ -----------
23
19
 
24
20
  ```ruby
25
- nexmo.http.verify_mode = OpenSSL::SSL::VERIFY_NONE
26
- ```
21
+ require 'nexmo'
27
22
 
28
- Use the `send_message` method to send an SMS, passing the API
29
- parameters as a hash:
23
+ nexmo = Nexmo::Client.new('...API KEY...', '...API SECRET...')
30
24
 
31
- ```ruby
32
25
  response = nexmo.send_message({
33
26
  from: 'RUBY',
34
27
  to: '...NUMBER...',
35
28
  text: 'Hello world'
36
29
  })
37
- ```
38
30
 
39
- Phone numbers should be specified in international format. If the response
40
- is successful you can access the message id, and if it's a failure you can
41
- retrieve the error message and/or the underlying HTTP response returned from
42
- the server:
43
-
44
- ```ruby
45
31
  if response.success?
46
- # store response.message_id
32
+ puts "Sent message: #{response.message_id}"
47
33
  elsif response.failure?
48
- # check response.error.message and/or response.http
49
- # raise response.error
34
+ raise response.error
50
35
  end
51
36
  ```
52
37
 
38
+
39
+ Troubleshooting
40
+ ---------------
41
+
42
+ Phone numbers should be specified in international format.
43
+
53
44
  The Nexmo documentation contains a [list of error codes](http://nexmo.com/documentation/index.html#dlr_error)
54
45
  which may be useful if you have problems sending a message.
55
46
 
56
- That's all folks. Chunky bacon.
47
+
48
+ Bugs/Issues
49
+ -----------
50
+
51
+ Please report all bugs/issues via the GitHub issue tracker.
@@ -20,45 +20,121 @@ module Nexmo
20
20
  def send_message(data)
21
21
  response = @http.post('/sms/json', encode(data), headers)
22
22
 
23
- if response.code.to_i == 200 && response['Content-Type'].sub(/;.*/, '') == 'application/json'
23
+ if ok?(response) && json?(response)
24
24
  object = JSON.parse(response.body)['messages'].first
25
25
 
26
26
  status = object['status'].to_i
27
27
 
28
28
  if status == 0
29
- Success.new(object['message-id'])
29
+ Object.new(:message_id => object['message-id'], :success? => true, :failure? => false)
30
30
  else
31
- Failure.new(Error.new("#{object['error-text']} (status=#{status})"), response, status)
31
+ error = Error.new("#{object['error-text']} (status=#{status})")
32
+
33
+ Object.new(:error => error, :http => response, :status => status, :success? => false, :failure? => true)
32
34
  end
33
35
  else
34
- Failure.new(Error.new("Unexpected HTTP response (code=#{response.code})"), response)
36
+ error = Error.new("Unexpected HTTP response (code=#{response.code})")
37
+
38
+ Object.new(:error => error, :http => response, :success? => false, :failure? => true)
35
39
  end
36
40
  end
37
41
 
42
+ def get_balance
43
+ get("/account/get-balance/#{key}/#{secret}")
44
+ end
45
+
46
+ def get_country_pricing(country_code)
47
+ get("/account/get-pricing/outbound/#{key}/#{secret}/#{country_code}")
48
+ end
49
+
50
+ def get_prefix_pricing(prefix)
51
+ get("/account/get-prefix-pricing/outbound/#{key}/#{secret}/#{prefix}")
52
+ end
53
+
54
+ def get_account_numbers(params)
55
+ get("/account/numbers/#{key}/#{secret}", params)
56
+ end
57
+
58
+ def number_search(country_code, params = {})
59
+ get("/number/search/#{key}/#{secret}/#{country_code}", params)
60
+ end
61
+
62
+ def get_message(id)
63
+ get("/search/message/#{key}/#{secret}/#{id}")
64
+ end
65
+
66
+ def get_message_rejections(params)
67
+ get("/search/rejections/#{key}/#{secret}", params)
68
+ end
69
+
38
70
  private
39
71
 
72
+ def get(path, params = {})
73
+ Response.new(@http.get(params.empty? ? path : "#{path}?#{URI.encode_www_form(params)}"))
74
+ end
75
+
76
+ def ok?(response)
77
+ response.code.to_i == 200
78
+ end
79
+
80
+ def json?(response)
81
+ response['Content-Type'].split(?;).first == 'application/json'
82
+ end
83
+
40
84
  def encode(data)
41
85
  URI.encode_www_form data.merge(:username => @key, :password => @secret)
42
86
  end
43
87
  end
44
88
 
45
- class Success < Struct.new(:message_id)
46
- def success?
47
- true
89
+ class Response
90
+ def initialize(http_response)
91
+ @http_response = http_response
92
+ end
93
+
94
+ def method_missing(name, *args, &block)
95
+ @http_response.send(name, *args, &block)
48
96
  end
49
97
 
50
- def failure?
51
- false
98
+ def ok?
99
+ code.to_i == 200
100
+ end
101
+
102
+ def json?
103
+ self['Content-Type'].split(?;).first == 'application/json'
104
+ end
105
+
106
+ def object
107
+ JSON.parse(body, object_class: Object)
52
108
  end
53
109
  end
54
110
 
55
- class Failure < Struct.new(:error, :http, :status)
56
- def success?
57
- false
111
+ class Object
112
+ def initialize(attributes = {})
113
+ @attributes = attributes.to_hash
58
114
  end
59
115
 
60
- def failure?
61
- true
116
+ def [](name)
117
+ @attributes[name]
118
+ end
119
+
120
+ def []=(name, value)
121
+ @attributes[name.to_s.tr(?-, ?_).to_sym] = value
122
+ end
123
+
124
+ def to_hash
125
+ @attributes
126
+ end
127
+
128
+ def respond_to_missing?(name, include_private = false)
129
+ @attributes.has_key?(name)
130
+ end
131
+
132
+ def method_missing(name, *args, &block)
133
+ if @attributes.has_key?(name) && args.empty? && block.nil?
134
+ @attributes[name]
135
+ else
136
+ super name, *args, &block
137
+ end
62
138
  end
63
139
  end
64
140
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'nexmo'
3
- s.version = '0.3.1'
3
+ s.version = '0.4.0'
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.authors = ['Tim Craft']
6
6
  s.email = ['mail@timcraft.com']
@@ -9,67 +9,215 @@ describe Nexmo::Client do
9
9
  end
10
10
 
11
11
  describe 'http method' do
12
- it 'should return a Net::HTTP object that uses SSL' do
12
+ it 'returns a net http object that uses ssl' do
13
13
  @client.http.must_be_instance_of(Net::HTTP)
14
14
  @client.http.use_ssl?.must_equal(true)
15
15
  end
16
16
  end
17
17
 
18
18
  describe 'headers method' do
19
- it 'should return a hash' do
19
+ it 'returns a hash' do
20
20
  @client.headers.must_be_kind_of(Hash)
21
21
  end
22
22
  end
23
23
 
24
24
  describe 'send_message method' do
25
- before do
26
- @headers = {'Content-Type' => 'application/x-www-form-urlencoded'}
27
- end
28
-
29
- it 'should make the correct http call and return a success object if the first message status equals 0' do
30
- http_response = stub(:code => '200', :body => '{"messages":[{"status":0,"message-id":"id"}]}')
25
+ it 'posts to the sms resource' do
26
+ http_response = stub(code: '200', body: '{"messages":[{"status":0,"message-id":"id"}]}')
31
27
  http_response.expects(:[]).with('Content-Type').returns('application/json;charset=utf-8')
32
28
 
33
29
  data = 'from=ruby&to=number&text=Hey%21&username=key&password=secret'
34
30
 
35
- @client.http.expects(:post).with('/sms/json', data, @headers).returns(http_response)
31
+ headers = {'Content-Type' => 'application/x-www-form-urlencoded'}
36
32
 
37
- response = @client.send_message({from: 'ruby', to: 'number', text: 'Hey!'})
33
+ @client.http.expects(:post).with('/sms/json', data, headers).returns(http_response)
38
34
 
39
- response.success?.must_equal(true)
40
- response.failure?.must_equal(false)
41
- response.message_id.must_equal('id')
35
+ @client.send_message({from: 'ruby', to: 'number', text: 'Hey!'})
42
36
  end
43
37
 
44
- it 'should return a failure object if the first message status does not equal 0' do
45
- http_response = stub(:code => '200', :body => '{"messages":[{"status":2,"error-text":"Missing from param"}]}')
46
- http_response.expects(:[]).with('Content-Type').returns('application/json')
38
+ describe 'when the first message status equals 0' do
39
+ it 'returns a success object' do
40
+ http_response = stub(code: '200', body: '{"messages":[{"status":0,"message-id":"id"}]}')
41
+ http_response.expects(:[]).with('Content-Type').returns('application/json;charset=utf-8')
42
+
43
+ @client.http.stubs(:post).returns(http_response)
44
+
45
+ response = @client.send_message({from: 'ruby', to: 'number', text: 'Hey!'})
46
+ response.success?.must_equal(true)
47
+ response.failure?.must_equal(false)
48
+ response.message_id.must_equal('id')
49
+ end
50
+ end
47
51
 
48
- data = 'to=number&text=Hey%21&username=key&password=secret'
52
+ describe 'when the first message status does not equal 0' do
53
+ it 'returns a failure object' do
54
+ http_response = stub(code: '200', body: '{"messages":[{"status":2,"error-text":"Missing from param"}]}')
55
+ http_response.expects(:[]).with('Content-Type').returns('application/json')
49
56
 
50
- @client.http.expects(:post).with('/sms/json', data, @headers).returns(http_response)
57
+ @client.http.stubs(:post).returns(http_response)
51
58
 
52
- response = @client.send_message({to: 'number', text: 'Hey!'})
59
+ response = @client.send_message({to: 'number', text: 'Hey!'})
53
60
 
54
- response.success?.must_equal(false)
55
- response.failure?.must_equal(true)
56
- response.error.to_s.must_equal('Missing from param (status=2)')
57
- response.http.wont_be_nil
61
+ response.success?.must_equal(false)
62
+ response.failure?.must_equal(true)
63
+ response.error.to_s.must_equal('Missing from param (status=2)')
64
+ response.http.wont_be_nil
65
+ end
58
66
  end
59
67
 
60
- it 'should return a failure object if the server returns an unexpected http response' do
61
- http_response = stub(:code => '503')
68
+ describe 'when the server returns an unexpected http response' do
69
+ it 'returns a failure object' do
70
+ @client.http.stubs(:post).returns(stub(code: '503'))
62
71
 
63
- data = 'from=ruby&to=number&text=Hey%21&username=key&password=secret'
72
+ response = @client.send_message({from: 'ruby', to: 'number', text: 'Hey!'})
73
+
74
+ response.success?.must_equal(false)
75
+ response.failure?.must_equal(true)
76
+ response.error.to_s.must_equal('Unexpected HTTP response (code=503)')
77
+ response.http.wont_be_nil
78
+ end
79
+ end
80
+ end
81
+
82
+ describe 'get_balance method' do
83
+ it 'fetches the account balance resource and returns a response object' do
84
+ @client.http.expects(:get).with('/account/get-balance/key/secret').returns(stub)
85
+
86
+ @client.get_balance.must_be_instance_of(Nexmo::Response)
87
+ end
88
+ end
89
+
90
+ describe 'get_country_pricing method' do
91
+ it 'fetches the outbound pricing resource for the given country and returns a response object' do
92
+ @client.http.expects(:get).with('/account/get-pricing/outbound/key/secret/CA').returns(stub)
93
+
94
+ @client.get_country_pricing(:CA).must_be_instance_of(Nexmo::Response)
95
+ end
96
+ end
97
+
98
+ describe 'get_prefix_pricing method' do
99
+ it 'fetches the outbound pricing resource for the given prefix and returns a response object' do
100
+ @client.http.expects(:get).with('/account/get-prefix-pricing/outbound/key/secret/44').returns(stub)
101
+
102
+ @client.get_prefix_pricing(44).must_be_instance_of(Nexmo::Response)
103
+ end
104
+ end
105
+
106
+ describe 'get_account_numbers method' do
107
+ it 'fetches the account numbers resource with the given parameters and returns a response object' do
108
+ @client.http.expects(:get).with('/account/numbers/key/secret?size=25&pattern=33').returns(stub)
109
+
110
+ @client.get_account_numbers(size: 25, pattern: 33).must_be_instance_of(Nexmo::Response)
111
+ end
112
+ end
113
+
114
+ describe 'number_search method' do
115
+ it 'fetches the number search resource for the given country with the given parameters and returns a response object' do
116
+ @client.http.expects(:get).with('/number/search/key/secret/CA?size=25').returns(stub)
117
+
118
+ @client.number_search(:CA, size: 25).must_be_instance_of(Nexmo::Response)
119
+ end
120
+ end
121
+
122
+ describe 'get_message method' do
123
+ it 'fetches the message search resource for the given message id and returns a response object' do
124
+ @client.http.expects(:get).with('/search/message/key/secret/00A0B0C0').returns(stub)
125
+
126
+ @client.get_message('00A0B0C0').must_be_instance_of(Nexmo::Response)
127
+ end
128
+ end
129
+
130
+ describe 'get_message_rejections method' do
131
+ it 'fetches the message rejections resource with the given parameters and returns a response object' do
132
+ @client.http.expects(:get).with('/search/rejections/key/secret?date=YYYY-MM-DD').returns(stub)
133
+
134
+ @client.get_message_rejections(date: 'YYYY-MM-DD').must_be_instance_of(Nexmo::Response)
135
+ end
136
+ end
137
+ end
64
138
 
65
- @client.http.expects(:post).with('/sms/json', data, @headers).returns(http_response)
139
+ describe Nexmo::Response do
140
+ before do
141
+ @http_response = mock()
142
+
143
+ @response = Nexmo::Response.new(@http_response)
144
+ end
145
+
146
+ it 'delegates to the underlying http response' do
147
+ @http_response.expects(:code).returns('200')
148
+
149
+ @response.code.must_equal('200')
150
+ end
151
+
152
+ describe 'ok query method' do
153
+ it 'returns true if the status code is 200' do
154
+ @http_response.expects(:code).returns('200')
155
+
156
+ @response.ok?.must_equal(true)
157
+ end
158
+
159
+ it 'returns false otherwise' do
160
+ @http_response.expects(:code).returns('400')
161
+
162
+ @response.ok?.must_equal(false)
163
+ end
164
+ end
165
+
166
+ describe 'json query method' do
167
+ it 'returns true if the response has a json content type' do
168
+ @http_response.expects(:[]).with('Content-Type').returns('application/json;charset=utf-8')
169
+
170
+ @response.json?.must_equal(true)
171
+ end
172
+
173
+ it 'returns false otherwise' do
174
+ @http_response.expects(:[]).with('Content-Type').returns('text/html')
175
+
176
+ @response.json?.must_equal(false)
177
+ end
178
+ end
179
+
180
+ describe 'object method' do
181
+ it 'decodes the response body as json and returns an object' do
182
+ @http_response.expects(:body).returns('{}')
183
+
184
+ @response.object.must_be_instance_of(Nexmo::Object)
185
+ end
186
+ end
187
+ end
188
+
189
+ describe Nexmo::Object do
190
+ before do
191
+ @value = 'xxx'
192
+
193
+ @object = Nexmo::Object.new(message_id: @value)
194
+ end
66
195
 
67
- response = @client.send_message({from: 'ruby', to: 'number', text: 'Hey!'})
196
+ it 'provides method access for attributes passed to the constructor' do
197
+ @object.message_id.must_equal(@value)
198
+ end
199
+
200
+ describe 'square brackets method' do
201
+ it 'returns the value of the given attribute' do
202
+ @object[:message_id].must_equal(@value)
203
+ end
204
+ end
205
+
206
+ describe 'square brackets equals method' do
207
+ it 'sets the value of the given attribute' do
208
+ @object['message_id'] = 'abc'
209
+ @object.message_id.wont_equal(@value)
210
+ end
211
+
212
+ it 'replaces dashes in keys with underscores' do
213
+ @object['message-id'] = 'abc'
214
+ @object.message_id.wont_equal(@value)
215
+ end
216
+ end
68
217
 
69
- response.success?.must_equal(false)
70
- response.failure?.must_equal(true)
71
- response.error.to_s.must_equal('Unexpected HTTP response (code=503)')
72
- response.http.wont_be_nil
218
+ describe 'to_hash method' do
219
+ it 'returns a hash containing the object attributes' do
220
+ @object.to_hash.must_equal({message_id: @value})
73
221
  end
74
222
  end
75
223
  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: 0.3.1
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-28 00:00:00.000000000 Z
12
+ date: 2012-10-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
16
- requirement: &3095740 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '1.5'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *3095740
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.5'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: mocha
27
- requirement: &3095540 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,7 +37,12 @@ dependencies:
32
37
  version: '0'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *3095540
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
36
46
  description: A simple wrapper for the Nexmo API
37
47
  email:
38
48
  - mail@timcraft.com
@@ -64,8 +74,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
74
  version: '0'
65
75
  requirements: []
66
76
  rubyforge_project:
67
- rubygems_version: 1.8.16
77
+ rubygems_version: 1.8.24
68
78
  signing_key:
69
79
  specification_version: 3
70
80
  summary: See description
71
81
  test_files: []
82
+ has_rdoc: