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.
- data/README.md +22 -27
- data/lib/nexmo.rb +90 -14
- data/nexmo.gemspec +1 -1
- data/spec/nexmo_spec.rb +180 -32
- 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
|
-
|
5
|
+
Requirements
|
6
6
|
------------
|
7
7
|
|
8
|
-
|
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
|
-
|
13
|
-
|
11
|
+
Installation
|
12
|
+
------------
|
14
13
|
|
15
|
-
|
14
|
+
gem install nexmo
|
16
15
|
|
17
|
-
```ruby
|
18
|
-
nexmo = Nexmo::Client.new('...KEY...', '...SECRET...')
|
19
|
-
```
|
20
16
|
|
21
|
-
|
22
|
-
|
17
|
+
Quick Start
|
18
|
+
-----------
|
23
19
|
|
24
20
|
```ruby
|
25
|
-
nexmo
|
26
|
-
```
|
21
|
+
require 'nexmo'
|
27
22
|
|
28
|
-
|
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
|
-
|
32
|
+
puts "Sent message: #{response.message_id}"
|
47
33
|
elsif response.failure?
|
48
|
-
|
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
|
-
|
47
|
+
|
48
|
+
Bugs/Issues
|
49
|
+
-----------
|
50
|
+
|
51
|
+
Please report all bugs/issues via the GitHub issue tracker.
|
data/lib/nexmo.rb
CHANGED
@@ -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
|
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
|
-
|
29
|
+
Object.new(:message_id => object['message-id'], :success? => true, :failure? => false)
|
30
30
|
else
|
31
|
-
|
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
|
-
|
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
|
46
|
-
def
|
47
|
-
|
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
|
51
|
-
|
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
|
56
|
-
def
|
57
|
-
|
111
|
+
class Object
|
112
|
+
def initialize(attributes = {})
|
113
|
+
@attributes = attributes.to_hash
|
58
114
|
end
|
59
115
|
|
60
|
-
def
|
61
|
-
|
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
|
|
data/nexmo.gemspec
CHANGED
data/spec/nexmo_spec.rb
CHANGED
@@ -9,67 +9,215 @@ describe Nexmo::Client do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
describe 'http method' do
|
12
|
-
it '
|
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 '
|
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
|
-
|
26
|
-
|
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
|
-
|
31
|
+
headers = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
36
32
|
|
37
|
-
|
33
|
+
@client.http.expects(:post).with('/sms/json', data, headers).returns(http_response)
|
38
34
|
|
39
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
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
|
-
|
57
|
+
@client.http.stubs(:post).returns(http_response)
|
51
58
|
|
52
|
-
|
59
|
+
response = @client.send_message({to: 'number', text: 'Hey!'})
|
53
60
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
61
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
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.
|
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-
|
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:
|
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:
|
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:
|
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:
|
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.
|
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:
|