elk 0.0.13 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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