4info 1.0.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.
- 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
|