mollie-sms 0.1.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/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