4info 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +59 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/init.rb +3 -0
- data/lib/contactable.rb +83 -0
- data/lib/controller.rb +62 -0
- data/lib/four_info.rb +35 -0
- data/lib/request.rb +76 -0
- data/lib/response.rb +22 -0
- data/lib/templates/confirm.haml +6 -0
- data/lib/templates/deliver.haml +7 -0
- data/lib/templates/unblock.haml +6 -0
- data/test/.gitignore +1 -0
- data/test/database.yml +18 -0
- data/test/four_info_contactable_test.rb +200 -0
- data/test/four_info_controller_test.rb +74 -0
- data/test/four_info_module_test.rb +39 -0
- data/test/sms.yml +3 -0
- data/test/test_helper.rb +40 -0
- metadata +116 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
4info
|
2
|
+
=====
|
3
|
+
|
4
|
+
Connect to the 4info SMS gateway
|
5
|
+
|
6
|
+
If you're using 4info.com as your SMS gateway this gem will give you a painless API.
|
7
|
+
|
8
|
+
USAGE
|
9
|
+
=====
|
10
|
+
|
11
|
+
Include FourInfo::Contactable into your User class or whatever you're using to represent an entity with a phone number.
|
12
|
+
|
13
|
+
class User < ActiveRecord::Base
|
14
|
+
include FourInfo::Contactable
|
15
|
+
end
|
16
|
+
|
17
|
+
You can also specify which attributes you'd like to use instead of the defaults
|
18
|
+
|
19
|
+
class User < ActiveRecord::Base
|
20
|
+
include FourInfo::Contactable
|
21
|
+
|
22
|
+
sms_phone_number_column :mobile_number
|
23
|
+
sms_blocked_column :is_sms_blocked
|
24
|
+
sms_confirmation_code_column :the_sms_confirmation_code
|
25
|
+
sms_confirmation_attempted_column :when_was_the_sms_confirmation_attempted
|
26
|
+
sms_confirmed_column :is_the_mobile_number_confirmed
|
27
|
+
|
28
|
+
# Defaults to the name on the left (minus the word '_column')
|
29
|
+
end
|
30
|
+
|
31
|
+
You can manage the user's SMS state like so:
|
32
|
+
|
33
|
+
@user = User.create(:sms_phone_number => '5552223333')
|
34
|
+
@user.confirm_sms!
|
35
|
+
# then ask the user for the confirmation code and
|
36
|
+
# compare it to @user.sms_confirmation_code
|
37
|
+
@user.update_attributes(:sms_confirmed => true)
|
38
|
+
@user.send_sms!("Hi! This is a text message.")
|
39
|
+
# Then maybe the user will reply with 'BLOCK' by accident
|
40
|
+
@user.unblock_sms!
|
41
|
+
|
42
|
+
|
43
|
+
There's also a controller module that allows you to super-easily create a controller
|
44
|
+
that receives data from 4info.com
|
45
|
+
|
46
|
+
class SMSController < ApplicationController
|
47
|
+
include FourInfo::Controller
|
48
|
+
|
49
|
+
sms_contactable User # or whichever class you included FourInfo::Contactable into
|
50
|
+
end
|
51
|
+
|
52
|
+
Now anything posted to the index (or create, if you've hooked this up RESTfully) action
|
53
|
+
will automatically work. If 4info.com sends you a response from a user the user record
|
54
|
+
will be found by their phone number and, if User has a 'receive_sms' method defined,
|
55
|
+
the record will receive the message that's just been sent.
|
56
|
+
|
57
|
+
That's it. Patches welcome, forks celebrated.
|
58
|
+
|
59
|
+
Copyright (c) 2010 Jack Danger Canty (http://jåck.com/), released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "4info"
|
8
|
+
gem.summary = %Q{Send and receive SMS messages via 4info.com}
|
9
|
+
gem.description = %Q{A complete Ruby API for handling SMS messages via 4info.com}
|
10
|
+
gem.email = "gitcommit@6brand.com"
|
11
|
+
gem.homepage = "http://github.com/JackDanger/4info"
|
12
|
+
gem.authors = ["Jack Danger Canty"]
|
13
|
+
gem.add_dependency "hpricot", ">= 0"
|
14
|
+
gem.add_dependency "haml", ">= 0"
|
15
|
+
gem.add_development_dependency "shoulda", ">= 0"
|
16
|
+
gem.add_development_dependency "mocha", ">= 0"
|
17
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
desc "Test 4info"
|
26
|
+
Rake::TestTask.new(:test) do |test|
|
27
|
+
test.libs << 'lib' << 'test'
|
28
|
+
test.pattern = 'test/**/*_test.rb'
|
29
|
+
test.verbose = true
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
require 'rcov/rcovtask'
|
34
|
+
Rcov::RcovTask.new do |test|
|
35
|
+
test.libs << 'test'
|
36
|
+
test.pattern = 'test/**/test_*.rb'
|
37
|
+
test.verbose = true
|
38
|
+
end
|
39
|
+
rescue LoadError
|
40
|
+
task :rcov do
|
41
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
task :test => :check_dependencies
|
46
|
+
|
47
|
+
task :default => :test
|
48
|
+
|
49
|
+
require 'rake/rdoctask'
|
50
|
+
Rake::RDocTask.new do |rdoc|
|
51
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
52
|
+
|
53
|
+
rdoc.rdoc_dir = 'rdoc'
|
54
|
+
rdoc.title = "inline_styles #{version}"
|
55
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/init.rb
ADDED
data/lib/contactable.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module FourInfo
|
2
|
+
module Contactable
|
3
|
+
|
4
|
+
Attributes = [ :sms_phone_number,
|
5
|
+
:sms_blocked,
|
6
|
+
:sms_confirmation_code,
|
7
|
+
:sms_confirmation_attempted,
|
8
|
+
:sms_confirmed ]
|
9
|
+
|
10
|
+
def self.included(model)
|
11
|
+
gem 'haml'
|
12
|
+
require 'haml'
|
13
|
+
require 'net/http'
|
14
|
+
|
15
|
+
Attributes.each do |attribute|
|
16
|
+
# add a method for setting or retrieving
|
17
|
+
# which column should be used for which attribute
|
18
|
+
#
|
19
|
+
# :sms_phone_number_column defaults to :sms_phone_number, etc.
|
20
|
+
model.instance_eval "
|
21
|
+
def #{attribute}_column(value = nil)
|
22
|
+
@#{attribute}_column ||= :#{attribute}
|
23
|
+
@#{attribute}_column = value if value
|
24
|
+
@#{attribute}_column
|
25
|
+
end
|
26
|
+
"
|
27
|
+
# provide a helper method to access the right value
|
28
|
+
# no matter which column it's stored in
|
29
|
+
#
|
30
|
+
# e.g.: @user.four_info_sms_confirmed
|
31
|
+
# => @user.send(User.sms_confirmed_column)
|
32
|
+
model.class_eval "
|
33
|
+
def four_info_#{attribute}(value = nil)
|
34
|
+
value ?
|
35
|
+
send(self.class.#{attribute}_column.to_s+'=', value) :
|
36
|
+
send(self.class.#{attribute}_column)
|
37
|
+
end
|
38
|
+
alias_method :four_info_#{attribute}?, :four_info_#{attribute}
|
39
|
+
alias_method :four_info_#{attribute}=, :four_info_#{attribute}
|
40
|
+
"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def send_sms!(msg, allow_multiple = false)
|
45
|
+
if msg.to_s.size > 160 && !allow_multiple
|
46
|
+
raise ArgumentError, "SMS Message is too long. Either specify that you want multiple messages or shorten the string."
|
47
|
+
end
|
48
|
+
return false if msg.to_s.strip.blank? || four_info_sms_blocked? || !four_info_sms_confirmed?
|
49
|
+
|
50
|
+
msg.to_s.scan(/.{1,160}/m).map do |text|
|
51
|
+
FourInfo::Request.new.deliver_message(text, four_info_sms_phone_number).success?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def confirm_sms!
|
56
|
+
return false if four_info_sms_blocked?
|
57
|
+
return true if four_info_sms_confirmed?
|
58
|
+
return false if four_info_sms_phone_number.blank?
|
59
|
+
|
60
|
+
response = FourInfo::Request.new.confirm(four_info_sms_phone_number)
|
61
|
+
if response.success?
|
62
|
+
self.four_info_sms_confirmation_code = response.confirmation_code
|
63
|
+
self.four_info_sms_confirmation_attempted = Time.now
|
64
|
+
save
|
65
|
+
else
|
66
|
+
# "Confirmation Failed: #{response['message'].inspect}"
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def unblock_sms!
|
72
|
+
return false unless four_info_sms_blocked?
|
73
|
+
|
74
|
+
response = FourInfo::Request.new.unblock(four_info_sms_phone_number)
|
75
|
+
if response.success?
|
76
|
+
four_info_sms_blocked 'false'
|
77
|
+
save!
|
78
|
+
else
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/controller.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module FourInfo
|
2
|
+
module Controller
|
3
|
+
|
4
|
+
def self.included(controller)
|
5
|
+
controller.instance_eval do
|
6
|
+
# the user should specify which class gets contacted
|
7
|
+
def sms_contactable(klass)
|
8
|
+
@@contactable_class = klass
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# the likely default
|
14
|
+
def index
|
15
|
+
recieve_xml
|
16
|
+
end
|
17
|
+
|
18
|
+
# in case this is hooked up as a RESTful route
|
19
|
+
def create
|
20
|
+
recieve_xml
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def recieve_xml
|
26
|
+
|
27
|
+
unless defined?(@@contactable_class)
|
28
|
+
raise RuntimeError, "Please define your user class in the FourInfo controller via the 'sms_contactable' method"
|
29
|
+
end
|
30
|
+
|
31
|
+
request = params[:request]
|
32
|
+
render :text => 'unknown format', :status => 500 and return unless request
|
33
|
+
case request['type']
|
34
|
+
when 'BLOCK'
|
35
|
+
@contactable = find_contactable(request[:block][:recipient][:id])
|
36
|
+
@contactable.four_info_sms_blocked = true
|
37
|
+
@contactable.save!
|
38
|
+
when 'MESSAGE'
|
39
|
+
@contactable = find_contactable(request[:message][:sender][:id])
|
40
|
+
if @contactable.respond_to?(:receive_sms)
|
41
|
+
@contactable.receive_sms(request[:message][:text])
|
42
|
+
else
|
43
|
+
warn "An SMS message was received but #{@@contactable_class.name.inspect} doesn't have a receive_sms method!"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
render :text => 'OK', :status => 200
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_contactable(id)
|
50
|
+
[id, id.sub(/^\+/,''), id.sub(/^\+1/,'')].each do |possible_phone_number|
|
51
|
+
found = @@contactable_class.find(
|
52
|
+
:first,
|
53
|
+
:conditions =>
|
54
|
+
{ @@contactable_class.sms_phone_number_column => possible_phone_number }
|
55
|
+
)
|
56
|
+
return found if found
|
57
|
+
end
|
58
|
+
# rescue => error
|
59
|
+
# render :text => error.inspect
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/four_info.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module FourInfo
|
2
|
+
Gateway = URI.parse 'http://gateway.4info.net/msg'
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def mode
|
6
|
+
@@mode ||= :live
|
7
|
+
end
|
8
|
+
def mode=(new_mode)
|
9
|
+
@@mode = new_mode
|
10
|
+
end
|
11
|
+
|
12
|
+
def numerize(numberish)
|
13
|
+
numberish.to_s.scan(/\d+/).join
|
14
|
+
end
|
15
|
+
|
16
|
+
def internationalize(given_number)
|
17
|
+
number = numerize(given_number)
|
18
|
+
case number.size
|
19
|
+
when 10
|
20
|
+
"+1#{number}"
|
21
|
+
when 11
|
22
|
+
"+#{number}"
|
23
|
+
when 12
|
24
|
+
number =~ /\+\d(11)/ ? number : nil
|
25
|
+
else
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
require File.join(File.dirname(__FILE__), 'contactable')
|
33
|
+
require File.join(File.dirname(__FILE__), 'controller')
|
34
|
+
require File.join(File.dirname(__FILE__), 'request')
|
35
|
+
require File.join(File.dirname(__FILE__), 'response')
|
data/lib/request.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module FourInfo
|
2
|
+
class Request
|
3
|
+
|
4
|
+
# Haml templates for XML
|
5
|
+
@@templates = Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), 'templates', '*.haml')))
|
6
|
+
|
7
|
+
# YML config files
|
8
|
+
@@test_mode_config_file = File.join(File.dirname(__FILE__), '..', 'test', 'sms.yml')
|
9
|
+
@@likely_config_files = [
|
10
|
+
File.join(File.dirname(__FILE__), '..', 'sms.yml'),
|
11
|
+
defined?(Rails) ? File.join(Rails.root, 'config', 'sms.yml') : '',
|
12
|
+
File.join('config', 'sms.yml'),
|
13
|
+
'sms.yml'
|
14
|
+
]
|
15
|
+
|
16
|
+
attr_accessor :config
|
17
|
+
attr_accessor :number
|
18
|
+
attr_accessor :message
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
config_file = :test == FourInfo.mode ?
|
22
|
+
@@test_mode_config_file :
|
23
|
+
@@likely_config_files.detect {|f| File.exist?(f) }
|
24
|
+
|
25
|
+
raise "Missing config File! Please add sms.yml to ./config or the 4info directory" unless config_file
|
26
|
+
|
27
|
+
@config = YAML.load(File.read(config_file))['4info'].with_indifferent_access
|
28
|
+
end
|
29
|
+
|
30
|
+
def deliver_message(message, number)
|
31
|
+
self.number = FourInfo.internationalize(number)
|
32
|
+
self.message = message
|
33
|
+
|
34
|
+
xml = template(:deliver).render(self)
|
35
|
+
Response.new(perform(xml))
|
36
|
+
end
|
37
|
+
|
38
|
+
def confirm(number)
|
39
|
+
self.number = FourInfo.internationalize(number)
|
40
|
+
|
41
|
+
xml = template(:confirm).render(self)
|
42
|
+
Response.new(perform(xml))
|
43
|
+
end
|
44
|
+
|
45
|
+
def unblock(number)
|
46
|
+
self.number = FourInfo.internationalize(number)
|
47
|
+
|
48
|
+
xml = template(:unblock).render(self)
|
49
|
+
Response.new(perform(xml))
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def template(name)
|
55
|
+
file = @@templates.detect {|t| File.basename(t).chomp('.haml').to_sym == name.to_sym }
|
56
|
+
raise ArgumentError, "Missing 4Info template: #{name}" unless file
|
57
|
+
Haml::Engine.new(File.read(file))
|
58
|
+
end
|
59
|
+
|
60
|
+
def perform(body)
|
61
|
+
STDOUT.puts('in perform')
|
62
|
+
start do |http|
|
63
|
+
http.post(FourInfo::Gateway.path, body).read_body
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def start
|
68
|
+
net = config[:proxy].blank? ?
|
69
|
+
Net::HTTP :
|
70
|
+
Net::HTTP::Proxy(*config[:proxy].split(":"))
|
71
|
+
net.start(FourInfo::Gateway.host, FourInfo::Gateway.port) do |http|
|
72
|
+
yield http
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/response.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module FourInfo
|
2
|
+
class Response
|
3
|
+
def initialize(xml)
|
4
|
+
gem 'hpricot'
|
5
|
+
require 'hpricot'
|
6
|
+
@body = Hpricot.parse(xml)
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](name)
|
10
|
+
nodes = (@body/name)
|
11
|
+
1 == nodes.size ? nodes.first : nodes
|
12
|
+
end
|
13
|
+
|
14
|
+
def success?
|
15
|
+
'Success' == self['message'].inner_text
|
16
|
+
end
|
17
|
+
|
18
|
+
def confirmation_code
|
19
|
+
self['confCode'].inner_text
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
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
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class FourInfoContactableTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
ValidationError = '<?xml version="1.0" encoding="UTF-8"?>
|
6
|
+
<response>
|
7
|
+
<status>
|
8
|
+
<id>4</id>
|
9
|
+
<message>Validation Error</message>
|
10
|
+
</status>
|
11
|
+
</response>'
|
12
|
+
ValidationSuccess = '<?xml version=”1.0” ?>
|
13
|
+
<response>
|
14
|
+
<requestId>F81D4FAE-7DEC-11D0-A765-00A0C91E6BF6</requestId>
|
15
|
+
<confCode>123abc</confCode>
|
16
|
+
<status>
|
17
|
+
<id>1</id>
|
18
|
+
<message>Success</message>
|
19
|
+
</status>
|
20
|
+
</response>'
|
21
|
+
SendMsgSuccess = '<?xml version="1.0" ?>
|
22
|
+
<response>
|
23
|
+
<requestId>F81D4FAE-7DEC-11D0-A765-00A0C91E6BF6</requestId>
|
24
|
+
<status>
|
25
|
+
<id>1</id>
|
26
|
+
<message>Success</message>
|
27
|
+
</status>
|
28
|
+
</response>'
|
29
|
+
UnblockSuccess = '<?xml version=”1.0” ?>
|
30
|
+
<response>
|
31
|
+
<status>
|
32
|
+
<id>1</id>
|
33
|
+
<message>Success</message>
|
34
|
+
</status>
|
35
|
+
</response>'
|
36
|
+
|
37
|
+
context "contactable class" do
|
38
|
+
setup {
|
39
|
+
@klass = Class.new
|
40
|
+
@klass.send :include, FourInfo::Contactable
|
41
|
+
}
|
42
|
+
FourInfo::Contactable::Attributes.each do |attribute|
|
43
|
+
should "begin with appropriate default for #{attribute}_column" do
|
44
|
+
assert_equal attribute, @klass.send("#{attribute}_column")
|
45
|
+
end
|
46
|
+
should "allow setting #{attribute}_column" do
|
47
|
+
new_column_name = :custom_column
|
48
|
+
@klass.send "#{attribute}_column", new_column_name
|
49
|
+
assert_equal new_column_name, @klass.send("#{attribute}_column")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "contactable instance" do
|
55
|
+
setup {
|
56
|
+
User.delete_all
|
57
|
+
@user = User.create! :sms_phone_number => '555-555-5555'
|
58
|
+
}
|
59
|
+
|
60
|
+
context "when phone number is blank" do
|
61
|
+
setup { @user.sms_phone_number = nil}
|
62
|
+
context "confirming phone number" do
|
63
|
+
setup { @user.confirm_sms! }
|
64
|
+
should_not_change "any attributes" do
|
65
|
+
@user.attributes.inspect
|
66
|
+
end
|
67
|
+
end
|
68
|
+
context "sending message" do
|
69
|
+
setup {
|
70
|
+
FourInfo::Request.any_instance.stubs(:perform).returns(SendMsgSuccess)
|
71
|
+
@worked = @user.send_sms!('message')
|
72
|
+
}
|
73
|
+
should "not work" do assert !@worked end
|
74
|
+
should_not_change "any attributes" do
|
75
|
+
@user.attributes.inspect
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when phone number exists" do
|
81
|
+
setup { @user.sms_phone_number = "206-555-5555"}
|
82
|
+
context "confirming phone number" do
|
83
|
+
setup {
|
84
|
+
FourInfo::Request.any_instance.stubs(:perform).returns(ValidationSuccess)
|
85
|
+
@worked = @user.confirm_sms!
|
86
|
+
}
|
87
|
+
should "work" do assert @worked end
|
88
|
+
should "save confirmation number in proper attribute" do
|
89
|
+
assert @user.four_info_sms_confirmation_code
|
90
|
+
end
|
91
|
+
should_change "stored code" do
|
92
|
+
@user.four_info_sms_confirmation_code
|
93
|
+
end
|
94
|
+
should "not change sms_confirmed? to true" do
|
95
|
+
assert !@user.four_info_sms_confirmed?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
context "confirming phone number when the confirmation fails for some reason" do
|
99
|
+
setup {
|
100
|
+
FourInfo::Request.any_instance.stubs(:perform).returns(ValidationError)
|
101
|
+
@worked = @user.confirm_sms!
|
102
|
+
}
|
103
|
+
should "not work" do assert !@worked end
|
104
|
+
should "not save confirmation number" do
|
105
|
+
assert @user.four_info_sms_confirmation_code.blank?
|
106
|
+
end
|
107
|
+
should_not_change "stored code" do
|
108
|
+
@user.four_info_sms_confirmation_code
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "when the number is not confirmed" do
|
114
|
+
context "sending a message" do
|
115
|
+
setup {
|
116
|
+
FourInfo::Request.any_instance.stubs(:perform).returns(SendMsgSuccess)
|
117
|
+
@result = @user.send_sms!('message')
|
118
|
+
}
|
119
|
+
should "send send no messages" do
|
120
|
+
assert_equal false, @result
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
context "when the number is blocked" do
|
125
|
+
setup {
|
126
|
+
@user.four_info_sms_blocked = true
|
127
|
+
@user.save!
|
128
|
+
}
|
129
|
+
context "sending a message" do
|
130
|
+
setup { @result = @user.send_sms!('message') }
|
131
|
+
should "send nothing" do
|
132
|
+
assert_equal false, @result
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
context "when the number is confirmed" do
|
137
|
+
setup {
|
138
|
+
FourInfo::Request.any_instance.stubs(:perform).returns(SendMsgSuccess)
|
139
|
+
@user.update_attributes!(User.sms_confirmed_column => true)
|
140
|
+
}
|
141
|
+
context "sending a message" do
|
142
|
+
setup { @result = @user.send_sms!('message') }
|
143
|
+
should "send send exactly one message messages" do
|
144
|
+
assert_equal [true], @result
|
145
|
+
end
|
146
|
+
end
|
147
|
+
context "sending a blank message" do
|
148
|
+
setup { @result = @user.send_sms!('') }
|
149
|
+
should "send send zero messages" do
|
150
|
+
assert_equal false, @result
|
151
|
+
end
|
152
|
+
end
|
153
|
+
context "sending a huge message" do
|
154
|
+
context "without the allow_multiple flag" do
|
155
|
+
should "raise an error" do
|
156
|
+
assert_raises ArgumentError do
|
157
|
+
@user.send_sms!("A"*200)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
context "with the allow_multiple flag" do
|
162
|
+
setup { @result = @user.send_sms!("A"*200, true) }
|
163
|
+
should "send multiple messages" do
|
164
|
+
assert_equal [true, true], @result
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "when the number is not blocked" do
|
171
|
+
setup {
|
172
|
+
FourInfo::Request.any_instance.expects(:perform).never
|
173
|
+
}
|
174
|
+
context "unblocking" do
|
175
|
+
setup { @worked = @user.unblock_sms! }
|
176
|
+
should "not do anything" do
|
177
|
+
assert !@worked
|
178
|
+
end
|
179
|
+
should_not_change "any attributes" do
|
180
|
+
@user.attributes.inspect
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
context "when the number is blocked" do
|
185
|
+
setup {
|
186
|
+
FourInfo::Request.any_instance.stubs(:perform).returns(UnblockSuccess)
|
187
|
+
@user.update_attributes!(:sms_blocked => true)
|
188
|
+
}
|
189
|
+
context "unblocking" do
|
190
|
+
setup { @worked = @user.unblock_sms! }
|
191
|
+
should "work" do
|
192
|
+
assert @worked
|
193
|
+
end
|
194
|
+
should_change "blocked attribute" do
|
195
|
+
@user.reload.sms_blocked
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require 'action_pack'
|
3
|
+
require 'action_controller'
|
4
|
+
require 'shoulda/action_controller'
|
5
|
+
require 'shoulda/action_controller/macros'
|
6
|
+
require 'shoulda/action_controller/matchers'
|
7
|
+
|
8
|
+
class FourInfoController < ActionController::Base
|
9
|
+
include FourInfo::Controller
|
10
|
+
|
11
|
+
sms_contactable User
|
12
|
+
end
|
13
|
+
ActionController::Routing::Routes.draw do |map|
|
14
|
+
map.route '*:url', :controller => 'four_info', :action => :index
|
15
|
+
end
|
16
|
+
|
17
|
+
class UserWithSMSReceiving < User
|
18
|
+
def receive_sms(message)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class FourInfoControllerTest < ActionController::TestCase
|
23
|
+
|
24
|
+
context "with a user" do
|
25
|
+
setup {
|
26
|
+
User.delete_all
|
27
|
+
@user = User.create! :sms_phone_number => '3334445555'
|
28
|
+
}
|
29
|
+
context "receiving BLOCK" do
|
30
|
+
setup {
|
31
|
+
post :index,
|
32
|
+
# this is what an xml request will parse to:
|
33
|
+
"request"=>{"block"=>{"recipient"=>{"property"=>{"name"=>"CARRIER", "value"=>"3"}, "id"=>"+1#{@user.sms_phone_number}", "type"=>"5"}}, "type" => "BLOCK"}
|
34
|
+
}
|
35
|
+
should_respond_with :success
|
36
|
+
should "block user" do
|
37
|
+
assert @user.reload.four_info_sms_blocked?
|
38
|
+
end
|
39
|
+
should_change "user block status" do
|
40
|
+
@user.reload.four_info_sms_blocked?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
context "receiving MESSAGE" do
|
44
|
+
setup {
|
45
|
+
# this is what an xml request will parse to:
|
46
|
+
@receive_params = {"request"=>{"message"=>{"id" => "ABCDEFG", "recipient"=>{"type"=> "6", "id"=>"12345"}, "sender" => {"type" => "5", "id" => "+1#{@user.sms_phone_number}", "property" => {"name" => "CARRIER", "value" => "5"}}, "text" => "This is a text message."}, "type" => "MESSAGE"}}
|
47
|
+
}
|
48
|
+
context "when the user is not set up to receive" do
|
49
|
+
setup {
|
50
|
+
@user.expects(:receive_sms).with("This is a text message.").never
|
51
|
+
post :index,
|
52
|
+
@receive_params
|
53
|
+
}
|
54
|
+
should_respond_with :success
|
55
|
+
should "not block user" do
|
56
|
+
assert !@user.reload.four_info_sms_blocked?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
context "when the user is set up to receive" do
|
60
|
+
setup {
|
61
|
+
User.delete_all
|
62
|
+
@new_user = UserWithSMSReceiving.create!(:sms_phone_number => @user.sms_phone_number)
|
63
|
+
UserWithSMSReceiving.any_instance.expects(:receive_sms).with("This is a text message.").once
|
64
|
+
post :index,
|
65
|
+
@receive_params
|
66
|
+
}
|
67
|
+
should_respond_with :success
|
68
|
+
should "not block user" do
|
69
|
+
assert !@new_user.reload.four_info_sms_blocked?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class FourInfoModuleTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
context "standardizing numbers" do
|
6
|
+
context "to digits" do
|
7
|
+
should "remove all but integers" do
|
8
|
+
assert_equal '12345', FourInfo.numerize('1-2-3-4-5')
|
9
|
+
assert_equal '12345', FourInfo.numerize('1 2 3 4 5')
|
10
|
+
assert_equal '12345', FourInfo.numerize('1,2(3)4.5')
|
11
|
+
assert_equal '12345', FourInfo.numerize('1,2(3)4.5')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
context "to international format" do
|
15
|
+
should "add a '+' to all 11 digit numbers" do
|
16
|
+
assert_equal '+12345678901', FourInfo.internationalize('12345678901')
|
17
|
+
assert_equal '+72345678901', FourInfo.internationalize('72345678901')
|
18
|
+
end
|
19
|
+
should "add a '+1' to any 10 digit number" do
|
20
|
+
assert_equal '+12345678901', FourInfo.internationalize('2345678901')
|
21
|
+
assert_equal '+17345678901', FourInfo.internationalize('7345678901')
|
22
|
+
end
|
23
|
+
should "leave 12 digit numbers unchanged" do
|
24
|
+
[ '+' + ('3'*11),
|
25
|
+
'+' + ('8'*11),
|
26
|
+
'+' + ('4'*11) ].each do |number|
|
27
|
+
assert_equal number, FourInfo.internationalize(number)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
should "return nil for all bad numbers" do
|
31
|
+
assert_equal nil, FourInfo.internationalize(nil)
|
32
|
+
assert_equal nil, FourInfo.internationalize('nil')
|
33
|
+
assert_equal nil, FourInfo.internationalize('1234')
|
34
|
+
assert_equal nil, FourInfo.internationalize('11111111111111111111111')
|
35
|
+
assert_equal nil, FourInfo.internationalize('what?')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/test/sms.yml
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'test-unit'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'mocha'
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_record'
|
7
|
+
require 'active_support/test_case'
|
8
|
+
require 'shoulda'
|
9
|
+
require File.join(File.dirname(__FILE__), "..", 'lib', 'four_info')
|
10
|
+
|
11
|
+
FourInfo.mode = :test
|
12
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
13
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
14
|
+
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3'])
|
15
|
+
|
16
|
+
ActiveRecord::Schema.define(:version => 1) do
|
17
|
+
create_table :users do |t|
|
18
|
+
t.column :sms_phone_number, :string
|
19
|
+
t.column :sms_confirmed, :boolean
|
20
|
+
t.column :sms_blocked, :boolean
|
21
|
+
t.column :sms_confirmation_attempted, :datetime
|
22
|
+
t.column :sms_confirmation_code, :string
|
23
|
+
|
24
|
+
t.column :type, :string
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class User < ActiveRecord::Base
|
29
|
+
include FourInfo::Contactable
|
30
|
+
end
|
31
|
+
|
32
|
+
# kill all network access
|
33
|
+
# module FourInfo
|
34
|
+
# class Request
|
35
|
+
# def start
|
36
|
+
# Response.new(:success => true,
|
37
|
+
# :confirmation_code => 'FAKE')
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
# end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: 4info
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jack Danger Canty
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-10 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hpricot
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: haml
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: shoulda
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: mocha
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
description: A complete Ruby API for handling SMS messages via 4info.com
|
56
|
+
email: gitcommit@6brand.com
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files:
|
62
|
+
- README
|
63
|
+
files:
|
64
|
+
- MIT-LICENSE
|
65
|
+
- README
|
66
|
+
- Rakefile
|
67
|
+
- VERSION
|
68
|
+
- init.rb
|
69
|
+
- lib/contactable.rb
|
70
|
+
- lib/controller.rb
|
71
|
+
- lib/four_info.rb
|
72
|
+
- lib/request.rb
|
73
|
+
- lib/response.rb
|
74
|
+
- lib/templates/confirm.haml
|
75
|
+
- lib/templates/deliver.haml
|
76
|
+
- lib/templates/unblock.haml
|
77
|
+
- test/.gitignore
|
78
|
+
- test/database.yml
|
79
|
+
- test/four_info_contactable_test.rb
|
80
|
+
- test/four_info_controller_test.rb
|
81
|
+
- test/four_info_module_test.rb
|
82
|
+
- test/sms.yml
|
83
|
+
- test/test_helper.rb
|
84
|
+
has_rdoc: true
|
85
|
+
homepage: http://github.com/JackDanger/4info
|
86
|
+
licenses: []
|
87
|
+
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options:
|
90
|
+
- --charset=UTF-8
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: "0"
|
98
|
+
version:
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: "0"
|
104
|
+
version:
|
105
|
+
requirements: []
|
106
|
+
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.3.5
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: Send and receive SMS messages via 4info.com
|
112
|
+
test_files:
|
113
|
+
- test/four_info_contactable_test.rb
|
114
|
+
- test/four_info_controller_test.rb
|
115
|
+
- test/four_info_module_test.rb
|
116
|
+
- test/test_helper.rb
|