clickatell-ruby19 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.
@@ -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,6 @@
1
+ module Clickatell
2
+ module Utility
3
+ end
4
+ end
5
+
6
+ require File.join(File.dirname(__FILE__), *%w[utility/options])
@@ -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,13 @@
1
+ module Clickatell #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 7
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+
9
+ def self.to_s
10
+ STRING
11
+ end
12
+ end
13
+ 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&param_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&param_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&param_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&param_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&param_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&param_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