clickatell-ruby19 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/History.txt +55 -0
- data/License.txt +20 -0
- data/RDOC_README.txt +33 -0
- data/README.textile +40 -0
- data/Rakefile +95 -0
- data/bin/sms +62 -0
- data/clickatell.gemspec +82 -0
- data/lib/clickatell.rb +10 -0
- data/lib/clickatell/api.rb +153 -0
- data/lib/clickatell/api/command.rb +31 -0
- data/lib/clickatell/api/command_executor.rb +69 -0
- data/lib/clickatell/api/error.rb +25 -0
- data/lib/clickatell/api/message_status.rb +26 -0
- data/lib/clickatell/response.rb +32 -0
- data/lib/clickatell/utility.rb +6 -0
- data/lib/clickatell/utility/options.rb +109 -0
- data/lib/clickatell/version.rb +13 -0
- data/lib/core-ext/hash.rb +15 -0
- data/scripts/txt2html +67 -0
- data/spec/api_spec.rb +239 -0
- data/spec/cli_options_test.rb +26 -0
- data/spec/command_executor_spec.rb +75 -0
- data/spec/hash_ext_spec.rb +14 -0
- data/spec/response_spec.rb +48 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +8 -0
- metadata +111 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require "cgi"
|
2
|
+
module Clickatell
|
3
|
+
class API
|
4
|
+
|
5
|
+
# Represents a Clickatell HTTP gateway command in the form
|
6
|
+
# of a complete URL (the raw, low-level request).
|
7
|
+
class Command
|
8
|
+
API_SERVICE_HOST = 'api.clickatell.com'
|
9
|
+
|
10
|
+
def initialize(command_name, service = 'http', opts={})
|
11
|
+
@command_name = command_name
|
12
|
+
@service = service
|
13
|
+
@options = { :secure => false }.merge(opts)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns a URL for the given parameters (a hash).
|
17
|
+
def with_params(param_hash)
|
18
|
+
param_string = '?' + param_hash.map { |key, value| "#{::CGI.escape(key.to_s)}=#{::CGI.escape(value.to_s)}" }.sort.join('&')
|
19
|
+
return URI.parse(File.join(api_service_uri, @command_name + param_string))
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def api_service_uri
|
24
|
+
protocol = @options[:secure] ? 'https' : 'http'
|
25
|
+
api_service_host = ((Clickatell::API.api_service_host.nil? || Clickatell::API.api_service_host.empty?) ? API_SERVICE_HOST : Clickatell::API.api_service_host)
|
26
|
+
return "#{protocol}://#{api_service_host}/#{@service}/"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
|
4
|
+
module Clickatell
|
5
|
+
class API
|
6
|
+
|
7
|
+
class FakeHttpResponse
|
8
|
+
def body
|
9
|
+
"test"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Used to run commands agains the Clickatell gateway.
|
14
|
+
class CommandExecutor
|
15
|
+
def initialize(authentication_hash, secure=false, debug=false, test_mode=false)
|
16
|
+
@authentication_hash = authentication_hash
|
17
|
+
@debug = debug
|
18
|
+
@secure = secure
|
19
|
+
@test_mode = test_mode
|
20
|
+
|
21
|
+
allow_request_recording if @test_mode
|
22
|
+
end
|
23
|
+
|
24
|
+
def in_test_mode?
|
25
|
+
@test_mode
|
26
|
+
end
|
27
|
+
|
28
|
+
# Builds a command object and sends it using HTTP GET.
|
29
|
+
# Will output URLs as they are requested to stdout when
|
30
|
+
# debugging is enabled.
|
31
|
+
def execute(command_name, service, parameters={})
|
32
|
+
request_uri = command(command_name, service, parameters)
|
33
|
+
puts "[debug] Sending request to #{request_uri}" if @debug
|
34
|
+
get_response(request_uri)
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def command(command_name, service, parameters) #:nodoc:
|
39
|
+
Command.new(command_name, service, :secure => @secure).with_params(
|
40
|
+
parameters.merge(@authentication_hash)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_response(uri)
|
45
|
+
if in_test_mode?
|
46
|
+
sms_requests << uri
|
47
|
+
[FakeHttpResponse.new]
|
48
|
+
else
|
49
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
50
|
+
http.use_ssl = (uri.scheme == 'https')
|
51
|
+
http.start do |http|
|
52
|
+
resp, body = http.get([uri.path, uri.query].join('?'))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def allow_request_recording
|
60
|
+
class << self
|
61
|
+
define_method :sms_requests do
|
62
|
+
@sms_requests ||= []
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Clickatell
|
2
|
+
class API
|
3
|
+
|
4
|
+
# Clickatell API Error exception.
|
5
|
+
class Error < StandardError
|
6
|
+
attr_reader :code, :message
|
7
|
+
|
8
|
+
def initialize(code, message)
|
9
|
+
@code, @message = code, message
|
10
|
+
end
|
11
|
+
|
12
|
+
# Creates a new Error from a Clickatell HTTP response string
|
13
|
+
# e.g.:
|
14
|
+
#
|
15
|
+
# Error.parse("ERR: 001, Authentication error")
|
16
|
+
# # => #<Clickatell::API::Error code='001' message='Authentication error'>
|
17
|
+
def self.parse(error_string)
|
18
|
+
error_details = error_string.split(':').last.strip
|
19
|
+
code, message = error_details.split(',').map { |s| s.strip }
|
20
|
+
self.new(code, message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Clickatell
|
2
|
+
class API
|
3
|
+
|
4
|
+
class MessageStatus
|
5
|
+
STATUS_MAP = {
|
6
|
+
1 => 'Message unknown',
|
7
|
+
2 => 'Message queued',
|
8
|
+
3 => 'Delivered to gateway',
|
9
|
+
4 => 'Received by recipient',
|
10
|
+
5 => 'Error with message',
|
11
|
+
6 => 'User cancelled messaged delivery',
|
12
|
+
7 => 'Error delivering message',
|
13
|
+
8 => 'OK',
|
14
|
+
9 => 'Routing error',
|
15
|
+
10 => 'Message expired',
|
16
|
+
11 => 'Message queued for later delivery',
|
17
|
+
12 => 'Out of credit'
|
18
|
+
}
|
19
|
+
|
20
|
+
def self.[](code)
|
21
|
+
STATUS_MAP[code]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Clickatell
|
4
|
+
|
5
|
+
# Used to parse HTTP responses returned from Clickatell API calls.
|
6
|
+
class Response
|
7
|
+
|
8
|
+
class << self
|
9
|
+
PARSE_REGEX = /[A-Za-z0-9]+:.*?(?:(?=[A-Za-z0-9]+:)|$)/
|
10
|
+
|
11
|
+
# Returns the HTTP response body data as a hash.
|
12
|
+
def parse(http_response)
|
13
|
+
return { 'OK' => 'session_id' } if API.test_mode
|
14
|
+
|
15
|
+
if http_response.body.scan(/ERR/).any?
|
16
|
+
raise Clickatell::API::Error.parse(http_response.body)
|
17
|
+
end
|
18
|
+
results = http_response.body.split("\n").map do |line|
|
19
|
+
# YAML.load converts integer strings that have leading zeroes into integers
|
20
|
+
# using octal rather than decimal. This isn't what we want, so we'll strip out any
|
21
|
+
# leading zeroes in numbers here.
|
22
|
+
response_fields = line.scan(PARSE_REGEX)
|
23
|
+
response_fields = response_fields.collect { |field| field.gsub(/\b0+(\d+)\b/, '\1') }
|
24
|
+
YAML.load(response_fields.join("\n"))
|
25
|
+
end
|
26
|
+
results.length == 1 ? results.first : results
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Clickatell
|
5
|
+
module Utility
|
6
|
+
class Options #:nodoc:
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def parse(args)
|
10
|
+
@options = self.default_options
|
11
|
+
parser = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: sms [options] recipient(s) message"
|
13
|
+
opts.separator " Recipients can be a comma-separated list, up to 100 max."
|
14
|
+
opts.separator ""
|
15
|
+
opts.separator "Specific options:"
|
16
|
+
|
17
|
+
opts.on('-u', '--username USERNAME',
|
18
|
+
"Specify the clickatell username (overrides ~/.clickatell setting)") do |username|
|
19
|
+
@options.username = username
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on('-p', '--password PASSWORD',
|
23
|
+
"Specify the clickatell password (overrides ~/.clickatell setting)") do |password|
|
24
|
+
@options.password = password
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('-k', '--apikey API_KEY',
|
28
|
+
"Specify the clickatell API key (overrides ~/.clickatell setting)") do |key|
|
29
|
+
@options.api_key = key
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on('-f', '--from NAME_OR_NUMBER',
|
33
|
+
"Specify the name or number that the SMS will appear from") do |from|
|
34
|
+
@options.from = from
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('-b', '--show-balance',
|
38
|
+
"Shows the total number of credits remaining on your account") do
|
39
|
+
@options.show_balance = true
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on('-s', '--status MESSAGE_ID',
|
43
|
+
"Displays the status of the specified message.") do |message_id|
|
44
|
+
@options.message_id = message_id
|
45
|
+
@options.show_status = true
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on('-S', '--secure',
|
49
|
+
"Sends request using HTTPS") do
|
50
|
+
Clickatell::API.secure_mode = true
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on('-d', '--debug') do
|
54
|
+
Clickatell::API.debug_mode = true
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on_tail('-h', '--help', "Show this message") do
|
58
|
+
puts opts
|
59
|
+
exit
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on_tail('-v', '--version') do
|
63
|
+
puts "Ruby Clickatell SMS Utility #{Clickatell::VERSION}"
|
64
|
+
exit
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
@options.recipient = args[-2].split(',') rescue nil
|
69
|
+
@options.message = args[-1]
|
70
|
+
|
71
|
+
parser.parse!(args)
|
72
|
+
|
73
|
+
if (@options.message.nil? || @options.recipient.nil?) && send_message?
|
74
|
+
puts "You must specify a recipient and message!"
|
75
|
+
puts parser
|
76
|
+
exit
|
77
|
+
end
|
78
|
+
|
79
|
+
return @options
|
80
|
+
|
81
|
+
rescue OptionParser::MissingArgument => e
|
82
|
+
switch_given = e.message.split(':').last.strip
|
83
|
+
puts "The #{switch_given} option requires an argument."
|
84
|
+
puts parser
|
85
|
+
exit
|
86
|
+
end
|
87
|
+
|
88
|
+
def default_options
|
89
|
+
options = OpenStruct.new
|
90
|
+
config_file = File.open(File.join(ENV['HOME'], '.clickatell'))
|
91
|
+
config = YAML.load(config_file)
|
92
|
+
options.username = config['username']
|
93
|
+
options.password = config['password']
|
94
|
+
options.api_key = config['api_key']
|
95
|
+
options.from = config['from']
|
96
|
+
return options
|
97
|
+
rescue Errno::ENOENT
|
98
|
+
return options
|
99
|
+
end
|
100
|
+
|
101
|
+
def send_message?
|
102
|
+
(@options.show_status.nil? &&
|
103
|
+
@options.show_balance.nil?)
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Hash
|
2
|
+
# Returns a new hash containing only the keys specified
|
3
|
+
# that exist in the current hash.
|
4
|
+
#
|
5
|
+
# {:a => '1', :b => '2', :c => '3'}.only(:a, :c)
|
6
|
+
# # => {:a => '1', :c => '3'}
|
7
|
+
#
|
8
|
+
# Keys that do not exist in the original hash are ignored.
|
9
|
+
def only(*keys)
|
10
|
+
inject( {} ) do |new_hash, (key, value)|
|
11
|
+
new_hash[key] = value if keys.include?(key)
|
12
|
+
new_hash
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/scripts/txt2html
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'redcloth'
|
5
|
+
require 'syntax/convertors/html'
|
6
|
+
require 'erb'
|
7
|
+
require File.dirname(__FILE__) + '/../lib/clickatell/version.rb'
|
8
|
+
|
9
|
+
version = Clickatell::VERSION::STRING
|
10
|
+
download = 'http://rubyforge.org/projects/clickatell'
|
11
|
+
|
12
|
+
class Fixnum
|
13
|
+
def ordinal
|
14
|
+
# teens
|
15
|
+
return 'th' if (10..19).include?(self % 100)
|
16
|
+
# others
|
17
|
+
case self % 10
|
18
|
+
when 1: return 'st'
|
19
|
+
when 2: return 'nd'
|
20
|
+
when 3: return 'rd'
|
21
|
+
else return 'th'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Time
|
27
|
+
def pretty
|
28
|
+
return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def convert_syntax(syntax, source)
|
33
|
+
return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
|
34
|
+
end
|
35
|
+
|
36
|
+
if ARGV.length >= 1
|
37
|
+
src, template = ARGV
|
38
|
+
template ||= File.dirname(__FILE__) + '/../website/template.rhtml'
|
39
|
+
|
40
|
+
else
|
41
|
+
puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
|
42
|
+
exit!
|
43
|
+
end
|
44
|
+
|
45
|
+
template = ERB.new(File.open(template).read)
|
46
|
+
|
47
|
+
title = nil
|
48
|
+
body = nil
|
49
|
+
File.open(src) do |fsrc|
|
50
|
+
title_text = fsrc.readline
|
51
|
+
body_text = fsrc.read
|
52
|
+
syntax_items = []
|
53
|
+
body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</>!m){
|
54
|
+
ident = syntax_items.length
|
55
|
+
element, syntax, source = $1, $2, $3
|
56
|
+
syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
|
57
|
+
"syntax-temp-#{ident}"
|
58
|
+
}
|
59
|
+
title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
|
60
|
+
body = RedCloth.new(body_text).to_html
|
61
|
+
body.gsub!(%r!(?:<pre><code>)?syntax-temp-(d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
|
62
|
+
end
|
63
|
+
stat = File.stat(src)
|
64
|
+
created = stat.ctime
|
65
|
+
modified = stat.mtime
|
66
|
+
|
67
|
+
$stdout << template.result(binding)
|
data/spec/api_spec.rb
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/clickatell'
|
3
|
+
|
4
|
+
class FakeHttp
|
5
|
+
def start(&block)
|
6
|
+
yield self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Clickatell
|
11
|
+
|
12
|
+
describe "API Command" do
|
13
|
+
before do
|
14
|
+
@command = API::Command.new('cmdname')
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
Clickatell::API.api_service_host = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return encoded URL for the specified command and parameters" do
|
22
|
+
url = @command.with_params(:param_one => 'abc', :param_two => '123')
|
23
|
+
url.should == URI.parse("http://api.clickatell.com/http/cmdname?param_one=abc¶m_two=123")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should URL encode any special characters in parameters" do
|
27
|
+
url = @command.with_params(:param_one => 'abc', :param_two => 'hello world & goodbye cruel world <grin>')
|
28
|
+
url.should == URI.parse("http://api.clickatell.com/http/cmdname?param_one=abc¶m_two=hello+world+%26+goodbye+cruel+world+%3Cgrin%3E")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should use a custom host when constructing command URLs if specified" do
|
32
|
+
Clickatell::API.api_service_host = 'api.clickatell-custom.co.uk'
|
33
|
+
url = @command.with_params(:param_one => 'abc', :param_two => '123')
|
34
|
+
url.should == URI.parse("http://api.clickatell-custom.co.uk/http/cmdname?param_one=abc¶m_two=123")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should use the default host if specified custom host is nil" do
|
38
|
+
Clickatell::API.api_service_host = nil
|
39
|
+
url = @command.with_params(:param_one => 'abc', :param_two => '123')
|
40
|
+
url.should == URI.parse("http://api.clickatell.com/http/cmdname?param_one=abc¶m_two=123")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should use the default host if specified custom host is an empty string" do
|
44
|
+
Clickatell::API.api_service_host = ''
|
45
|
+
url = @command.with_params(:param_one => 'abc', :param_two => '123')
|
46
|
+
url.should == URI.parse("http://api.clickatell.com/http/cmdname?param_one=abc¶m_two=123")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "Secure API Command" do
|
51
|
+
before do
|
52
|
+
@command = API::Command.new('cmdname', 'http', :secure => true)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should use HTTPS" do
|
56
|
+
url = @command.with_params(:param_one => 'abc', :param_two => '123')
|
57
|
+
url.should == URI.parse("https://api.clickatell.com/http/cmdname?param_one=abc¶m_two=123")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "Command executor" do
|
62
|
+
it "should create an API command with the given params" do
|
63
|
+
executor = API::CommandExecutor.new(:session_id => '12345')
|
64
|
+
executor.stubs(:get_response).returns([])
|
65
|
+
API::Command.expects(:new).with('cmdname', 'http', :secure => false).returns(command = stub('Command'))
|
66
|
+
command.expects(:with_params).with(:param_one => 'foo', :session_id => '12345').returns(stub_everything('URI'))
|
67
|
+
executor.execute('cmdname', 'http', :param_one => 'foo')
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should send the URI generated by the created command via HTTP get and return the response" do
|
71
|
+
executor = API::CommandExecutor.new(:session_id => '12345')
|
72
|
+
command_uri = URI.parse('http://clickatell.com:8080/path?foo=bar')
|
73
|
+
API::Command.stubs(:new).returns(command = stub('Command', :with_params => command_uri))
|
74
|
+
Net::HTTP.stubs(:new).with(command_uri.host, command_uri.port).returns(http = FakeHttp.new)
|
75
|
+
http.stubs(:use_ssl=)
|
76
|
+
http.stubs(:get).with('/path?foo=bar').returns([response = stub('HTTP Response'), 'response body'])
|
77
|
+
executor.execute('cmdname', 'http').should == response
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should send the command over a secure HTTPS connection if :secure option is set to true" do
|
81
|
+
executor = API::CommandExecutor.new({:session_id => '12345'}, secure = true)
|
82
|
+
Net::HTTP.stubs(:new).returns(http = mock('HTTP'))
|
83
|
+
http.expects(:use_ssl=).with(true)
|
84
|
+
http.stubs(:start).returns([])
|
85
|
+
executor.execute('cmdname', 'http')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "API" do
|
90
|
+
before do
|
91
|
+
API.debug_mode = false
|
92
|
+
API.secure_mode = false
|
93
|
+
API.test_mode = false
|
94
|
+
|
95
|
+
@executor = mock('command executor')
|
96
|
+
@api = API.new(:session_id => '1234')
|
97
|
+
API::CommandExecutor.stubs(:new).with({:session_id => '1234'}, false, false, false).returns(@executor)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should use the api_id, username and password to authenticate and return the new session id" do
|
101
|
+
@executor.expects(:execute).with('auth', 'http',
|
102
|
+
:api_id => '1234',
|
103
|
+
:user => 'joebloggs',
|
104
|
+
:password => 'superpass'
|
105
|
+
).returns(response = stub('response'))
|
106
|
+
Response.stubs(:parse).with(response).returns('OK' => 'new_session_id')
|
107
|
+
@api.authenticate('1234', 'joebloggs', 'superpass').should == 'new_session_id'
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should support ping, using the current session_id" do
|
111
|
+
@executor.expects(:execute).with('ping', 'http', :session_id => 'abcdefg').returns(response = stub('response'))
|
112
|
+
@api.ping('abcdefg').should == response
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should support sending messages to a specified number, returning the message id" do
|
116
|
+
@executor.expects(:execute).with('sendmsg', 'http',
|
117
|
+
:to => '4477791234567',
|
118
|
+
:text => 'hello world & goodbye'
|
119
|
+
).returns(response = stub('response'))
|
120
|
+
Response.stubs(:parse).with(response).returns('ID' => 'message_id')
|
121
|
+
@api.send_message('4477791234567', 'hello world & goodbye').should == 'message_id'
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should support sending messages to a multiple numbers, returning the message ids" do
|
125
|
+
@executor.expects(:execute).with('sendmsg', 'http',
|
126
|
+
:to => '4477791234567,447779999999',
|
127
|
+
:text => 'hello world & goodbye'
|
128
|
+
).returns(response = stub('response'))
|
129
|
+
Response.stubs(:parse).with(response).returns([{'ID' => 'message_1_id'}, {'ID' => 'message_2_id'}])
|
130
|
+
@api.send_message(['4477791234567', '447779999999'], 'hello world & goodbye').should == ['message_1_id', 'message_2_id']
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should set the :from parameter and set the :req_feat to 48 when using a custom from string when sending a message" do
|
134
|
+
@executor.expects(:execute).with('sendmsg', 'http', has_entries(:from => 'LUKE', :req_feat => '48')).returns(response = stub('response'))
|
135
|
+
Response.stubs(:parse).with(response).returns('ID' => 'message_id')
|
136
|
+
@api.send_message('4477791234567', 'hello world', :from => 'LUKE')
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should set the :mo flag to 1 when :set_mobile_originated is true when sending a message" do
|
140
|
+
@executor.expects(:execute).with('sendmsg', 'http', has_entry(:mo => '1')).returns(response=mock('response'))
|
141
|
+
Response.stubs(:parse).with(response).returns('ID' => 'message_id')
|
142
|
+
@api.send_message('4477791234567', 'hello world', :set_mobile_originated => true)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should set the callback flag to the number passed in the options hash" do
|
146
|
+
@executor.expects(:execute).with('sendmsg', 'http', has_entry(:callback => 1)).returns(response=mock('response'))
|
147
|
+
Response.stubs(:parse).with(response).returns('ID' => 'message_id')
|
148
|
+
@api.send_message('4477791234567', 'hello world', :callback => 1)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should ignore any invalid parameters when sending a message" do
|
152
|
+
@executor.expects(:execute).with('sendmsg', 'http', Not(has_key(:any_old_param))).returns(response = stub('response'))
|
153
|
+
Response.stubs(:parse).returns('ID' => 'foo')
|
154
|
+
@api.send_message('4477791234567', 'hello world', :from => 'LUKE', :any_old_param => 'test')
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should support message status query for a given message id, returning the message status" do
|
158
|
+
@executor.expects(:execute).with('querymsg', 'http', :apimsgid => 'messageid').returns(response = stub('response'))
|
159
|
+
Response.expects(:parse).with(response).returns('ID' => 'message_id', 'Status' => 'message_status')
|
160
|
+
@api.message_status('messageid').should == 'message_status'
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should support balance query, returning number of credits as a float" do
|
164
|
+
@executor.expects(:execute).with('getbalance', 'http', {}).returns(response=mock('response'))
|
165
|
+
Response.stubs(:parse).with(response).returns('Credit' => '10.0')
|
166
|
+
@api.account_balance.should == 10.0
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should raise an API::Error if the response parser raises" do
|
170
|
+
@executor.stubs(:execute)
|
171
|
+
Response.stubs(:parse).raises(Clickatell::API::Error.new('', ''))
|
172
|
+
proc { @api.account_balance }.should raise_error(Clickatell::API::Error)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe API, ' when authenticating' do
|
177
|
+
it "should authenticate to retrieve a session_id and return a new API instance using that session id" do
|
178
|
+
API.stubs(:new).returns(api = mock('api'))
|
179
|
+
api.stubs(:authenticate).with('my_api_key', 'joebloggs', 'mypassword').returns('new_session_id')
|
180
|
+
api.expects(:auth_options=).with(:session_id => 'new_session_id')
|
181
|
+
API.authenticate('my_api_key', 'joebloggs', 'mypassword')
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe API, ' with no authentication options set' do
|
186
|
+
it "should build commands with no authentication options" do
|
187
|
+
api = API.new
|
188
|
+
API::CommandExecutor.stubs(:new).with({}, false, false, false).returns(executor = stub('command executor'))
|
189
|
+
executor.stubs(:execute)
|
190
|
+
api.ping('1234')
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe API, ' in secure mode' do
|
195
|
+
it "should execute commands securely" do
|
196
|
+
API.secure_mode = true
|
197
|
+
api = API.new
|
198
|
+
API::CommandExecutor.expects(:new).with({}, true, false, false).returns(executor = stub('command executor'))
|
199
|
+
executor.stubs(:execute)
|
200
|
+
api.ping('1234')
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "API Error" do
|
205
|
+
it "should parse http response string to create error" do
|
206
|
+
response_string = "ERR: 001, Authentication error"
|
207
|
+
error = Clickatell::API::Error.parse(response_string)
|
208
|
+
error.code.should == '001'
|
209
|
+
error.message.should == 'Authentication error'
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe API, "#test_mode" do
|
214
|
+
before(:each) do
|
215
|
+
API.secure_mode = false
|
216
|
+
API.test_mode = true
|
217
|
+
@api = API.new
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should create a new CommandExecutor with test_mode parameter set to true" do
|
221
|
+
API::CommandExecutor.expects(:new).with({}, false, false, true).once.returns(executor = mock('command executor'))
|
222
|
+
executor.stubs(:execute)
|
223
|
+
executor.stubs(:sms_requests).returns([])
|
224
|
+
@api.ping('1234')
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should record all commands" do
|
228
|
+
@api.ping('1234')
|
229
|
+
@api.sms_requests.should_not be_empty
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should return the recorded commands in a flattened array" do
|
233
|
+
@api.ping('1234')
|
234
|
+
@api.sms_requests.size.should == 1
|
235
|
+
@api.sms_requests.first.should_not be_instance_of(Array)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|