elk 0.0.13 → 0.5.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.
@@ -1,8 +1,10 @@
1
+ require "time"
2
+
1
3
  module Elk
2
4
  # Used to send SMS through 46elks SMS-gateway
3
5
  class SMS
4
- attr_reader :from, :to, :message, :message_id, :created_at,
5
- :loaded_at, :direction, :status #:nodoc:
6
+ attr_reader :from, :to, :message, :message_id, :created_at,
7
+ :loaded_at, :direction, :status, :client #:nodoc:
6
8
 
7
9
  def initialize(parameters) #:nodoc:
8
10
  set_parameters(parameters)
@@ -12,71 +14,94 @@ module Elk
12
14
  @from = parameters[:from]
13
15
  @to = parameters[:to]
14
16
  @message = parameters[:message]
15
- @message_id = parameters[:id]
17
+ @message_id = parameters[:id]
16
18
  @created_at = Time.parse(parameters[:created]) if parameters[:created]
17
19
  @loaded_at = Time.now
18
20
  @direction = parameters[:direction]
19
21
  @status = parameters[:status]
22
+ @client = parameters.fetch(:client) { Elk.client }
20
23
  end
21
24
 
22
25
  # Reloads a SMS from server
23
26
  def reload
24
- response = Elk.get("/SMS/#{self.message_id}")
25
- self.set_parameters(Elk.parse_json(response.body))
27
+ response = @client.get("/SMS/#{self.message_id}")
28
+ self.set_parameters(Elk::Util.parse_json(response.body))
26
29
  response.code == 200
27
30
  end
28
31
 
29
32
  class << self
30
33
  include Elk::Util
31
-
34
+
32
35
  # Send SMS
33
36
  # Required parameters
34
37
  #
35
38
  # * :from - Either the one of the allocated numbers or arbitrary alphanumeric string of at most 11 characters
36
39
  # * :to - Any phone number capable of receiving SMS. Multiple numbers can be given as Array or comma separated String
37
40
  # * :message - Any UTF-8 text Splitting and joining multi-part SMS messages are automatically handled by the API
38
- #
41
+ #
39
42
  # Optional parameters
40
43
  # * :flash - if set to non-false value SMS is sent as a "Flash SMS"
44
+ # * :client - `Elk::Client` instance
45
+ # * :whendelivered - Callback URL that will receive a POST after delivery
41
46
  #
42
47
  def send(parameters)
43
48
  verify_parameters(parameters, [:from, :message, :to])
44
49
 
45
- parameters[:to] = Array(parameters[:to]).join(',')
50
+ client = parameters.fetch(:client) { Elk.client }
51
+
52
+ arguments = {}
53
+ arguments[:from] = parameters.fetch(:from)
54
+ arguments[:to] = Array(parameters.fetch(:to)).join(",")
55
+ arguments[:message] = parameters.fetch(:message)
46
56
 
47
- if parameters[:flash]
48
- parameters.delete(:flash)
49
- parameters[:flashsms] = 'yes'
57
+ if parameters.fetch(:flash) { false }
58
+ arguments[:flashsms] = "yes"
50
59
  end
51
60
 
52
- # Warn if the from string will be capped by the sms gateway
53
- if parameters[:from] && parameters[:from].match(/^(\w{11,})$/)
54
- warn "SMS 'from' value #{parameters[:from]} will be capped at 11 chars"
61
+ if parameters.key?(:whendelivered)
62
+ arguments[:whendelivered] = parameters.fetch(:whendelivered)
55
63
  end
56
64
 
57
- response = Elk.post('/SMS', parameters)
58
- parsed_response = Elk.parse_json(response.body)
59
-
60
- if multiple_recipients?(parameters[:to])
65
+ check_sender_limit(arguments[:from])
66
+
67
+ response = client.post("/SMS", arguments)
68
+ parsed_response = Elk::Util.parse_json(response.body)
69
+
70
+ if multiple_recipients?(arguments[:to])
71
+ parsed_response.each { |m| m[:client] = client }
61
72
  instantiate_multiple(parsed_response)
62
73
  else
74
+ parsed_response[:client] = client
63
75
  self.new(parsed_response)
64
76
  end
65
77
  end
66
78
 
67
79
  # Get outgoing and incomming messages. Limited by the API to 100 latest
68
- def all
69
- response = Elk.get('/SMS')
70
- instantiate_multiple(Elk.parse_json(response.body)[:data])
80
+ #
81
+ # Optional parameters
82
+ # * :client - Elk::Client instance
83
+ #
84
+ def all(parameters = {})
85
+ client = parameters.fetch(:client) { Elk.client }
86
+ response = client.get("/SMS")
87
+ messages = Elk::Util.parse_json(response.body).fetch(:data).each { |m| m[:client] = client }
88
+ instantiate_multiple(messages)
71
89
  end
72
90
 
73
91
  private
74
- def instantiate_multiple(multiple)
75
- multiple.collect { |n| self.new(n) }
92
+ def instantiate_multiple(messages)
93
+ messages.map { |message| self.new(message) }
76
94
  end
77
95
 
78
96
  def multiple_recipients?(to)
79
- to.split(',').length > 1
97
+ to.split(",").length > 1
98
+ end
99
+
100
+ # Warn if the from string will be capped by the sms gateway
101
+ def check_sender_limit(from)
102
+ if from.to_s.match(/^(\w{11,})$/)
103
+ warn "SMS 'from' value #{from} will be capped at 11 chars"
104
+ end
80
105
  end
81
106
  end
82
107
  end
@@ -1,10 +1,20 @@
1
+ require "json"
2
+
1
3
  module Elk
2
4
  module Util
3
5
  def verify_parameters(parameters, required_parameters)
4
6
  missing_parameters = (required_parameters - parameters.keys)
5
7
  unless missing_parameters.empty?
6
- raise Elk::MissingParameter, "Requires #{missing_parameters.collect {|s| ":#{s}"}.join(', ')} parameters"
7
- end
8
+ message = missing_parameters.map { |s| ":#{s}" }.join(', ')
9
+ raise Elk::MissingParameter, "Requires #{message} parameters"
10
+ end
11
+ end
12
+
13
+ # Wrapper around MultiJson.load, symbolize names
14
+ def self.parse_json(body)
15
+ JSON.parse(body, :symbolize_names => true)
16
+ rescue JSON::ParserError
17
+ raise BadResponse, "Can't parse JSON"
8
18
  end
9
19
  end
10
- end
20
+ end
@@ -1,3 +1,3 @@
1
1
  module Elk
2
- VERSION = '0.0.13'
2
+ VERSION = "0.5.0"
3
3
  end
@@ -5,4 +5,4 @@ Content-Type: application/json
5
5
  Connection: keep-alive
6
6
  Content-Length: 135
7
7
 
8
- [{"to": "+46704508449", "id": "sb326c7a214f9f4abc90a11bd36d6abc3"}, { "to": "+46704508449", "id": "s47a89d6cc51d8db395d45ae7e16e86b7"}]
8
+ [{"to": "+46704508448", "id": "sb326c7a214f9f4abc90a11bd36d6abc3"}, { "to": "+46704508449", "id": "s47a89d6cc51d8db395d45ae7e16e86b7"}]
@@ -3,6 +3,6 @@ Server: nginx/0.7.67
3
3
  Date: Tue, 05 Jul 2011 15:32:45 GMT
4
4
  Content-Type: application/json
5
5
  Connection: keep-alive
6
- Content-Length: 197
6
+ Content-Length: 245
7
7
 
8
- {"to": "+46704508449", "message": "Your order #171 has now been sent!", "from": "VeryVeryVeryVeryLongSenderName", "id": "scbc6667592e7245fa1abcdc74682f690", "created": "2011-07-05T15:32:45.296367"}
8
+ {"to": "+46704508449", "message": "Your order #171 has now been sent!", "from": "VeryVeryVeryVeryLongSenderName", "id": "scbc6667592e7245fa1abcdc74682f690", "created": "2011-07-05T15:32:45.296367", "direction": "outgoing", "status": "delivered"}
@@ -0,0 +1,191 @@
1
+ require "spec_helper"
2
+ require "elk"
3
+
4
+ describe Elk::Number do
5
+ before { configure_elk }
6
+ let(:url) { "https://USERNAME:PASSWORD@api.46elks.com/a1/Numbers" }
7
+
8
+ describe ".allocate" do
9
+ context "swedish sms number" do
10
+ let(:sms_url) { 'http://localhost/receive' }
11
+ let(:country) { 'se' }
12
+ let(:arguments) { { sms_url: sms_url, country: country } }
13
+
14
+ subject(:number) { described_class.allocate(arguments) }
15
+
16
+ before(:each) do
17
+ stub_request(:post, url).
18
+ with(body: arguments,
19
+ headers: post_headers).
20
+ to_return(fixture('allocates_a_number.txt'))
21
+ end
22
+
23
+ it { is_expected.to have_attributes(arguments) }
24
+
25
+ it "should be active" do
26
+ expect(number.status).to eq(:active)
27
+ end
28
+
29
+ it "should have a new swedish number" do
30
+ expect(number.number).to match(/\+46\d+/)
31
+ end
32
+
33
+ it "should have sms capabilities" do
34
+ expect(number.capabilities).to include(:sms)
35
+ end
36
+ end
37
+
38
+ context "without arguments" do
39
+ it 'should raise exception' do
40
+ expect {
41
+ described_class.allocate({})
42
+ }.to raise_error(Elk::MissingParameter)
43
+ end
44
+ end
45
+ end
46
+
47
+ describe ".all" do
48
+ context "with two allocated numbers" do
49
+ before(:each) do
50
+ stub_request(:get, url).
51
+ with(headers: get_headers).
52
+ to_return(fixture('gets_allocated_numbers.txt'))
53
+ end
54
+
55
+ subject(:numbers) { described_class.all }
56
+
57
+ it "should return two numbers" do
58
+ expect(numbers.size).to eq(2)
59
+ end
60
+
61
+ it "should have different number id:s" do
62
+ expect(numbers.first.number_id).to_not be eq(numbers.last.number_id)
63
+ end
64
+
65
+ it "should have different phone numbers" do
66
+ expect(numbers.first.number).to_not be eq(numbers.last.number)
67
+ end
68
+
69
+ context "first numbers sms_url" do
70
+ subject(:number) { numbers[0].sms_url }
71
+ it { is_expected.to eq('http://localhost/receive1') }
72
+ end
73
+
74
+ context "second numbers sms_url" do
75
+ subject(:number) { numbers[1].sms_url }
76
+ it { is_expected.to eq('http://localhost/receive2') }
77
+ end
78
+ end
79
+
80
+ context "with wrong password" do
81
+ let(:url) { "https://USERNAME:WRONG@api.46elks.com/a1/Numbers" }
82
+
83
+ before(:each) do
84
+ stub_request(:get, url).
85
+ with(headers: get_headers).
86
+ to_return(fixture('auth_error.txt'))
87
+
88
+ Elk.configure do |config|
89
+ config.username = 'USERNAME'
90
+ config.password = 'WRONG'
91
+ end
92
+ end
93
+
94
+ it 'should raise authentication error' do
95
+ expect {
96
+ described_class.all
97
+ }.to raise_error(Elk::AuthError)
98
+ end
99
+ end
100
+
101
+ context "when server is broken" do
102
+ before(:each) do
103
+ stub_request(:get, url).
104
+ with(headers: get_headers).
105
+ to_return(fixture('server_error.txt'))
106
+ end
107
+
108
+ it 'should raise server error' do
109
+ expect {
110
+ described_class.all
111
+ }.to raise_error(Elk::ServerError)
112
+ end
113
+ end
114
+ end
115
+
116
+ describe "#save" do
117
+ before(:each) do
118
+ stub_request(:get, url).
119
+ with(headers: get_headers).
120
+ to_return(fixture('gets_allocated_numbers.txt'))
121
+
122
+ stub_request(:post, "#{url}/nea19c8e291676fb7003fa1d63bba7899").
123
+ with(body: {"sms_url" => "http://otherhost/receive", "voice_start" => ""},
124
+ headers: post_headers).
125
+ to_return(fixture('updates_a_number.txt'))
126
+ end
127
+
128
+ subject(:number) { described_class.all.first }
129
+
130
+ it 'should update a number' do
131
+ number.country = 'no'
132
+ number.sms_url = 'http://otherhost/receive'
133
+
134
+ expect(number.save).to be(true)
135
+ end
136
+ end
137
+
138
+ describe "#deallocate!" do
139
+ before(:each) do
140
+ stub_request(:get, url).
141
+ with(headers: get_headers).
142
+ to_return(fixture('gets_allocated_numbers.txt'))
143
+
144
+ stub_request(:post, "#{url}/nea19c8e291676fb7003fa1d63bba7899").
145
+ with(body: {"active" => "no"},
146
+ headers: post_headers).
147
+ to_return(fixture('deallocates_a_number.txt'))
148
+ end
149
+
150
+ subject(:number) { described_class.all.first }
151
+
152
+ it "should return true" do
153
+ expect(number.deallocate!).to be(true)
154
+ end
155
+
156
+ it "should update loaded_at" do
157
+ expect { number.deallocate! }.to change { number.status }.to(:deallocated)
158
+ end
159
+ end
160
+
161
+ describe "#reload" do
162
+ before(:each) do
163
+ stub_request(:get, url).
164
+ with(headers: get_headers).
165
+ to_return(fixture('gets_allocated_numbers.txt'))
166
+
167
+ stub_request(:get, "#{url}/nea19c8e291676fb7003fa1d63bba7899").
168
+ with(headers: get_headers).
169
+ to_return(fixture('reloads_a_number.txt'))
170
+ end
171
+
172
+ subject(:number) { described_class.all.first }
173
+
174
+ it "should return true" do
175
+ expect(number.reload).to be(true)
176
+ end
177
+
178
+ it "should not change object_id" do
179
+ expect { number.reload }.to_not change { number.object_id }
180
+ end
181
+
182
+ it "should update loaded_at" do
183
+ expect { number.reload }.to change { number.loaded_at }
184
+ end
185
+
186
+ it "should reset mutations" do
187
+ number.country = "blah"
188
+ expect { number.reload }.to change { number.country }.to("se")
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,241 @@
1
+ require "spec_helper"
2
+ require "elk"
3
+
4
+ describe Elk::SMS do
5
+ before { configure_elk }
6
+ let(:url) { "https://USERNAME:PASSWORD@api.46elks.com/a1/SMS" }
7
+
8
+ describe ".send" do
9
+ let(:from) { "+46761042247" }
10
+ let(:to) { "+46704508449" }
11
+ let(:message) { "Your order #171 has now been sent!" }
12
+
13
+ context "ordinary SMS" do
14
+ before(:each) do
15
+ stub_request(:post, url).
16
+ with(body: { from: from, message: message, to: to }, headers: post_headers).
17
+ to_return(fixture('sends_a_sms.txt'))
18
+ end
19
+
20
+ subject(:sms) { described_class.send(from: from, to: to, message: message) }
21
+
22
+ it "should not create warnings" do
23
+ expect { sms }.to_not output.to_stderr
24
+ end
25
+
26
+ describe "#from" do
27
+ subject { sms.from }
28
+ it { is_expected.to eq(from) }
29
+ end
30
+
31
+ describe "#to" do
32
+ subject { sms.to }
33
+ it { is_expected.to eq(to) }
34
+ end
35
+
36
+ describe "#message" do
37
+ subject { sms.message }
38
+ it { is_expected.to eq(message) }
39
+ end
40
+
41
+ describe "#direction" do
42
+ subject { sms.direction }
43
+ it { is_expected.to eq("outgoing") }
44
+ end
45
+
46
+ describe "#status" do
47
+ subject { sms.status }
48
+ it { is_expected.to eq("delivered") }
49
+ end
50
+ end
51
+
52
+ context "flash SMS" do
53
+ before(:each) do
54
+ @stub = stub_request(:post, url).
55
+ with(body: { from: from, message: message, to: to, flashsms: "yes" },
56
+ headers: post_headers).
57
+ to_return(fixture('sends_a_sms.txt'))
58
+ end
59
+
60
+ it "should send flash SMS through API" do
61
+ described_class.send(from: from, to: to, message: message, flash: true)
62
+
63
+ expect(@stub).to have_been_requested
64
+ end
65
+ end
66
+
67
+ context 'multiple recipients' do
68
+ before do
69
+ stub_request(:post, url).
70
+ with(body: { from: from, message: message, to: to.join(",") },
71
+ headers: post_headers).
72
+ to_return(fixture('sends_a_sms_to_multiple_recipients.txt'))
73
+ end
74
+
75
+ let(:to) { ["+46704508448", "+46704508449"] }
76
+
77
+ subject(:messages) { described_class.send(from: from, to: to, message: message) }
78
+
79
+ it "should create two messages" do
80
+ expect(messages.size).to eq(2)
81
+ end
82
+
83
+ context "first sms" do
84
+ subject(:sms) { messages[0] }
85
+
86
+ it "should contain first message" do
87
+ expect(sms.to).to eq(to[0])
88
+ expect(sms.message_id).to eq("sb326c7a214f9f4abc90a11bd36d6abc3")
89
+ end
90
+ end
91
+
92
+ context "second sms" do
93
+ subject(:sms) { messages[1] }
94
+
95
+ it "should contain second message" do
96
+ expect(sms.to).to eq(to[1])
97
+ expect(sms.message_id).to eq("s47a89d6cc51d8db395d45ae7e16e86b7")
98
+ end
99
+ end
100
+
101
+ context "with recipients as array and comma separated string" do
102
+ subject(:as_comma) { described_class.send(from: from, to: to.join(","), message: message) }
103
+ subject(:as_array) { described_class.send(from: from, to: to, message: message) }
104
+
105
+ it "response should send the same messages" do
106
+ expect(as_comma.map(&:message_id)).to eq(as_array.map(&:message_id))
107
+ end
108
+ end
109
+ end
110
+
111
+ context "with too long sender" do
112
+ before(:each) do
113
+ stub_request(:post, url).
114
+ with(body: { from: from, message: message, to: to }, headers: post_headers).
115
+ to_return(fixture('sends_a_sms_with_long_sender.txt'))
116
+ end
117
+
118
+ subject(:sms) { described_class.send(from: from, to: to, message: message) }
119
+
120
+ let(:from) { "VeryVeryVeryVeryLongSenderName" }
121
+
122
+ it "should create warning" do
123
+ expect { sms }.to output("SMS 'from' value #{from} will be capped at 11 chars\n").to_stderr
124
+ end
125
+
126
+ describe "#from" do
127
+ subject { sms.from }
128
+ it { is_expected.to eq(from) }
129
+ end
130
+
131
+ describe "#to" do
132
+ subject { sms.to }
133
+ it { is_expected.to eq(to) }
134
+ end
135
+
136
+ describe "#message" do
137
+ subject { sms.message }
138
+ it { is_expected.to eq(message) }
139
+ end
140
+
141
+ describe "#direction" do
142
+ subject { sms.direction }
143
+ it { is_expected.to eq("outgoing") }
144
+ end
145
+
146
+ describe "#status" do
147
+ subject { sms.status }
148
+ it { is_expected.to eq("delivered") }
149
+ end
150
+ end
151
+
152
+ context "with invalid number" do
153
+ before(:each) do
154
+ stub_request(:post, url).
155
+ with(body: { from: from, message: message, to: to }, headers: post_headers).
156
+ to_return(fixture('invalid_to_number.txt'))
157
+ end
158
+
159
+ let(:from) { "+46761042247" }
160
+ let(:to) { "monkey" }
161
+
162
+ it 'should handle invalid to number' do
163
+ expect {
164
+ described_class.send(from: from, to: to, message: message)
165
+ }.to raise_error(Elk::BadRequest, "Invalid to number")
166
+ end
167
+ end
168
+
169
+ context "without parameters" do
170
+ it 'should handle no parameters' do
171
+ expect {
172
+ described_class.send({})
173
+ }.to raise_error(Elk::MissingParameter)
174
+ end
175
+ end
176
+ end
177
+
178
+ describe ".all" do
179
+ before(:each) do
180
+ stub_request(:get, url).
181
+ with(headers: get_headers).
182
+ to_return(fixture('sms_history.txt'))
183
+ end
184
+
185
+ subject(:history) { described_class.all }
186
+
187
+ it "should have return all messages" do
188
+ expect(history.size).to eq(3)
189
+ end
190
+
191
+ context "first item" do
192
+ subject(:sms) { history[0] }
193
+
194
+ it "should contain the correct message" do
195
+ expect(sms.message).to eq("Your order #171 has now been sent!")
196
+ end
197
+ end
198
+
199
+ context "second item" do
200
+ subject(:sms) { history[1] }
201
+
202
+ it "should contain the correct message" do
203
+ expect(sms.message).to eq("I'd like to order a pair of elks!")
204
+ end
205
+ end
206
+
207
+ context "third item" do
208
+ subject(:sms) { history[2] }
209
+
210
+ it "should contain the correct message" do
211
+ expect(sms.message).to eq("Want an elk?")
212
+ end
213
+ end
214
+ end
215
+
216
+ describe "#reload" do
217
+ before(:each) do
218
+ stub_request(:get, url).
219
+ with(headers: get_headers).
220
+ to_return(fixture('sms_history.txt'))
221
+
222
+ stub_request(:get, "https://USERNAME:PASSWORD@api.46elks.com/a1/SMS/s8952031bb83bf3e64f8e13b071c131c0").
223
+ with(headers: get_headers).
224
+ to_return(fixture('reloads_a_sms.txt'))
225
+ end
226
+
227
+ subject(:sms) { described_class.all.first }
228
+
229
+ it "should return true" do
230
+ expect(sms.reload).to be(true)
231
+ end
232
+
233
+ it "should not change object_id" do
234
+ expect { sms.reload }.to_not change { sms.object_id }
235
+ end
236
+
237
+ it "should update loaded_at" do
238
+ expect { sms.reload }.to change { sms.loaded_at }
239
+ end
240
+ end
241
+ end