nexmo 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|