mote_sms 1.2.0 → 1.3.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -11
- data/lib/mote_sms/delivery_job.rb +16 -0
- data/lib/mote_sms/message.rb +18 -1
- data/lib/mote_sms/ssl_certs/ThawtePrimaryRootG3CA.pem +25 -0
- data/lib/mote_sms/ssl_certs/ThawteSSLGA2CA.pem +101 -0
- data/lib/mote_sms/ssl_certs/api.swisscom.com.pem +29 -0
- data/lib/mote_sms/ssl_certs/bulk.mobile-gw.com.pem +93 -79
- data/lib/mote_sms/transports/action_mailer_transport.rb +1 -1
- data/lib/mote_sms/transports/http_client.rb +91 -0
- data/lib/mote_sms/transports/mobile_technics_transport.rb +22 -103
- data/lib/mote_sms/transports/swisscom_transport.rb +106 -0
- data/lib/mote_sms/transports.rb +2 -0
- data/lib/mote_sms/version.rb +1 -1
- data/lib/mote_sms.rb +23 -4
- data/mote_sms.gemspec +12 -9
- data/spec/mote_sms/delivery_job_spec.rb +13 -0
- data/spec/mote_sms/message_spec.rb +50 -7
- data/spec/mote_sms/number_spec.rb +22 -20
- data/spec/mote_sms/transports/http_client_spec.rb +87 -0
- data/spec/spec_helper.rb +7 -0
- metadata +71 -14
- data/lib/mote_sms/ssl_certs/ThawteSSLCA.pem +0 -97
@@ -3,6 +3,8 @@ require 'net/http'
|
|
3
3
|
require 'phony'
|
4
4
|
require 'logger'
|
5
5
|
|
6
|
+
require 'mote_sms/transports/http_client'
|
7
|
+
|
6
8
|
module MoteSMS
|
7
9
|
|
8
10
|
# MoteSMS::MobileTechnicsTransport provides the implementation to
|
@@ -16,40 +18,14 @@ module MoteSMS
|
|
16
18
|
# # => ['000-791234', '001-7987324']
|
17
19
|
#
|
18
20
|
class MobileTechnicsTransport
|
19
|
-
|
20
21
|
# Maximum recipients allowed by API
|
21
22
|
MAX_RECIPIENT = 100
|
22
23
|
|
23
|
-
# Path to certificates
|
24
|
-
CERTS_PATH = File.expand_path File.join(File.dirname(__FILE__), '..', 'ssl_certs')
|
25
|
-
|
26
24
|
# Custom exception subclass.
|
27
25
|
ServiceError = Class.new(::Exception)
|
28
26
|
|
29
27
|
# Readable attributes
|
30
|
-
attr_reader :endpoint, :username, :password, :options
|
31
|
-
|
32
|
-
# Internal: The default certificate store, adds all *CA.pem files
|
33
|
-
# from mote_sms/ssl_certs directory.
|
34
|
-
#
|
35
|
-
# Returns a OpenSSL::X509::Store
|
36
|
-
def self.default_cert_store
|
37
|
-
@cert_store ||= OpenSSL::X509::Store.new.tap do |store|
|
38
|
-
Dir["#{CERTS_PATH}/*CA.pem"].each { |c| store.add_file c }
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# Internal: Load a X509::Certificate based on the hostname, used to handle
|
43
|
-
# server certificate fingerprinting.
|
44
|
-
#
|
45
|
-
# host - The String with the hostname
|
46
|
-
#
|
47
|
-
# Returns OpenSSL::X509::Certificate or nil if no certificate for this host
|
48
|
-
# is found
|
49
|
-
def self.fingerprint_cert(host)
|
50
|
-
cert = "#{CERTS_PATH}/#{host}.pem"
|
51
|
-
OpenSSL::X509::Certificate.new(File.read(cert)) if File.exists?(cert)
|
52
|
-
end
|
28
|
+
attr_reader :endpoint, :username, :password, :options, :http_client
|
53
29
|
|
54
30
|
# Public: Global default parameters for sending messages, Procs/lambdas
|
55
31
|
# are evaluated on #deliver. Ensure to use only symbols as keys. Contains
|
@@ -61,13 +37,8 @@ module MoteSMS
|
|
61
37
|
#
|
62
38
|
# Returns Hash with options.
|
63
39
|
def self.defaults
|
64
|
-
|
65
|
-
allow_adaption: true
|
66
|
-
ssl: ->(http) {
|
67
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
68
|
-
http.verify_depth = 9
|
69
|
-
http.cert_store = self.default_cert_store
|
70
|
-
}
|
40
|
+
@options ||= {
|
41
|
+
allow_adaption: true
|
71
42
|
}
|
72
43
|
end
|
73
44
|
|
@@ -76,7 +47,7 @@ module MoteSMS
|
|
76
47
|
#
|
77
48
|
# Returns Logger instance.
|
78
49
|
def self.logger
|
79
|
-
|
50
|
+
@logger ||= ::Logger.new($stdout)
|
80
51
|
end
|
81
52
|
|
82
53
|
# Public: Change the logger used to log all HTTP requests to
|
@@ -86,7 +57,7 @@ module MoteSMS
|
|
86
57
|
#
|
87
58
|
# Returns nothing.
|
88
59
|
def self.logger=(logger)
|
89
|
-
|
60
|
+
@logger = logger
|
90
61
|
end
|
91
62
|
|
92
63
|
# Public: Create a new instance using specified endpoint, username
|
@@ -99,11 +70,17 @@ module MoteSMS
|
|
99
70
|
# :ssl - SSL client options
|
100
71
|
#
|
101
72
|
# Returns a new instance.
|
102
|
-
def initialize(endpoint, username, password, options =
|
73
|
+
def initialize(endpoint, username, password, options = {})
|
103
74
|
@endpoint = URI.parse(endpoint)
|
104
75
|
@username = username
|
105
76
|
@password = password
|
106
|
-
|
77
|
+
|
78
|
+
@options = self.class.defaults.merge(options)
|
79
|
+
|
80
|
+
@http_client = Transports::HttpClient.new(endpoint,
|
81
|
+
proxy_address: @options[:proxy_address],
|
82
|
+
proxy_port: @options[:proxy_port],
|
83
|
+
ssl: @options[:ssl])
|
107
84
|
end
|
108
85
|
|
109
86
|
# Public: Delivers message using mobile technics HTTP/S API.
|
@@ -112,84 +89,26 @@ module MoteSMS
|
|
112
89
|
# options - The Hash with service specific options.
|
113
90
|
#
|
114
91
|
# Returns Array with sender ids.
|
115
|
-
def deliver(message,
|
92
|
+
def deliver(message, deliver_options = {})
|
116
93
|
raise ArgumentError, "too many recipients, max. is #{MAX_RECIPIENT} (current: #{message.to.length})" if message.to.length > MAX_RECIPIENT
|
117
94
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
95
|
+
request = Net::HTTP::Post.new(endpoint.request_uri).tap do |req|
|
96
|
+
req.body = URI.encode_www_form post_params(message, options.merge(deliver_options))
|
97
|
+
req.content_type = 'application/x-www-form-urlencoded; charset=utf-8'
|
98
|
+
end
|
122
99
|
|
123
|
-
# Log as `curl` request
|
124
100
|
self.class.logger.debug "curl -X#{request.method} '#{endpoint}' -d '#{request.body}'"
|
125
101
|
|
126
|
-
|
127
|
-
resp = http.request(request)
|
102
|
+
resp = http_client.request(request)
|
128
103
|
|
129
|
-
# Handle errors
|
130
104
|
raise ServiceError, "endpoint did respond with #{resp.code}" unless resp.code.to_i == 200
|
131
105
|
raise ServiceError, "unable to deliver message to all recipients (CAUSE: #{resp.body.strip})" unless resp.body.split("\n").all? { |l| l =~ /Result_code: 00/ }
|
132
106
|
|
133
|
-
# extract Nth-SmsIds
|
134
107
|
resp['X-Nth-SmsId'].split(',')
|
135
108
|
end
|
136
109
|
|
137
110
|
private
|
138
111
|
|
139
|
-
# Internal: Prepare request including body, headers etc.
|
140
|
-
#
|
141
|
-
# uri - The URI from the endpoint.
|
142
|
-
# params - The Array with the attributes.
|
143
|
-
#
|
144
|
-
# Returns Net::HTTP::Post instance.
|
145
|
-
def http_request(params)
|
146
|
-
Net::HTTP::Post.new(endpoint.request_uri).tap do |request|
|
147
|
-
request.body = URI.encode_www_form params
|
148
|
-
request.content_type = 'application/x-www-form-urlencoded; charset=utf-8'
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
# Internal: Build new Net::HTTP instance, enables SSL if requested.
|
153
|
-
#
|
154
|
-
# options - The Hash with all options
|
155
|
-
#
|
156
|
-
# Returns Net::HTTP client instance.
|
157
|
-
def http_client(options)
|
158
|
-
Net::HTTP.new(endpoint.host, endpoint.port).tap do |http|
|
159
|
-
if endpoint.instance_of?(URI::HTTPS)
|
160
|
-
cert = self.class.fingerprint_cert(endpoint.host)
|
161
|
-
http.use_ssl = true
|
162
|
-
http.verify_callback = ->(ok, store) { verify_fingerprint(cert.serial, ok, store) } if cert
|
163
|
-
options[:ssl].call(http) if options[:ssl].respond_to?(:call)
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
# Public: Verify SSL server certifcate when a certificate is available in
|
169
|
-
# mote_sms/ssl_certs/{host}.pem. Implemented to return false if first
|
170
|
-
# certificate in chain does not match the expected serial.
|
171
|
-
#
|
172
|
-
# serial - The expected server certificates serial
|
173
|
-
# ok - The Boolean forwarded by verify_callback
|
174
|
-
# store - The OpenSSL::X509::Store instance with the chain
|
175
|
-
#
|
176
|
-
# Returns Boolean
|
177
|
-
def verify_fingerprint(serial, ok, store)
|
178
|
-
return false unless store.chain.first.serial == serial
|
179
|
-
ok
|
180
|
-
end
|
181
|
-
|
182
|
-
# Internal: Merge defaults from class and instance with options
|
183
|
-
# supplied to #deliver. Removes `:http` options, because those
|
184
|
-
# are only for the HTTP client to set ssl verify mode et all.
|
185
|
-
#
|
186
|
-
# options - The Hash to merge with #defaults and #options.
|
187
|
-
#
|
188
|
-
# Returns Hash.
|
189
|
-
def prepare_options(options)
|
190
|
-
options = self.class.defaults.merge(self.options).merge(options)
|
191
|
-
end
|
192
|
-
|
193
112
|
# Internal: Prepare parameters for sending POST to endpoint, merges defaults,
|
194
113
|
# local and per call options, adds message related informations etc etc.
|
195
114
|
#
|
@@ -198,7 +117,7 @@ module MoteSMS
|
|
198
117
|
#
|
199
118
|
# Returns Array with params.
|
200
119
|
def post_params(message, options)
|
201
|
-
params = options.reject { |key, v|
|
120
|
+
params = options.reject { |key, v| [:proxy_address, :proxy_port, :ssl].include?(key) }
|
202
121
|
params.merge! username: self.username,
|
203
122
|
password: self.password,
|
204
123
|
origin: message.from ? message.from.to_number : params[:origin],
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'phony'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require 'mote_sms/transports/http_client'
|
5
|
+
|
6
|
+
module MoteSMS
|
7
|
+
|
8
|
+
# MoteSMS::MobileTechnicsTransport provides the implementation to
|
9
|
+
# send messages using nth.ch bulk SMS HTTP/S API. Each customer has
|
10
|
+
# custom endpoint (with port) and username/password.
|
11
|
+
#
|
12
|
+
# Examples:
|
13
|
+
#
|
14
|
+
# transport = MoteSMS::SwisscomTransport.new 'https://api.swisscom.com/', 'ApIkEy'
|
15
|
+
# transport.deliver message
|
16
|
+
# # => ['000-791234', '001-7987324']
|
17
|
+
#
|
18
|
+
class SwisscomTransport
|
19
|
+
# Maximum recipients allowed by API
|
20
|
+
MAX_RECIPIENT = 1
|
21
|
+
|
22
|
+
# Custom exception subclass.
|
23
|
+
ServiceError = Class.new(::Exception)
|
24
|
+
|
25
|
+
# Readable attributes
|
26
|
+
|
27
|
+
# Public: Logger used to log HTTP requests to mobile
|
28
|
+
# technics API endpoint.
|
29
|
+
#
|
30
|
+
# Returns Logger instance.
|
31
|
+
def self.logger
|
32
|
+
@@logger ||= ::Logger.new($stdout)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Change the logger used to log all HTTP requests to
|
36
|
+
# the endpoint.
|
37
|
+
#
|
38
|
+
# logger - The Logger instance, should at least respond to #debug, #error.
|
39
|
+
#
|
40
|
+
# Returns nothing.
|
41
|
+
def self.logger=(logger)
|
42
|
+
@@logger = logger
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :endpoint, :api_key, :from_number, :options, :http_client
|
46
|
+
|
47
|
+
# Public: Create a new instance using specified endpoint, api_key
|
48
|
+
# and password.
|
49
|
+
#
|
50
|
+
# endpoint - The swisscom base url of the API
|
51
|
+
# api_key - The String with the API key.
|
52
|
+
# from_number - The phone number to send from (mandatory @ swisscom)
|
53
|
+
# options - The Hash with additional URL params passed to mobile techics endpoint
|
54
|
+
#
|
55
|
+
# Returns a new instance.
|
56
|
+
def initialize(endpoint, api_key, from_number = nil, options = {})
|
57
|
+
@endpoint = URI.parse(endpoint)
|
58
|
+
@api_key = api_key
|
59
|
+
@from_number = from_number
|
60
|
+
@options = options
|
61
|
+
|
62
|
+
@http_client = Transports::HttpClient.new(endpoint,
|
63
|
+
proxy_address: options[:proxy_address],
|
64
|
+
proxy_port: options[:proxy_port],
|
65
|
+
ssl: options[:ssl])
|
66
|
+
end
|
67
|
+
|
68
|
+
# Public: Delivers message using mobile technics HTTP/S API.
|
69
|
+
#
|
70
|
+
# message - The MoteSMS::Message instance to send.
|
71
|
+
# options - The Hash with service specific options.
|
72
|
+
#
|
73
|
+
# Returns Array with sender ids.
|
74
|
+
def deliver(message, options = {})
|
75
|
+
raise ArgumentError, "too many recipients, max. is #{MAX_RECIPIENT} (current: #{message.to.length})" if message.to.length > MAX_RECIPIENT
|
76
|
+
|
77
|
+
# Prepare request
|
78
|
+
request = Net::HTTP::Post.new("/messaging/v1/sms").tap do |request|
|
79
|
+
request.body = post_params(message)
|
80
|
+
request.content_type = 'application/json; charset=utf-8'
|
81
|
+
request['Accept'] = 'application/json; charset=utf-8'
|
82
|
+
request['client_id'] = api_key
|
83
|
+
end
|
84
|
+
|
85
|
+
# Log as `curl` request
|
86
|
+
self.class.logger.debug "curl -X#{request.method} '#{endpoint}' -d '#{request.body}'"
|
87
|
+
|
88
|
+
# Perform request
|
89
|
+
resp = http.request(request)
|
90
|
+
|
91
|
+
# Handle errors
|
92
|
+
raise ServiceError, "endpoint did respond with #{resp.code} and #{resp.body}" unless resp.code.to_i == 201
|
93
|
+
self.class.logger.debug resp.body
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def post_params(message)
|
99
|
+
{ to: prepare_numbers(message.to), text: message.body }
|
100
|
+
end
|
101
|
+
|
102
|
+
def prepare_numbers(number_list)
|
103
|
+
number_list.normalized_numbers.map { |n| Phony.formatted(n, format: :international_absolute, spaces: '') }.first
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/mote_sms/transports.rb
CHANGED
@@ -2,7 +2,9 @@ module MoteSMS
|
|
2
2
|
|
3
3
|
# All transports live within mote_sms/transports, though should be
|
4
4
|
# available in ruby as `MoteSMS::<Some>Transport`.
|
5
|
+
autoload :SslTransport, 'mote_sms/transports/concerns/ssl_transport'
|
5
6
|
autoload :TestTransport, 'mote_sms/transports/test_transport'
|
6
7
|
autoload :MobileTechnicsTransport, 'mote_sms/transports/mobile_technics_transport'
|
7
8
|
autoload :ActionMailerTransport, 'mote_sms/transports/action_mailer_transport'
|
9
|
+
autoload :SwisscomTransport, 'mote_sms/transports/swisscom_transport'
|
8
10
|
end
|
data/lib/mote_sms/version.rb
CHANGED
data/lib/mote_sms.rb
CHANGED
@@ -1,14 +1,33 @@
|
|
1
1
|
require 'mote_sms/transports'
|
2
2
|
|
3
3
|
module MoteSMS
|
4
|
-
autoload :Number,
|
5
|
-
autoload :NumberList,
|
6
|
-
autoload :Message,
|
4
|
+
autoload :Number, 'mote_sms/number'
|
5
|
+
autoload :NumberList, 'mote_sms/number_list'
|
6
|
+
autoload :Message, 'mote_sms/message'
|
7
|
+
autoload :DeliveryJob, 'mote_sms/delivery_job'
|
7
8
|
|
8
9
|
autoload :VERSION, 'mote_sms/version'
|
9
10
|
|
10
11
|
# No default transport.
|
11
12
|
@@transport = nil
|
13
|
+
@@delayed_delivery_queue = :default
|
14
|
+
|
15
|
+
# Public: Get globally defined queue name for ActiveJob, if any.
|
16
|
+
# Defaults to `nil`.
|
17
|
+
#
|
18
|
+
# Returns global ActiveJob queue name.
|
19
|
+
def self.delayed_delivery_queue
|
20
|
+
@@delayed_delivery_queue
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Set global queue name for ActiveJob
|
24
|
+
#
|
25
|
+
# queue - A string or symbol that represents the queue to use for ActiveJob
|
26
|
+
#
|
27
|
+
# Returns nothing.
|
28
|
+
def self.delayed_delivery_queue=(queue)
|
29
|
+
@@delayed_delivery_queue = queue
|
30
|
+
end
|
12
31
|
|
13
32
|
# Public: Get globally defined transport method, if any.
|
14
33
|
# Defaults to `nil`.
|
@@ -40,6 +59,6 @@ module MoteSMS
|
|
40
59
|
# Returns result of #deliver.
|
41
60
|
def self.deliver(&block)
|
42
61
|
raise ArgumentError, 'Block missing' unless block_given?
|
43
|
-
Message.new(&block).
|
62
|
+
Message.new(&block).deliver_now
|
44
63
|
end
|
45
64
|
end
|
data/mote_sms.gemspec
CHANGED
@@ -3,26 +3,29 @@ require File.expand_path('../lib/mote_sms/version', __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.name = 'mote_sms'
|
6
|
-
gem.authors = ['Lukas Westermann']
|
7
|
-
gem.email = ['lukas.westermann@at-point.ch']
|
8
|
-
gem.summary = %q{Deliver SMS using MobileTechnics
|
9
|
-
gem.description = %q{Unofficial ruby adapter for MobileTechnics
|
6
|
+
gem.authors = ['Lukas Westermann', 'Loris Gavillet']
|
7
|
+
gem.email = ['lukas.westermann@at-point.ch', 'loris@at-point.ch']
|
8
|
+
gem.summary = %q{Deliver SMS using Swisscom / MobileTechnics REST API.}
|
9
|
+
gem.description = %q{Unofficial ruby adapter for Swisscom and MobileTechnics Bulk SMS APIs.
|
10
10
|
Tries to mimick mail API, so users can switch e.g. ActionMailer
|
11
11
|
with this SMS provider.}
|
12
|
-
gem.homepage = 'https://at-point
|
12
|
+
gem.homepage = 'https://github.com/at-point/mote_sms'
|
13
13
|
|
14
14
|
gem.files = %w{.gitignore Gemfile Rakefile README.md mote_sms.gemspec} + Dir['**/*.{rb,pem}']
|
15
|
-
gem.
|
15
|
+
gem.bindir = 'exe'
|
16
|
+
gem.executables = gem.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
16
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
18
|
gem.require_paths = %w{lib}
|
18
19
|
gem.version = MoteSMS::VERSION
|
19
20
|
|
20
|
-
gem.required_ruby_version = '>=
|
21
|
+
gem.required_ruby_version = '>= 2.0'
|
21
22
|
|
22
23
|
gem.add_dependency 'phony', ['>= 1.7', '< 3.0']
|
24
|
+
gem.add_dependency 'activesupport', ['>= 4.2', '< 6']
|
23
25
|
|
24
26
|
gem.add_development_dependency 'rake'
|
25
|
-
gem.add_development_dependency 'rspec', ['~> 2.
|
27
|
+
gem.add_development_dependency 'rspec', ['~> 2.4']
|
26
28
|
gem.add_development_dependency 'webmock', ['~> 1.8.0']
|
27
|
-
gem.add_development_dependency 'actionmailer', ['>=
|
29
|
+
gem.add_development_dependency 'actionmailer', ['>= 4.2', '< 6']
|
30
|
+
gem.add_development_dependency 'activejob', ['>= 4.2', '< 6']
|
28
31
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mote_sms'
|
3
|
+
|
4
|
+
describe MoteSMS::DeliveryJob do
|
5
|
+
subject { described_class.new }
|
6
|
+
|
7
|
+
context '#perform' do
|
8
|
+
it 'creates a new message based on the params and delivers it' do
|
9
|
+
expect_any_instance_of(MoteSMS::Message).to receive(:deliver_now).with(d: 123)
|
10
|
+
subject.perform('SENDER', ['41791231212'], 'This is the SMS content', d: 123)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'mote_sms
|
2
|
+
require 'mote_sms'
|
3
|
+
require 'active_job'
|
3
4
|
|
4
5
|
describe MoteSMS::Message do
|
5
6
|
it 'can be constructed using a block' do
|
@@ -32,29 +33,71 @@ describe MoteSMS::Message do
|
|
32
33
|
end
|
33
34
|
|
34
35
|
context "#deliver" do
|
36
|
+
subject { described_class.new }
|
37
|
+
|
38
|
+
it "delegates to deliver_now and deprecates it" do
|
39
|
+
expect(subject).to receive(:deliver_now)
|
40
|
+
expect(Kernel).to receive(:warn).with('Message#deliver is deprecated and will be removed from MoteSMS. Please use #deliver_now')
|
41
|
+
subject.deliver
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "#deliver_now" do
|
35
46
|
let(:transport) { double("Some Transport") }
|
36
|
-
subject {
|
47
|
+
subject {
|
48
|
+
expect(Kernel).to receive(:warn).with('Message#new(transport) is deprecated and will be removed from MoteSMS')
|
49
|
+
described_class.new(transport)
|
50
|
+
}
|
37
51
|
|
38
52
|
it "sends messages to transport" do
|
39
53
|
expect(transport).to receive(:deliver).with(subject, {})
|
40
|
-
subject.
|
54
|
+
subject.deliver_now
|
41
55
|
end
|
42
56
|
|
43
57
|
it "can pass additional attributes to transport" do
|
44
58
|
expect(transport).to receive(:deliver).with(subject, serviceid: "myapplication")
|
45
|
-
subject.
|
59
|
+
subject.deliver_now serviceid: "myapplication"
|
46
60
|
end
|
47
61
|
|
48
|
-
it "can override per message transport using :transport option" do
|
62
|
+
it "can override per message transport using :transport option and it deprecates it" do
|
49
63
|
expect(transport).to_not receive(:deliver)
|
50
|
-
|
64
|
+
expect(Kernel).to receive(:warn).with('options[:transport] in Message#deliver_now is deprecated and will be removed from MoteSMS')
|
65
|
+
subject.deliver_now transport: double(deliver: true)
|
51
66
|
end
|
52
67
|
|
53
68
|
it "uses global MoteSMS.transport if no per message transport defined" do
|
54
69
|
message = described_class.new
|
55
70
|
expect(transport).to receive(:deliver).with(message, {})
|
56
71
|
expect(MoteSMS).to receive(:transport) { transport }
|
57
|
-
message.
|
72
|
+
message.deliver_now
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "#deliver_later" do
|
77
|
+
before { MoteSMS.transport = MoteSMS::TestTransport }
|
78
|
+
after { MoteSMS.transport = nil }
|
79
|
+
|
80
|
+
subject do
|
81
|
+
described_class.new do
|
82
|
+
from 'SENDER'
|
83
|
+
to '+41 79 123 12 12'
|
84
|
+
body 'This is the SMS content'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "can not override per message transport using :transport option and it deprecates it" do
|
89
|
+
expect(Kernel).to receive(:warn).with('options[:transport] is not supported in Message#deliveer_later')
|
90
|
+
expect(MoteSMS::DeliveryJob).to_not receive(:perform_later)
|
91
|
+
subject.deliver_later transport: double(deliver: true)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "queues the delivery in the DeliveryJob" do
|
95
|
+
subject.deliver_later
|
96
|
+
job = ActiveJob::Base.queue_adapter.enqueued_jobs.first
|
97
|
+
expect(job[:job]).to eq MoteSMS::DeliveryJob
|
98
|
+
expect(job[:args]).to include 'SENDER'
|
99
|
+
expect(job[:args]).to include ['41791231212']
|
100
|
+
expect(job[:args]).to include 'This is the SMS content'
|
58
101
|
end
|
59
102
|
end
|
60
103
|
end
|
@@ -2,36 +2,38 @@ require 'spec_helper'
|
|
2
2
|
require 'mote_sms/number'
|
3
3
|
|
4
4
|
describe MoteSMS::Number do
|
5
|
+
shared_examples_for 'parsed number' do |formatted, e164|
|
6
|
+
it '#to_s returns a formatted number' do
|
7
|
+
expect(subject.to_s).to eq formatted
|
8
|
+
end
|
9
|
+
|
10
|
+
it '#number returns an E164 number' do
|
11
|
+
expect(subject.number).to eq e164
|
12
|
+
end
|
13
|
+
|
14
|
+
it '#to_number returns an E164 number' do
|
15
|
+
expect(subject.to_number).to eq e164
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
5
19
|
context 'normalized number' do
|
6
20
|
subject { described_class.new('41443643533') }
|
7
|
-
|
8
|
-
its(:to_s) { should == '+41 44 364 35 33' }
|
9
|
-
its(:number) { should == '41443643533' }
|
10
|
-
its(:to_number) { should == '41443643533' }
|
21
|
+
it_behaves_like 'parsed number', '+41 44 364 35 33', '41443643533'
|
11
22
|
end
|
12
23
|
|
13
24
|
context 'E164 conforming number' do
|
14
25
|
subject { described_class.new('+41 44 3643533') }
|
15
|
-
|
16
|
-
its(:to_s) { should == '+41 44 364 35 33' }
|
17
|
-
its(:number) { should == '41443643533' }
|
18
|
-
its(:to_number) { should == '41443643533' }
|
26
|
+
it_behaves_like 'parsed number', '+41 44 364 35 33', '41443643533'
|
19
27
|
end
|
20
28
|
|
21
29
|
context 'handles local numbers' do
|
22
30
|
subject { described_class.new('079 700 50 90', cc: '41') }
|
23
|
-
|
24
|
-
its(:to_s) { should == '+41 79 700 50 90' }
|
25
|
-
its(:number) { should == '41797005090'}
|
26
|
-
its(:to_number) { should == '41797005090' }
|
31
|
+
it_behaves_like 'parsed number', '+41 79 700 50 90', '41797005090'
|
27
32
|
end
|
28
33
|
|
29
34
|
context 'handles numbers with NDC regexp' do
|
30
35
|
subject { described_class.new('079 700 50 90', cc: '41', ndc: /(44|79)/) }
|
31
|
-
|
32
|
-
its(:to_s) { should == '+41 79 700 50 90' }
|
33
|
-
its(:number) { should == '41797005090' }
|
34
|
-
its(:to_number) { should == '41797005090' }
|
36
|
+
it_behaves_like 'parsed number', '+41 79 700 50 90', '41797005090'
|
35
37
|
end
|
36
38
|
|
37
39
|
context 'non conforming number' do
|
@@ -50,11 +52,11 @@ describe MoteSMS::Number do
|
|
50
52
|
|
51
53
|
context 'vanity numbers' do
|
52
54
|
subject { described_class.new('0800-vanity', vanity: true) }
|
55
|
+
it_behaves_like 'parsed number', '0800VANITY', '0800VANITY'
|
53
56
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
its(:vanity?) { should be_truthy }
|
57
|
+
it 'is a #vanity? number' do
|
58
|
+
expect(subject.vanity?).to be_truthy
|
59
|
+
end
|
58
60
|
|
59
61
|
it 'raises error if more than 11 alpha numeric chars' do
|
60
62
|
expect { described_class.new('1234567890AB', vanity: true) }.to raise_error(ArgumentError, /invalid vanity/i)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mote_sms/transports/http_client'
|
3
|
+
|
4
|
+
describe Transports::HttpClient do
|
5
|
+
subject { described_class.new('https://example.org/') }
|
6
|
+
|
7
|
+
context 'Certificate checks', http: true do
|
8
|
+
context 'api.swisscom.com' do
|
9
|
+
subject { described_class.new('https://api.swisscom.com/', enable_fingerprint: true) }
|
10
|
+
|
11
|
+
it 'makes a "successful" request, i.e. no HTTPS issues' do
|
12
|
+
request = Net::HTTP::Get.new('/')
|
13
|
+
response = subject.request(request)
|
14
|
+
expect(response).to be_a Net::HTTPInternalServerError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'https://bulk.mobile-gw.com:9012' do
|
19
|
+
subject { described_class.new('https://bulk.mobile-gw.com:9012', enable_fingerprint: true) }
|
20
|
+
|
21
|
+
it 'makes a "successful" request, i.e. no HTTPS issues' do
|
22
|
+
request = Net::HTTP::Get.new('/')
|
23
|
+
response = subject.request(request)
|
24
|
+
expect(response).to be_a Net::HTTPOK
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context '#initialize' do
|
30
|
+
before { ENV.delete('MOTE_SMS_EXAMPLE_ORG_FINGERPRINT') }
|
31
|
+
|
32
|
+
it 'has a default user agent' do
|
33
|
+
expect(subject.user_agent).to eq "Ruby/mote_sms #{MoteSMS::VERSION}"
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'has no proxy by default' do
|
37
|
+
expect(subject.proxy_address).to be_nil
|
38
|
+
expect(subject.proxy_port).to be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'tries to load a fingerprint via hostname, when ENV is not set' do
|
42
|
+
expect(described_class).to receive(:fingerprint_host).with('example.org') { 'pem-fingerprint' }
|
43
|
+
expect(subject.fingerprint).to eq 'pem-fingerprint'
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'tries to use the ENV for a fingerprint lookup' do
|
47
|
+
ENV['MOTE_SMS_EXAMPLE_ORG_FINGERPRINT'] = 'env-fingerprint'
|
48
|
+
allow(described_class).to receive(:fingerprint_host) { 'pem-fingerprint' }
|
49
|
+
expect(subject.fingerprint).to eq 'env-fingerprint'
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'with enable_fingerprint: false' do
|
53
|
+
subject { described_class.new('https://example.org', enable_fingerprint: false) }
|
54
|
+
|
55
|
+
it 'can skip fingerprinting by setting enable_fingerprint: false' do
|
56
|
+
ENV['MOTE_SMS_EXAMPLE_ORG_FINGERPRINT'] = 'env-fingerprint'
|
57
|
+
allow(described_class).to receive(:fingerprint_host) { 'pem-fingerprint' }
|
58
|
+
expect(subject.fingerprint).to be_nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context '#https?' do
|
64
|
+
it 'returns true when it is a HTTPS url' do
|
65
|
+
expect(subject.https?).to be_truthy
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'returns false (of course) when it is just HTTP' do
|
69
|
+
expect(described_class.new('http://foo.example.org').https?).to be_falsey
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context '#request' do
|
74
|
+
let(:request) { Net::HTTP::Get.new('/') }
|
75
|
+
|
76
|
+
before {
|
77
|
+
stub_request(:get, "https://example.org/").
|
78
|
+
with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent' => 'Ruby/mote_sms 1.2.0' }).
|
79
|
+
to_return(status: 200, body: "", headers: {})
|
80
|
+
}
|
81
|
+
|
82
|
+
it 'submits a request and overrides the UA' do
|
83
|
+
response = subject.request(request)
|
84
|
+
expect(response).to be_a Net::HTTPOK
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|