twilio_contactable 0.7.1
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/MIT-LICENSE +20 -0
- data/README.markdown +144 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/init.rb +3 -0
- data/lib/configuration.rb +41 -0
- data/lib/contactable.rb +204 -0
- data/lib/controller.rb +78 -0
- data/lib/gateway.rb +105 -0
- data/lib/twilio_contactable.rb +40 -0
- data/test/.gitignore +1 -0
- data/test/database.yml +18 -0
- data/test/test_helper.rb +63 -0
- data/test/twilio_contactable_contactable_test.rb +308 -0
- data/test/twilio_contactable_controller_test.rb +97 -0
- data/test/twilio_module_test.rb +56 -0
- data/twilio_contactable.gemspec +68 -0
- metadata +118 -0
data/lib/controller.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module TwilioContactable
|
2
|
+
module Controller
|
3
|
+
|
4
|
+
def self.included(controller)
|
5
|
+
controller.instance_eval do
|
6
|
+
# the developer should specify which model(s) will be sought
|
7
|
+
# when the app receives incoming requests
|
8
|
+
protected
|
9
|
+
def twilio_contactable(*klasses)
|
10
|
+
@@contactable_classes = klasses
|
11
|
+
klasses.each do |klass|
|
12
|
+
klass.twilio_contactable.controller = self.class.name.underscore.chomp('_controller')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def receive_sms_message
|
19
|
+
unless defined?(@@contactable_classes) && !@@contactable_classes.blank?
|
20
|
+
raise RuntimeError, "Please define which model(s) this controller should receive for via the 'twilio_contactable' method"
|
21
|
+
end
|
22
|
+
|
23
|
+
request = params[:request]
|
24
|
+
render :text => 'unknown format', :status => 500 and return unless request
|
25
|
+
case request['type']
|
26
|
+
when 'BLOCK'
|
27
|
+
@contactable = find_contactable_by_phone_number(request[:block][:recipient][:id])
|
28
|
+
@contactable._TC_sms_blocked = true
|
29
|
+
@contactable.save!
|
30
|
+
when 'MESSAGE'
|
31
|
+
@contactable = find_contactable_by_phone_number(request[:message][:sender][:id])
|
32
|
+
if @contactable.respond_to?(:receive_sms)
|
33
|
+
@contactable.receive_sms(request[:message][:text])
|
34
|
+
else
|
35
|
+
warn "An SMS message was received but #{@contactable.class.name.inspect} doesn't have a receive_sms method!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
render :text => 'OK', :status => 200
|
39
|
+
end
|
40
|
+
|
41
|
+
def start_voice_confirmation
|
42
|
+
render :xml => (Twilio::Response.new.tap do |response|
|
43
|
+
response.addGather(
|
44
|
+
:action => url_for(
|
45
|
+
:action => 'receive_voice_confirmation',
|
46
|
+
:contactable_type => params[:contactable_type],
|
47
|
+
:contactable_id => params[:contactable_id]
|
48
|
+
)
|
49
|
+
).tap do |gather|
|
50
|
+
gather.addSay "Please type the numbers that appear on your screen, followed by the pound sign"
|
51
|
+
end
|
52
|
+
end.respond)
|
53
|
+
end
|
54
|
+
|
55
|
+
def receive_voice_confirmation
|
56
|
+
@contactable = params[:contactable_type].constantize.find(params[:contactable_id])
|
57
|
+
@contactable.voice_confirm_with(params['Digits'])
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def find_contactable_by_phone_number(number)
|
63
|
+
[number, number.sub(/^\+/,''), number.sub(/^\+1/,'')].uniq.compact.each do |possible_phone_number|
|
64
|
+
@@contactable_classes.each do |klass|
|
65
|
+
found = klass.find(
|
66
|
+
:first,
|
67
|
+
:conditions =>
|
68
|
+
{ klass.twilio_contactable.formatted_phone_number_column => possible_phone_number }
|
69
|
+
)
|
70
|
+
return found if found
|
71
|
+
end
|
72
|
+
end
|
73
|
+
nil
|
74
|
+
# rescue => error
|
75
|
+
# render :text => error.inspect
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/gateway.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
module TwilioContactable
|
2
|
+
module Gateway
|
3
|
+
class WebsiteAddressNotConfiguredError < ArgumentError
|
4
|
+
def initialize
|
5
|
+
super "Please define your `website_address` config parameter "+
|
6
|
+
"in the TwilioContactable initializer. "+
|
7
|
+
"It should be in the format of: \"http://example.com/\""
|
8
|
+
|
9
|
+
end
|
10
|
+
end
|
11
|
+
class ControllerNotConfiguredError < ArgumentError
|
12
|
+
def initialize(record)
|
13
|
+
super "You're initiating a voice call for a "+
|
14
|
+
record.class.name.inspect +
|
15
|
+
" but you have not specified this class "+
|
16
|
+
"as a possible recipient in a controller."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
class Response
|
20
|
+
def initialize(*args)
|
21
|
+
@options = args.last
|
22
|
+
end
|
23
|
+
|
24
|
+
def success?
|
25
|
+
:success == @options[:status]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
Success = Response.new(:status => :success)
|
29
|
+
Error = Response.new(:status => :error)
|
30
|
+
|
31
|
+
API_VERSION = '2010-04-01'
|
32
|
+
|
33
|
+
class << self
|
34
|
+
|
35
|
+
def initiate_voice_call(record, to, from = nil)
|
36
|
+
|
37
|
+
unless TwilioContactable.configuration.website_address
|
38
|
+
raise WebsiteAddressNotConfiguredError.new
|
39
|
+
end
|
40
|
+
unless controller = record.class.twilio_contactable.controller
|
41
|
+
raise ControllerNotConfiguredError.new(record)
|
42
|
+
end
|
43
|
+
|
44
|
+
url = TwilioContactable.configuration.website_address.chomp('/')
|
45
|
+
url = "#{url}/#{record.class.twilio_contactable.controller}"
|
46
|
+
url = "#{url}/start_voice_confirmation?contactable_type=#{record.class}&contactable_id=#{record.id}"
|
47
|
+
|
48
|
+
deliver :voice,
|
49
|
+
'To' => to,
|
50
|
+
'From' => from,
|
51
|
+
'Url' => url
|
52
|
+
end
|
53
|
+
|
54
|
+
def deliver_sms(message, to, from = nil)
|
55
|
+
deliver :sms,
|
56
|
+
'Message' => message,
|
57
|
+
'To' => to,
|
58
|
+
'From' => from
|
59
|
+
end
|
60
|
+
|
61
|
+
def account
|
62
|
+
@account ||= begin
|
63
|
+
if TwilioContactable.configuration.client_id.blank? ||
|
64
|
+
TwilioContactable.configuration.client_key.blank?
|
65
|
+
raise "Add your Twilio account id (as client_id) and token (as client_key) to the TwilioContactable.configure block"
|
66
|
+
end
|
67
|
+
Twilio::RestAccount.new(
|
68
|
+
TwilioContactable.configuration.client_id,
|
69
|
+
TwilioContactable.configuration.client_key
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def deliver(type, data = {})
|
77
|
+
|
78
|
+
data['From'] = TwilioContactable.configuration.default_from_phone_number if data['From'].blank?
|
79
|
+
raise "'From' number required for Twilio" unless data['From']
|
80
|
+
|
81
|
+
service = case type
|
82
|
+
when :sms
|
83
|
+
'SMS/Messages'
|
84
|
+
when :voice
|
85
|
+
'Calls'
|
86
|
+
end
|
87
|
+
|
88
|
+
response = post service, data
|
89
|
+
|
90
|
+
Net::HTTPCreated == response.code_type ?
|
91
|
+
TwilioContactable::Gateway::Success :
|
92
|
+
TwilioContactable::Gateway::Error
|
93
|
+
end
|
94
|
+
|
95
|
+
def post(service, data = {})
|
96
|
+
url = "/#{API_VERSION}/Accounts/#{TwilioContactable.configuration.client_id}/#{service}"
|
97
|
+
puts "putting data: #{data.inspect}"
|
98
|
+
puts "to url: #{url}"
|
99
|
+
account.request url,
|
100
|
+
"POST",
|
101
|
+
data
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module TwilioContactable
|
2
|
+
class << self
|
3
|
+
def numerize(numberish)
|
4
|
+
numberish.to_s.scan(/\d+/).join
|
5
|
+
end
|
6
|
+
|
7
|
+
def internationalize(given_number)
|
8
|
+
number = numerize(given_number)
|
9
|
+
case number.size
|
10
|
+
when 10
|
11
|
+
"+1#{number}"
|
12
|
+
when 11
|
13
|
+
"+#{number}"
|
14
|
+
when 12
|
15
|
+
number =~ /\+\d(11)/ ? number : nil
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def confirmation_message(confirmation_code)
|
22
|
+
"Code: #{confirmation_code} Enter code on web to verify phone. Msg&data rates may apply. Freq set by u. T&C & support on web site. Txt HELP for help"
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_confirmation_code
|
26
|
+
chars = (0..9).to_a + ('A'..'Z').to_a
|
27
|
+
(0...6).collect { chars[Kernel.rand(chars.length)] }.join
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
gem 'twiliolib'
|
33
|
+
require 'twiliolib'
|
34
|
+
|
35
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'configuration'))
|
36
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'gateway'))
|
37
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'contactable'))
|
38
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'controller'))
|
39
|
+
|
40
|
+
ActiveRecord::Base.send :include, TwilioContactable::Contactable
|
data/test/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
debug.log
|
data/test/database.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
sqlite:
|
2
|
+
:adapter: sqlite
|
3
|
+
:database: plugin.sqlite.db
|
4
|
+
sqlite3:
|
5
|
+
:adapter: sqlite3
|
6
|
+
:database: ":memory:"
|
7
|
+
postgresql:
|
8
|
+
:adapter: postgresql
|
9
|
+
:username: postgres
|
10
|
+
:password: postgres
|
11
|
+
:database: plugin_test
|
12
|
+
:min_messages: ERROR
|
13
|
+
mysql:
|
14
|
+
:adapter: mysql
|
15
|
+
:host: localhost
|
16
|
+
:username: rails
|
17
|
+
:password:
|
18
|
+
:database: plugin_test
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'test-unit'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'mocha'
|
5
|
+
gem 'activesupport', '< 3.0.0'
|
6
|
+
require 'active_support'
|
7
|
+
require 'active_support/test_case'
|
8
|
+
gem 'activerecord', '< 3.0.0'
|
9
|
+
require 'active_record'
|
10
|
+
gem 'actionpack', '< 3.0.0'
|
11
|
+
require 'actionpack'
|
12
|
+
require 'action_controller'
|
13
|
+
require 'shoulda'
|
14
|
+
require 'shoulda/action_controller'
|
15
|
+
require 'shoulda/action_controller/macros'
|
16
|
+
require 'shoulda/action_controller/matchers'
|
17
|
+
require File.join(File.dirname(__FILE__), "..", 'lib', 'twilio_contactable')
|
18
|
+
|
19
|
+
# default test configuration
|
20
|
+
TwilioContactable.configure do |config|
|
21
|
+
config.client_id = '1'
|
22
|
+
config.client_key = 'ABCDEF'
|
23
|
+
config.website_address = 'http://example.com'
|
24
|
+
config.default_from_phone_number = '1 (206) 867-5309'
|
25
|
+
end
|
26
|
+
|
27
|
+
TwilioContactable.mode = :test
|
28
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
29
|
+
require 'logger'
|
30
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
31
|
+
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3'])
|
32
|
+
|
33
|
+
ActiveRecord::Schema.define(:version => 1) do
|
34
|
+
create_table :users do |t|
|
35
|
+
t.column :phone_number, :string
|
36
|
+
t.column :formatted_phone_number, :string
|
37
|
+
t.column :sms_blocked, :boolean
|
38
|
+
t.column :sms_confirmation_code, :string
|
39
|
+
t.column :sms_confirmation_attempted, :datetime
|
40
|
+
t.column :sms_confirmed_phone_number, :string
|
41
|
+
t.column :voice_blocked, :boolean
|
42
|
+
t.column :voice_confirmation_code, :string
|
43
|
+
t.column :voice_confirmation_attempted, :datetime
|
44
|
+
t.column :voice_confirmed_phone_number, :string
|
45
|
+
|
46
|
+
t.column :custom_column, :string
|
47
|
+
|
48
|
+
t.column :type, :string
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class User < ActiveRecord::Base
|
53
|
+
include TwilioContactable::Contactable
|
54
|
+
end
|
55
|
+
|
56
|
+
# kill all network access
|
57
|
+
module TwilioContactable
|
58
|
+
module Gateway
|
59
|
+
def post
|
60
|
+
raise "You forgot to stub out your net requests!"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class TwilioContactableContactableTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
Success = TwilioContactable::Gateway::Response.new(:status => :success)
|
6
|
+
Error = TwilioContactable::Gateway::Response.new(:status => :error)
|
7
|
+
|
8
|
+
context "configuration" do
|
9
|
+
TwilioContactable::Contactable::Attributes.each do |attr|
|
10
|
+
context "attribute: #{attr}" do
|
11
|
+
should "begin with appropriate default for #{attr}_column and allow overwriting" do
|
12
|
+
klass = Class.new
|
13
|
+
klass.send :include, TwilioContactable::Contactable
|
14
|
+
assert_equal attr, klass.twilio_contactable.send("#{attr}_column")
|
15
|
+
klass.twilio_contactable do |config|
|
16
|
+
config.send("#{attr}_column=", :custom_column)
|
17
|
+
end
|
18
|
+
assert_equal :custom_column, klass.twilio_contactable.send("#{attr}_column")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "contactable instance" do
|
25
|
+
setup {
|
26
|
+
User.delete_all
|
27
|
+
@user = User.create! :phone_number => '(555) 123-4567'
|
28
|
+
}
|
29
|
+
|
30
|
+
should "normalize phone number" do
|
31
|
+
assert_equal '+15551234567', @user.formatted_phone_number
|
32
|
+
end
|
33
|
+
context "when phone number is blank" do
|
34
|
+
setup { @user._TC_phone_number = nil}
|
35
|
+
context "confirming phone number" do
|
36
|
+
setup { @user.send_sms_confirmation! }
|
37
|
+
should_not_change "any attributes" do
|
38
|
+
@user.attributes.inspect
|
39
|
+
end
|
40
|
+
end
|
41
|
+
context "sending message" do
|
42
|
+
setup {
|
43
|
+
TwilioContactable::Gateway.stubs(:perform).returns(Success)
|
44
|
+
@worked = @user.send_sms!('message')
|
45
|
+
}
|
46
|
+
should "not work" do assert !@worked end
|
47
|
+
should_not_change "any attributes" do
|
48
|
+
@user.attributes.inspect
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when phone number exists" do
|
54
|
+
setup { @user._TC_phone_number = "206-555-5555"}
|
55
|
+
context "confirming phone number" do
|
56
|
+
context "via sms" do
|
57
|
+
setup {
|
58
|
+
TwilioContactable::Gateway.stubs(:deliver).returns(Success)
|
59
|
+
@worked = @user.send_sms_confirmation!
|
60
|
+
}
|
61
|
+
should "work" do assert @worked end
|
62
|
+
should "save confirmation number in proper attribute" do
|
63
|
+
assert @user._TC_sms_confirmation_code
|
64
|
+
end
|
65
|
+
should "set confirmation attempted time" do
|
66
|
+
assert @user._TC_sms_confirmation_attempted > 3.minutes.ago
|
67
|
+
end
|
68
|
+
should_change "stored code" do
|
69
|
+
@user._TC_sms_confirmation_code
|
70
|
+
end
|
71
|
+
should "not have number confirmed yet" do
|
72
|
+
assert !@user.sms_confirmed?
|
73
|
+
end
|
74
|
+
context "calling sms_confirm_with(right_code)" do
|
75
|
+
setup { @user.sms_confirm_with(@user._TC_sms_confirmation_code) }
|
76
|
+
should "work" do
|
77
|
+
assert @worked
|
78
|
+
end
|
79
|
+
should "save the phone number into the confirmed attribute" do
|
80
|
+
assert_equal @user._TC_phone_number,
|
81
|
+
@user._TC_sms_confirmed_phone_number,
|
82
|
+
@user.reload.inspect
|
83
|
+
end
|
84
|
+
should_change "confirmed phone number attribute" do
|
85
|
+
@user._TC_sms_confirmed_phone_number
|
86
|
+
end
|
87
|
+
context "and then attempting to confirm another number" do
|
88
|
+
setup {
|
89
|
+
@user._TC_phone_number = "206-555-8990"
|
90
|
+
TwilioContactable::Gateway.stubs(:deliver).returns(Success).once
|
91
|
+
@user.send_sms_confirmation!
|
92
|
+
}
|
93
|
+
should "eliminate the previous confirmed phone number" do
|
94
|
+
assert @user._TC_sms_confirmed_phone_number.blank?
|
95
|
+
end
|
96
|
+
should "un-confirm the record" do
|
97
|
+
assert !@user.sms_confirmed?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
context "calling sms_confirm_with(right code, wrong case)" do
|
102
|
+
setup {
|
103
|
+
@downcased_code = @user._TC_sms_confirmation_code.downcase
|
104
|
+
@worked = @user.sms_confirm_with(@downcased_code)
|
105
|
+
}
|
106
|
+
should "have good test data" do
|
107
|
+
assert_not_equal @downcased_code,
|
108
|
+
@user._TC_sms_confirmation_code
|
109
|
+
end
|
110
|
+
should "work" do
|
111
|
+
assert @worked
|
112
|
+
end
|
113
|
+
should "save the phone number into the confirmed attribute" do
|
114
|
+
assert_equal @user._TC_sms_confirmed_phone_number,
|
115
|
+
@user._TC_phone_number
|
116
|
+
end
|
117
|
+
end
|
118
|
+
context "calling sms_confirm_with(wrong_code)" do
|
119
|
+
setup { @worked = @user.sms_confirm_with('wrong_code') }
|
120
|
+
should "not work" do
|
121
|
+
assert !@worked
|
122
|
+
end
|
123
|
+
should "not save the phone number into the confirmed attribute" do
|
124
|
+
assert_not_equal @user._TC_sms_confirmed_phone_number,
|
125
|
+
@user._TC_phone_number
|
126
|
+
end
|
127
|
+
should_not_change "confirmed phone number attribute" do
|
128
|
+
@user.reload._TC_sms_confirmed_phone_number
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
context "confirming phone number with a custom short code" do
|
133
|
+
context "with expectations" do
|
134
|
+
setup {
|
135
|
+
message = "long message blah blah MYCODE blah"
|
136
|
+
TwilioContactable.expects(:generate_confirmation_code).returns('MYCODE').once
|
137
|
+
TwilioContactable.expects(:confirmation_message).returns(message).once
|
138
|
+
TwilioContactable::Gateway.expects(:deliver).with(message, @user._TC_phone_number).once
|
139
|
+
@user.send_sms_confirmation!
|
140
|
+
}
|
141
|
+
end
|
142
|
+
context "(normal)" do
|
143
|
+
setup {
|
144
|
+
TwilioContactable::Gateway.stubs(:deliver).returns(Success)
|
145
|
+
@worked = @user.send_sms_confirmation!
|
146
|
+
}
|
147
|
+
should "work" do
|
148
|
+
assert @worked
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
context "confirming phone number when the confirmation fails for some reason" do
|
153
|
+
setup {
|
154
|
+
TwilioContactable::Gateway.stubs(:deliver).returns(Error)
|
155
|
+
@worked = @user.send_sms_confirmation!
|
156
|
+
}
|
157
|
+
should "not work" do assert !@worked end
|
158
|
+
should "not save confirmation number" do
|
159
|
+
assert @user._TC_sms_confirmation_code.blank?
|
160
|
+
end
|
161
|
+
should_not_change "stored code" do
|
162
|
+
@user._TC_sms_confirmation_code
|
163
|
+
end
|
164
|
+
end
|
165
|
+
context "via voice" do
|
166
|
+
setup {
|
167
|
+
TwilioContactable::Gateway.stubs(:deliver).returns(Success)
|
168
|
+
# To start any voice call we'll need to first initialize
|
169
|
+
# this model in a controller
|
170
|
+
class TestController < ActionController::Base
|
171
|
+
include TwilioContactable::Controller
|
172
|
+
twilio_contactable User
|
173
|
+
self
|
174
|
+
end
|
175
|
+
@worked = @user.send_voice_confirmation!
|
176
|
+
}
|
177
|
+
should "work" do assert @worked end
|
178
|
+
should "save confirmation number in proper attribute" do
|
179
|
+
assert @user._TC_voice_confirmation_code
|
180
|
+
end
|
181
|
+
should "set confirmation attempted time" do
|
182
|
+
assert @user._TC_voice_confirmation_attempted > 3.minutes.ago
|
183
|
+
end
|
184
|
+
should_change "stored code" do
|
185
|
+
@user._TC_voice_confirmation_code
|
186
|
+
end
|
187
|
+
should "not have number confirmed yet" do
|
188
|
+
assert !@user.voice_confirmed?
|
189
|
+
end
|
190
|
+
context "calling voice_confirm_with(right_code)" do
|
191
|
+
setup { @user.voice_confirm_with(@user._TC_voice_confirmation_code) }
|
192
|
+
should "work" do
|
193
|
+
assert @worked
|
194
|
+
end
|
195
|
+
should "save the phone number into the confirmed attribute" do
|
196
|
+
assert_equal @user._TC_phone_number,
|
197
|
+
@user._TC_voice_confirmed_phone_number,
|
198
|
+
@user.reload.inspect
|
199
|
+
end
|
200
|
+
should_change "confirmed phone number attribute" do
|
201
|
+
@user._TC_voice_confirmed_phone_number
|
202
|
+
end
|
203
|
+
context "and then attempting to confirm another number" do
|
204
|
+
setup {
|
205
|
+
@user._TC_phone_number = "206-555-8990"
|
206
|
+
TwilioContactable::Gateway.stubs(:deliver).returns(Success).once
|
207
|
+
@user.send_voice_confirmation!
|
208
|
+
}
|
209
|
+
should "eliminate the previous confirmed phone number" do
|
210
|
+
assert @user._TC_voice_confirmed_phone_number.blank?
|
211
|
+
end
|
212
|
+
should "un-confirm the record" do
|
213
|
+
assert !@user.voice_confirmed?
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
context "calling voice_confirm_with(right code, wrong case)" do
|
218
|
+
setup {
|
219
|
+
@downcased_code = @user._TC_voice_confirmation_code.downcase
|
220
|
+
@worked = @user.voice_confirm_with(@downcased_code)
|
221
|
+
}
|
222
|
+
should "have good test data" do
|
223
|
+
assert_not_equal @downcased_code,
|
224
|
+
@user._TC_voice_confirmation_code
|
225
|
+
end
|
226
|
+
should "work" do
|
227
|
+
assert @worked
|
228
|
+
end
|
229
|
+
should "save the phone number into the confirmed attribute" do
|
230
|
+
assert_equal @user._TC_voice_confirmed_phone_number,
|
231
|
+
@user._TC_phone_number
|
232
|
+
end
|
233
|
+
end
|
234
|
+
context "calling voice_confirm_with(wrong_code)" do
|
235
|
+
setup { @worked = @user.voice_confirm_with('wrong_code') }
|
236
|
+
should "not work" do
|
237
|
+
assert !@worked
|
238
|
+
end
|
239
|
+
should "not save the phone number into the confirmed attribute" do
|
240
|
+
assert_not_equal @user._TC_voice_confirmed_phone_number,
|
241
|
+
@user._TC_phone_number
|
242
|
+
end
|
243
|
+
should_not_change "confirmed phone number attribute" do
|
244
|
+
@user.reload._TC_voice_confirmed_phone_number
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context "when the number is not confirmed" do
|
252
|
+
context "sending a message" do
|
253
|
+
setup {
|
254
|
+
TwilioContactable::Gateway.stubs(:deliver).returns(Success)
|
255
|
+
@result = @user.send_sms!('message')
|
256
|
+
}
|
257
|
+
should "send send no messages" do
|
258
|
+
assert_equal false, @result
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
context "when the number is blocked" do
|
263
|
+
setup {
|
264
|
+
@user._TC_sms_blocked = true
|
265
|
+
@user.save!
|
266
|
+
}
|
267
|
+
context "sending a message" do
|
268
|
+
setup { @result = @user.send_sms!('message') }
|
269
|
+
should "send nothing" do
|
270
|
+
assert_equal false, @result
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
context "when the number is confirmed" do
|
275
|
+
setup {
|
276
|
+
TwilioContactable::Gateway.stubs(:deliver).returns(Success)
|
277
|
+
@user.stubs(:sms_confirmed?).returns(true)
|
278
|
+
}
|
279
|
+
context "sending a message" do
|
280
|
+
setup { @result = @user.send_sms!('message') }
|
281
|
+
should "send send exactly one message" do
|
282
|
+
assert_equal [7], @result
|
283
|
+
end
|
284
|
+
end
|
285
|
+
context "sending a blank message" do
|
286
|
+
setup { @result = @user.send_sms!('') }
|
287
|
+
should "send send zero messages" do
|
288
|
+
assert_equal false, @result
|
289
|
+
end
|
290
|
+
end
|
291
|
+
context "sending a huge message" do
|
292
|
+
context "without the allow_multiple flag" do
|
293
|
+
should "raise an error" do
|
294
|
+
assert_raises ArgumentError do
|
295
|
+
@user.send_sms!("A"*200)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
context "with the allow_multiple flag" do
|
300
|
+
setup { @result = @user.send_sms!("A"*200, true) }
|
301
|
+
should "send multiple messages" do
|
302
|
+
assert_equal [160, 40], @result
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|