nexmo 0.3.1 → 0.4.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 +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: