mollie-sms 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Eloy Duran, Fingertips <eloy@fngtps.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
data/README ADDED
@@ -0,0 +1,7 @@
1
+ A Ruby client that allows you to send SMS messages via http://mollie.nl.
2
+
3
+ It does only what we need, for our app, at this point in time. Which means that
4
+ connects to the webservice via SSL, and only sends a message to one recipient
5
+ at a time.
6
+
7
+ Patches are accepted.
data/lib/mollie/sms.rb ADDED
@@ -0,0 +1,139 @@
1
+ require "digest/md5"
2
+ require "uri"
3
+ require "net/https"
4
+
5
+ begin
6
+ require "rubygems"
7
+ rescue LoadError
8
+ end
9
+ require "active_support"
10
+
11
+ module Mollie
12
+ class SMS
13
+ GATEWAY_URI = URI.parse("https://secure.mollie.nl/xml/sms")
14
+
15
+ GATEWAYS = {
16
+ 'basic' => '2',
17
+ 'business' => '4',
18
+ 'business+' => '1',
19
+ 'landline' => '8'
20
+ }
21
+
22
+ class StandardError < ::StandardError; end
23
+ class ValidationError < StandardError; end
24
+ class MissingRequiredParam < StandardError; end
25
+
26
+ REQUIRED_PARAMS = %w{ username md5_password originator gateway charset type recipients message }
27
+
28
+ class << self
29
+ attr_accessor :username, :password, :originator, :charset, :type, :gateway
30
+
31
+ def password=(password)
32
+ @password = Digest::MD5.hexdigest(password)
33
+ end
34
+
35
+ def default_params
36
+ {
37
+ 'username' => @username,
38
+ 'md5_password' => @password,
39
+ 'originator' => @originator,
40
+ 'gateway' => @gateway,
41
+ 'charset' => @charset,
42
+ 'type' => @type
43
+ }
44
+ end
45
+ end
46
+
47
+ self.charset = 'UTF-8'
48
+ self.type = 'normal'
49
+ self.gateway = GATEWAYS['basic']
50
+
51
+ attr_reader :params
52
+
53
+ def initialize(telephone_number = nil, body = nil, extra_params = {})
54
+ @params = self.class.default_params.merge(extra_params)
55
+ self.telephone_number = telephone_number if telephone_number
56
+ self.body = body if body
57
+ end
58
+
59
+ def telephone_number
60
+ @params['recipients']
61
+ end
62
+
63
+ def telephone_number=(telephone_number)
64
+ @params['recipients'] = telephone_number
65
+ end
66
+
67
+ def body
68
+ @params['message']
69
+ end
70
+
71
+ def body=(body)
72
+ @params['message'] = body
73
+ end
74
+
75
+ def inspect
76
+ "#<#{self.class.name} from: <#{@params['originator']}> to: <#{telephone_number}> body: \"#{body}\" >"
77
+ end
78
+
79
+ def deliver
80
+ validate_params!
81
+
82
+ post = Net::HTTP::Post.new(GATEWAY_URI.path)
83
+ post.form_data = params
84
+ request = Net::HTTP.new(GATEWAY_URI.host, GATEWAY_URI.port)
85
+ request.use_ssl = true
86
+ request.start do |http|
87
+ response = http.request(post)
88
+ Response.new(response)
89
+ end
90
+ end
91
+
92
+ def validate_params!
93
+ params.slice(*REQUIRED_PARAMS).each do |key, value|
94
+ raise MissingRequiredParam, "The required parameter `#{key}' is missing." if value.blank?
95
+ end
96
+
97
+ originator = params['originator']
98
+ if originator =~ /^\d+$/
99
+ if originator.size > 14
100
+ raise ValidationError, "Originator may have a maximimun of 14 numerical characters."
101
+ end
102
+ elsif originator.size > 11
103
+ raise ValidationError, "Originator may have a maximimun of 11 alphanumerical characters."
104
+ end
105
+ end
106
+
107
+ class Response
108
+ attr_reader :http_response
109
+
110
+ def initialize(http_response)
111
+ @http_response = http_response
112
+ end
113
+
114
+ def params
115
+ @params ||= http_failure? ? {} : Hash.from_xml(@http_response.read_body)['response']['item']
116
+ end
117
+
118
+ def result_code
119
+ (http_failure? ? @http_response.code : params['resultcode']).to_i
120
+ end
121
+
122
+ def message
123
+ http_failure? ? "[HTTP: #{@http_response.code}] #{@http_response.message}" : params['resultmessage']
124
+ end
125
+
126
+ def success?
127
+ !http_failure? && params['success'] == 'true'
128
+ end
129
+
130
+ def http_failure?
131
+ !@http_response.is_a?(Net::HTTPSuccess)
132
+ end
133
+
134
+ def inspect
135
+ "#<#{self.class.name} #{ success? ? 'succeeded' : 'failed' } (#{result_code}) `#{message}'>"
136
+ end
137
+ end
138
+ end
139
+ end
data/spec/sms_spec.rb ADDED
@@ -0,0 +1,241 @@
1
+ require File.expand_path("../spec_helper", __FILE__)
2
+ require "mollie/sms"
3
+
4
+ Mollie::SMS.username = 'AstroRadio'
5
+ Mollie::SMS.password = 'secret'
6
+ Mollie::SMS.originator = 'Astro INC'
7
+
8
+ describe "Mollie::SMS" do
9
+ it "holds the gateway uri" do
10
+ Mollie::SMS::GATEWAY_URI.should == URI.parse("https://secure.mollie.nl/xml/sms")
11
+ end
12
+
13
+ it "holds the service username" do
14
+ Mollie::SMS.username.should == 'AstroRadio'
15
+ end
16
+
17
+ it "holds the service password as a MD5 hashed version" do
18
+ Mollie::SMS.password.should == Digest::MD5.hexdigest('secret')
19
+ end
20
+
21
+ it "holds the originator" do
22
+ Mollie::SMS.originator.should == 'Astro INC'
23
+ end
24
+
25
+ it "returns the default charset" do
26
+ Mollie::SMS.charset.should == 'UTF-8'
27
+ end
28
+
29
+ it "returns the default message type" do
30
+ Mollie::SMS.type.should == 'normal'
31
+ end
32
+
33
+ it "holds a list of available gateways" do
34
+ Mollie::SMS::GATEWAYS['basic'].should == '2'
35
+ Mollie::SMS::GATEWAYS['business'].should == '4'
36
+ Mollie::SMS::GATEWAYS['business+'].should == '1'
37
+ Mollie::SMS::GATEWAYS['landline'].should == '8'
38
+ end
39
+
40
+ it "returns the default gateway to use" do
41
+ Mollie::SMS.gateway.should == Mollie::SMS::GATEWAYS['basic']
42
+ end
43
+
44
+ it "returns a hash of default params for a request" do
45
+ Mollie::SMS.default_params.should == {
46
+ 'username' => 'AstroRadio',
47
+ 'md5_password' => Digest::MD5.hexdigest('secret'),
48
+ 'originator' => 'Astro INC',
49
+ 'gateway' => '2',
50
+ 'charset' => 'UTF-8',
51
+ 'type' => 'normal'
52
+ }
53
+ end
54
+
55
+ it "initializes, optionally, with a telephone number, body, and params" do
56
+ sms1 = Mollie::SMS.new
57
+ sms1.telephone_number = '+31612345678'
58
+ sms1.body = "The stars tell me you will have chicken noodle soup for breakfast."
59
+
60
+ sms2 = Mollie::SMS.new('+31612345678', "The stars tell me you will have chicken noodle soup for breakfast.", 'originator' => 'Eloy')
61
+ sms2.params['originator'].should == 'Eloy'
62
+
63
+ [sms1, sms2].each do |sms|
64
+ sms.telephone_number.should == '+31612345678'
65
+ sms.body.should == "The stars tell me you will have chicken noodle soup for breakfast."
66
+ end
67
+ end
68
+ end
69
+
70
+ describe "A Mollie::SMS instance" do
71
+ before do
72
+ @sms = Mollie::SMS.new
73
+ @sms.telephone_number = '+31612345678'
74
+ @sms.body = "The stars tell me you will have chicken noodle soup for breakfast."
75
+ end
76
+
77
+ it "returns the phone number" do
78
+ @sms.telephone_number.should == '+31612345678'
79
+ end
80
+
81
+ it "returns the message body" do
82
+ @sms.body.should == "The stars tell me you will have chicken noodle soup for breakfast."
83
+ end
84
+
85
+ it "returns the request params with all string keys and values" do
86
+ params = Mollie::SMS.default_params.merge(
87
+ 'recipients' => '+31612345678',
88
+ 'message' => "The stars tell me you will have chicken noodle soup for breakfast."
89
+ )
90
+ @sms.params.should == params
91
+ end
92
+ end
93
+
94
+ describe "When sending a Mollie::SMS message" do
95
+ before do
96
+ @sms = Mollie::SMS.new
97
+ @sms.telephone_number = '+31612345678'
98
+ @sms.body = "The stars tell me you will have chicken noodle soup for breakfast."
99
+ end
100
+
101
+ after do
102
+ Net::HTTP.reset!
103
+ end
104
+
105
+ it "posts the post body to the gateway" do
106
+ @sms.stubs(:params).returns('a key' => 'a value')
107
+ @sms.stubs(:validate_params!)
108
+ @sms.deliver
109
+
110
+ request, post = Net::HTTP.posted
111
+ request.should.use_ssl
112
+ request.host.should == Mollie::SMS::GATEWAY_URI.host
113
+ request.port.should == Mollie::SMS::GATEWAY_URI.port
114
+ post.path.should == Mollie::SMS::GATEWAY_URI.path
115
+ post.body.should == "a%20key=a%20value"
116
+ end
117
+
118
+ it "returns a Mollie::SMS::Response object, with the Net::HTTP response" do
119
+ Net::HTTP.stubbed_response = Net::HTTPOK.new('1.1', '200', 'OK')
120
+ Net::HTTP.stubbed_response.stubs(:read_body).returns(SUCCESS_BODY)
121
+ response = @sms.deliver
122
+ response.should.be.instance_of Mollie::SMS::Response
123
+ response.http_response.should == Net::HTTP.stubbed_response
124
+ end
125
+ end
126
+
127
+ describe "A Mollie::SMS::Response instance, for a succeeded request" do
128
+ before do
129
+ @http_response = Net::HTTPOK.new('1.1', '200', 'OK')
130
+ @http_response.stubs(:read_body).returns(SUCCESS_BODY)
131
+ @http_response.add_field('Content-type', 'application/xml')
132
+ @response = Mollie::SMS::Response.new(@http_response)
133
+ end
134
+
135
+ it "returns the Net::HTTP response object" do
136
+ @response.http_response.should == @http_response
137
+ end
138
+
139
+ it "returns the response body as a hash" do
140
+ @response.params.should == Hash.from_xml(SUCCESS_BODY)['response']['item']
141
+ end
142
+
143
+ it "returns whether or not it was a success" do
144
+ @response.should.be.success
145
+
146
+ @response.stubs(:params).returns('success' => 'false')
147
+ @response.should.not.be.success
148
+ end
149
+
150
+ it "returns the result code" do
151
+ @response.result_code.should == 10
152
+ end
153
+
154
+ it "returns the message corresponding to the result code" do
155
+ @response.message.should == "Message successfully sent."
156
+ end
157
+ end
158
+
159
+ describe "A Mollie::SMS::Response instance, for a request that failed at the gateway" do
160
+ before do
161
+ @http_response = Net::HTTPOK.new('1.1', '200', 'OK')
162
+ @http_response.stubs(:read_body).returns(FAILURE_BODY)
163
+ @http_response.add_field('Content-type', 'application/xml')
164
+ @response = Mollie::SMS::Response.new(@http_response)
165
+ end
166
+
167
+ it "returns that the request was not a success" do
168
+ @response.should.not.be.success
169
+ end
170
+
171
+ it "returns that this is a *not* HTTP failure" do
172
+ @response.should.not.be.http_failure
173
+ end
174
+
175
+ it "returns the result_code" do
176
+ @response.result_code.should == 20
177
+ end
178
+
179
+ it "returns the message corresponding to the result code" do
180
+ @response.message.should == "No username given."
181
+ end
182
+ end
183
+
184
+ describe "A Mollie::SMS::Response instance, for a failed HTTP request" do
185
+ before do
186
+ @http_response = Net::HTTPBadRequest.new('1.1', '400', 'Bad request')
187
+ @response = Mollie::SMS::Response.new(@http_response)
188
+ end
189
+
190
+ it "returns an empty hash as the params" do
191
+ @response.params.should == {}
192
+ end
193
+
194
+ it "returns that the request was not a success" do
195
+ @response.should.not.be.success
196
+ end
197
+
198
+ it "returns that this is a HTTP failure" do
199
+ @response.should.be.http_failure
200
+ end
201
+
202
+ it "returns the HTTP response code as the result_code" do
203
+ @response.result_code.should == 400
204
+ end
205
+
206
+ it "returns the HTTP error message as the message" do
207
+ @response.message.should == "[HTTP: 400] Bad request"
208
+ end
209
+ end
210
+
211
+ describe "Mollie::SMS, concerning validation" do
212
+ before do
213
+ @sms = Mollie::SMS.new
214
+ @sms.telephone_number = '+31612345678'
215
+ @sms.body = "The stars tell me you will have chicken noodle soup for breakfast."
216
+ end
217
+
218
+ it "accepts an originator of upto 14 numbers" do
219
+ @sms.params['originator'] = "00000000001111"
220
+ lambda { @sms.deliver }.should.not.raise
221
+ end
222
+
223
+ it "does not accept an originator string with more than 14 numbers" do
224
+ @sms.params['originator'] = "000000000011112"
225
+ lambda do
226
+ @sms.deliver
227
+ end.should.raise(Mollie::SMS::ValidationError, "Originator may have a maximimun of 14 numerical characters.")
228
+ end
229
+
230
+ it "accepts an originator of upto 11 alphanumerical characters" do
231
+ @sms.params['originator'] = "0123456789A"
232
+ lambda { @sms.deliver }.should.not.raise
233
+ end
234
+
235
+ it "does not accept an originator string with more than 11 alphanumerical characters" do
236
+ @sms.params['originator'] = "0123456789AB"
237
+ lambda do
238
+ @sms.deliver
239
+ end.should.raise(Mollie::SMS::ValidationError, "Originator may have a maximimun of 11 alphanumerical characters.")
240
+ end
241
+ end
@@ -0,0 +1,58 @@
1
+ require "rubygems"
2
+ require "bacon"
3
+ require "mocha"
4
+
5
+ $:.unshift File.expand_path("../../lib", __FILE__)
6
+
7
+ Bacon.summary_on_exit
8
+
9
+ module Net
10
+ class HTTP
11
+ class << self
12
+ attr_accessor :posted, :stubbed_response
13
+
14
+ def reset!
15
+ @posted = nil
16
+ @stubbed_response = nil
17
+ end
18
+ end
19
+
20
+ def host
21
+ @address
22
+ end
23
+
24
+ def start
25
+ yield self
26
+ end
27
+
28
+ def request(request)
29
+ self.class.posted = [self, request]
30
+ self.class.stubbed_response
31
+ end
32
+ end
33
+ end
34
+
35
+ class ResponseStub
36
+ end
37
+
38
+ SUCCESS_BODY = %{
39
+ <?xml version="1.0" ?>
40
+ <response>
41
+ <item type="sms">
42
+ <recipients>1</recipients>
43
+ <success>true</success>
44
+ <resultcode>10</resultcode>
45
+ <resultmessage>Message successfully sent.</resultmessage>
46
+ </item>
47
+ </response>}
48
+
49
+ FAILURE_BODY = %{
50
+ <?xml version="1.0"?>
51
+ <response>
52
+ <item type="sms">
53
+ <recipients>1</recipients>
54
+ <success>false</success>
55
+ <resultcode>20</resultcode>
56
+ <resultmessage>No username given.</resultmessage>
57
+ </item>
58
+ </response>}
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mollie-sms
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Eloy Duran
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-02 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 19
30
+ segments:
31
+ - 2
32
+ - 3
33
+ - 8
34
+ version: 2.3.8
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: Send SMS text messages via the Mollie.nl SMS gateway.
38
+ email:
39
+ - eloy@fngtps.com
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files:
45
+ - LICENSE
46
+ - README
47
+ files:
48
+ - LICENSE
49
+ - README
50
+ - lib/mollie/sms.rb
51
+ - spec/sms_spec.rb
52
+ - spec/spec_helper.rb
53
+ has_rdoc: true
54
+ homepage: http://github.com/Fingertips/Mollie-SMS
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --charset=UTF-8
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ hash: 3
68
+ segments:
69
+ - 0
70
+ version: "0"
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.3.7
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Send SMS text messages via the Mollie.nl SMS gateway.
87
+ test_files:
88
+ - spec/sms_spec.rb
89
+ - spec/spec_helper.rb