Permalink
Browse files

Merge pull request #1 from globaldev/master

Support for get and a few other things
  • Loading branch information...
2 parents 64ba4be + 01a91d9 commit 4d5d6111ecf84a8749030b80d1ccbae22a39229b @JMongol committed Jan 30, 2013
View
2 Gemfile.lock
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
- twilio-test-toolkit (1.0.0)
+ twilio-test-toolkit (1.0.1)
capybara
uuidtools
View
32 README.md
@@ -47,9 +47,9 @@ TTT doesn't yet support (but you should contribute them!)
What's required
================
-TTT depends on [Capybara](https://github.com/jnicklas/capybara). It uses Capybara's session object to POST requests to your controllers.
+TTT depends on [Capybara](https://github.com/jnicklas/capybara). It uses Capybara's session object to make requests to your controllers.
-TTT expects your controller actions to behave like well-behaved Twilio callbacks. That is, you need to respond to XML-formatted requests, and need to respond with a 200 OK. TTT also requires that your controller actions be wired for POST, not GET (Twilio does support GET, but TTT lacks this support). Twilio will not follow 301 or 302 redirects properly, and neither will TTT (see below for more details).
+TTT expects your controller actions to behave like well-behaved Twilio callbacks. That is, you need to respond to XML-formatted requests, and need to respond with a 200 OK. Twilio will not follow 301 or 302 redirects properly, and neither will TTT (see below for more details).
TTT has only been tested with RSpec on Rails. It might work on other test frameworks or other Rack-based frameworks. Feel free to submit pull requests to improve compatibility with these.
@@ -89,15 +89,19 @@ This section describes how to use TTT and its basic functionality.
ttt_call
-------------
-The *ttt_call* method is the main entry point for working with TTT. You call this method to initiate a "Twilio phone call" to your controller actions. TTT simulates Twilio's behavior by POSTing to your action with the expected Twilio parameters (From, To, CallSid, etc).
+The *ttt_call* method is the main entry point for working with TTT. You call this method to initiate a "Twilio phone call" to your controller actions. TTT simulates Twilio's behavior by making requests to your action with the expected Twilio parameters (From, To, CallSid, etc).
-*ttt_call* has three required parameters, and two optional ones:
+*ttt_call* has three required parameters and an options hash:
- @call = ttt_call(action_path, from_number, to_number, call_sid = nil, is_machine = false)
+ @call = ttt_call(action_path, from_number, to_number, options = {})
-* **action_path**. Where to POST your request. It should be obvious by now, but whatever action you specify here should be a POST action.
+* **action_path**. Where to make your request. By default this will be a POST, but you can override it with the options hash.
* **from_number**. What to fill params[:From] with. If you don't care about this value, you can pass a blank one, as this is only used to pass along to your actions.
* **to_number**. What to fill params[:To] with.
+
+Options are:
+
+* **method** . Specify the http method of the initial request. By default this will be :post.
* **call_sid**. Specify an optional fixed value to be passed as params[:CallSid]. This is useful if you are expecting a specific SID. For instance, a common pattern is to initiate a call, store the SID in your database, and look up the call when you get the callback. If you don't pass a SID, TTT will generate one for you that's just a UUID.
* **is_machine**. Controls params[:AnsweredBy]. See Twilio's documentation for more information on how Twilio uses this.
@@ -155,7 +159,7 @@ These methods are available on any CallScope.
Gathers
--------------
-Gathers are used to collect digits from the caller (e.g. "press 1 to speak with a representative, press 2 to cancel"). Twilio handles Gathers by speaking the contents of the Gather and waiting for a digit or digits to be pressed. If nothing is pressed, it continues with the script. If something is pressed, it aborts processing the current script and POSTs the digits (via params[:Digits]) to the path specified by the action attribute. TTT handles Gathers in a similar way.
+Gathers are used to collect digits from the caller (e.g. "press 1 to speak with a representative, press 2 to cancel"). Twilio handles Gathers by speaking the contents of the Gather and waiting for a digit or digits to be pressed. If nothing is pressed, it continues with the script. If something is pressed, it aborts processing the current script and makes a request with the digits (via params[:Digits]) to the path specified by the action attribute. If no method is specified in the options, this will be a post TTT handles Gathers in a similar way.
You can only interact with a gather by calling *within_gather*:
@@ -178,7 +182,7 @@ Within a gather CallScope, you can use the following methods:
You can also use other CallScope methods (e.g. *has_say?* and similar.)
-The *press* method has a few caveats worth knowing. It's only callable once per gather - when you call it, TTT will immediately POST to the gather's action. There is no way to simulate pressing buttons slowly, and you don't really need to do this anyways - Twilio doesn't care and just passes them all at once. *press* simply fills out the value of params[:Digits] and POSTs that to your method, just like Twilio does.
+The *press* method has a few caveats worth knowing. It's only callable once per gather - when you call it, TTT will immediately call the gather's action. There is no way to simulate pressing buttons slowly, and you don't really need to do this anyways - Twilio doesn't care and just passes them all at once. *press* simply fills out the value of params[:Digits] and calls your method, just like Twilio does.
Although you can technically pass whatever you want to *press*, in practice Twilio only sends digits and #. Still it's probably a good idea to test garbage data in this parameter with your actions, so TTT doesn't get in your way if you want to call press with "UNICORNSANDPONIES" as a parameter.
@@ -187,7 +191,7 @@ TTT doesn't attempt to validate your TwiML, so it's worth knowing that Gather on
Redirects
--------------
-The Redirect element is used to tell Twilio to POST to a different page. It differs from a standard 301 or 302 redirect (created by a *redirect_to*) in that the 301/302 redirects don't support a POST, and if you try a *redirect_to* within a Twilio action, Twilio will fail and your caller will get the dreaded "I'm sorry, an application error has occurred" message. Because Twilio doesn't support 301/302 redirects, TTT doesn't either, and if you use one, TTT will complain.
+The Redirect element is used to tell Twilio to call a different page. It differs from a standard 301 or 302 redirect (created by a *redirect_to*) in that the 301/302 redirects don't support a POST, and if you try a *redirect_to* within a Twilio action, Twilio will fail and your caller will get the dreaded "I'm sorry, an application error has occurred" message. Because Twilio doesn't support 301/302 redirects, TTT doesn't either, and if you use one, TTT will complain.
There are several methods you can use within a CallScope related to redirects:
@@ -207,7 +211,6 @@ Contributing
TTT is pretty basic, but it should work for most people's needs. You might consider helping to improve it. Some things that could be done:
* Support more Twilio functionality
-* Support GET actions. Twilio technically supports this, although it doesn't seem to be used very often.
* Build more stringent checks into says and plays - e.g. verify that there's enough of a pause between sentences.
* Build checks to make sure that numbers, dates, and other special text is spoken properly (e.g. "one two three" instead of "one hundred twenty three").
* Build checks to validate the correctness of your TwiML.
@@ -217,6 +220,15 @@ TTT is pretty basic, but it should work for most people's needs. You might consi
Contributions are welcome and encouraged. The usual deal applies - fork a branch, add tests, add your changes, submit a pull request. If I haven't done anything with your pull request in a reasonable amount of time, ping me on Twitter or email and I'll get on it.
+Running Tests
+----------------
+
+ bundle install
+ cd spec/dummy
+ bundle exec rake db:create
+ cd ../..
+ bundle exec rspec
+
Credits
================
View
22 lib/twilio-test-toolkit/call_in_progress.rb
@@ -3,26 +3,30 @@
module TwilioTestToolkit
# Models a call
class CallInProgress < CallScope
- # Init the call
- def initialize(initial_path, from_number, to_number, call_sid, is_machine)
+ # Initiate a call. Options:
+ # * :method - specify the http method of the initial api call
+ # * :call_sid - specify an optional fixed value to be passed as params[:CallSid]
+ # * :is_machine - controls params[:AnsweredBy]
+ def initialize(initial_path, from_number, to_number, options = {})
# Save our variables for later
@initial_path = initial_path
@from_number = from_number
@to_number = to_number
- @is_machine = is_machine
+ @is_machine = options[:is_machine]
+ @method = options[:method] || :post
# Generate an initial call SID if we don't have one
- if (call_sid.nil?)
+ if (options[:call_sid].nil?)
@sid = UUIDTools::UUID.random_create.to_s
else
- @sid = call_sid
+ @sid = options[:call_sid]
end
# We are the root call
self.root_call = self
# Create the request
- post_for_twiml!(@initial_path, "", @is_machine)
+ request_for_twiml!(@initial_path, :method => @method, :is_machine => @is_machine)
end
def sid
@@ -44,5 +48,9 @@ def to_number
def is_machine
@is_machine
end
+
+ def method
+ @method
+ end
end
-end
+end
View
33 lib/twilio-test-toolkit/call_scope.rb
@@ -12,14 +12,14 @@ def follow_redirect
el = get_redirect_node
raise "No redirect" if el.nil?
- return CallScope.from_post(self, el.text)
+ return CallScope.from_request(self, el.text, :method =>el[:method])
end
def follow_redirect!
el = get_redirect_node
raise "No redirect" if el.nil?
- post_for_twiml!(normalize_redirect_path(el.text))
+ request_for_twiml!(normalize_redirect_path(el.text), :method => el[:method])
end
# Stuff for Says
@@ -66,18 +66,23 @@ def gather?
end
def gather_action
- rasie "Not a gather" unless gather?
+ raise "Not a gather" unless gather?
return @xml["action"]
end
+ def gather_method
+ raise "Not a gather" unless gather?
+ return @xml["method"]
+ end
+
def press(digits)
raise "Not a gather" unless gather?
# Fetch the path and then post
path = gather_action
# Update the root call
- root_call.post_for_twiml!(path, digits)
+ root_call.request_for_twiml!(path, :digits => digits, :method => gather_method)
end
# Some basic accessors
@@ -115,11 +120,13 @@ def set_xml(xml)
@xml = xml
end
- # Create a new object from a post
- def self.from_post(parent, path, digits = "")
+ # Create a new object from a post. Options:
+ # * :method - the http method of the request, defaults to :post
+ # * :digits - becomes params[:Digits], defaults to ""
+ def self.from_request(parent, path, options = {})
new_scope = CallScope.new
new_scope.send(:root_call=, parent.root_call)
- new_scope.send(:post_for_twiml!, path, digits)
+ new_scope.send(:request_for_twiml!, path, :digits => options[:digits] || "", :method => options[:method] || :post)
return new_scope
end
@@ -131,19 +138,21 @@ def normalize_redirect_path(path)
return p
end
- # Post and update the scope
- def post_for_twiml!(path, digits = "", is_machine = false)
+ # Post and update the scope. Options:
+ # :digits - becomes params[:Digits], optional (becomes "")
+ # :is_machine - becomes params[:AnsweredBy], defaults to false / human
+ def request_for_twiml!(path, options = {})
@current_path = normalize_redirect_path(path)
# Post the query
rack_test_session_wrapper = Capybara.current_session.driver
- @response = rack_test_session_wrapper.post(@current_path,
+ @response = rack_test_session_wrapper.send(options[:method] || :post, @current_path,
:format => :xml,
:CallSid => @root_call.sid,
- :Digits => digits,
:From => @root_call.from_number,
+ :Digits => options[:digits] || "",
:To => @root_call.to_number,
- :AnsweredBy => (is_machine ? "machine" : "human")
+ :AnsweredBy => (options[:is_machine] ? "machine" : "human")
)
# All Twilio responses must be a success.
View
11 lib/twilio-test-toolkit/dsl.rb
@@ -2,11 +2,14 @@ module TwilioTestToolkit
require 'twilio-test-toolkit/call_in_progress'
# Adds the `ttt_call` method to the top-level namespace.
- module DSL
- # call = ttt_call(inbound_phone_index_path, "+12065551212")
- def ttt_call(initial_path, from_number, to_number, call_sid = nil, is_machine = false)
+ module DSL
+ # Initiate a call. Options:
+ # * :method - specify the http method of the initial api call
+ # * :call_sid - specify an optional fixed value to be passed as params[:CallSid]
+ # * :is_machine - controls params[:AnsweredBy]
+ def ttt_call(initial_path, from_number, to_number, options = {})
# Make a new call in progress
- return CallInProgress.new(initial_path, from_number, to_number, call_sid, is_machine)
+ return CallInProgress.new(initial_path, from_number, to_number, options)
end
end
end
View
2 lib/twilio-test-toolkit/version.rb
@@ -1,3 +1,3 @@
module TwilioTestToolkit
- VERSION = "1.0.0"
+ VERSION = "1.0.1"
end
View
2 spec/dummy/app/controllers/twilio_controller.rb
@@ -2,7 +2,7 @@ class TwilioController < ApplicationController
layout "twilio.layout"
respond_to :xml
- def testaction
+ def test_action
@digits = params[:Digits]
end
end
View
0 ...dummy/app/views/twilio/testaction.xml.erb → ...ummy/app/views/twilio/test_action.xml.erb
File renamed without changes.
View
0 spec/dummy/app/views/twilio/testdial.xml.erb → .../dummy/app/views/twilio/test_dial.xml.erb
File renamed without changes.
View
0 ...dummy/app/views/twilio/testhangup.xml.erb → ...ummy/app/views/twilio/test_hangup.xml.erb
File renamed without changes.
View
1 spec/dummy/app/views/twilio/test_redirect.xml.erb
@@ -0,0 +1 @@
+<Redirect method="post"><%= test_start_twilio_index_path %></Redirect>
View
0 spec/dummy/app/views/twilio/testsay.xml.erb → spec/dummy/app/views/twilio/test_say.xml.erb
File renamed without changes.
View
5 spec/dummy/app/views/twilio/test_start.xml.erb
@@ -0,0 +1,5 @@
+<Say voice="woman">Hi there.</Say>
+
+<Gather action="<%= test_action_twilio_index_path %>" method="post">
+ <Say>Please enter some digits.</Say>
+</Gather>
View
1 spec/dummy/app/views/twilio/testredirect.xml.erb
@@ -1 +0,0 @@
-<Redirect><%= teststart_twilio_index_path %></Redirect>
View
5 spec/dummy/app/views/twilio/teststart.xml.erb
@@ -1,5 +0,0 @@
-<Say voice="woman">Hi there.</Say>
-
-<Gather action="<%= testaction_twilio_index_path %>">
- <Say>Please enter some digits.</Say>
-</Gather>
View
13 spec/dummy/config/routes.rb
@@ -1,11 +1,12 @@
Dummy::Application.routes.draw do
resources :twilio do
- post "teststart", :on => :collection
- post "testaction", :on => :collection
+ get "test_start", :on => :collection
+ post "test_start", :on => :collection
+ post "test_action", :on => :collection
- post "testhangup", :on => :collection
- post "testdial", :on => :collection
- post "testredirect", :on => :collection
- post "testsay", :on => :collection
+ post "test_hangup", :on => :collection
+ post "test_dial", :on => :collection
+ post "test_redirect", :on => :collection
+ post "test_say", :on => :collection
end
end
View
0 spec/dummy/db/.gitkeep
No changes.
View
40 spec/requests/call_scope_spec.rb
@@ -8,7 +8,7 @@
describe "basics" do
before(:each) do
- @call = ttt_call(teststart_twilio_index_path, @our_number, @their_number)
+ @call = ttt_call(test_start_twilio_index_path, @our_number, @their_number)
end
it "should be a CallScope" do
@@ -21,7 +21,7 @@
end
it "should have the right path" do
- @call.current_path.should == teststart_twilio_index_path
+ @call.current_path.should == test_start_twilio_index_path
end
it "should have a response xml value" do
@@ -37,7 +37,7 @@
describe "redirect" do
describe "success" do
before(:each) do
- @call = ttt_call(testredirect_twilio_index_path, @our_number, @their_number)
+ @call = ttt_call(test_redirect_twilio_index_path, @our_number, @their_number)
end
it "should have the redirect methods" do
@@ -53,39 +53,39 @@
it "should have the right values for has_redirect_to?" do
@call.has_redirect_to?("http://foo").should be_false
- @call.has_redirect_to?(teststart_twilio_index_path).should be_true
- @call.has_redirect_to?(teststart_twilio_index_path + ".xml").should be_true # Should force normalization
+ @call.has_redirect_to?(test_start_twilio_index_path).should be_true
+ @call.has_redirect_to?(test_start_twilio_index_path + ".xml").should be_true # Should force normalization
end
it "should follow the redirect (immutable version)" do
# follow_redirect returns a new CallScope
newcall = @call.follow_redirect
# Make sure it followed
- newcall.current_path.should == teststart_twilio_index_path
+ newcall.current_path.should == test_start_twilio_index_path
# And is not the same call
newcall.response_xml.should_not == @call.response_xml
# But it's linked
newcall.root_call.should == @call
# And we did not modify the original call
- @call.current_path.should == testredirect_twilio_index_path
+ @call.current_path.should == test_redirect_twilio_index_path
end
it "should follow the redirect (mutable version)" do
# follow_redirect! modifies the CallScope
@call.follow_redirect!
# Make sure it followed
- @call.current_path.should == teststart_twilio_index_path
+ @call.current_path.should == test_start_twilio_index_path
end
end
describe "failure" do
before(:each) do
# Initiate a call that's not on a redirect - various calls will fail
- @call = ttt_call(testsay_twilio_index_path, @our_number, @their_number)
+ @call = ttt_call(test_say_twilio_index_path, @our_number, @their_number)
end
it "should have the right value for has_redirect?" do
@@ -94,8 +94,8 @@
it "should have the right values for has_redirect_to?" do
@call.has_redirect_to?("http://foo").should be_false
- @call.has_redirect_to?(teststart_twilio_index_path).should be_false
- @call.has_redirect_to?(teststart_twilio_index_path + ".xml").should be_false
+ @call.has_redirect_to?(test_start_twilio_index_path).should be_false
+ @call.has_redirect_to?(test_start_twilio_index_path + ".xml").should be_false
end
it "should raise an error on follow_redirect" do
@@ -110,7 +110,7 @@
describe "say" do
before(:each) do
- @call = ttt_call(testsay_twilio_index_path, @our_number, @their_number)
+ @call = ttt_call(test_say_twilio_index_path, @our_number, @their_number)
end
it "should have the expected say methods" do
@@ -126,7 +126,7 @@
describe "dial" do
before(:each) do
- @call = ttt_call(testdial_twilio_index_path, @our_number, @their_number)
+ @call = ttt_call(test_dial_twilio_index_path, @our_number, @their_number)
end
it "should have the expected dial methods" do
@@ -143,7 +143,7 @@
describe "hangup" do
describe "success" do
before(:each) do
- @call = ttt_call(testhangup_twilio_index_path, @our_number, @their_number)
+ @call = ttt_call(test_hangup_twilio_index_path, @our_number, @their_number)
end
it "should have the expected hangup methods" do
@@ -157,7 +157,7 @@
describe "failure" do
before(:each) do
- @call = ttt_call(teststart_twilio_index_path, @our_number, @their_number)
+ @call = ttt_call(test_start_twilio_index_path, @our_number, @their_number)
end
it "should have the right value for has_hangup?" do
@@ -169,7 +169,7 @@
describe "gather" do
describe "success" do
before(:each) do
- @call = ttt_call(teststart_twilio_index_path, @our_number, @their_number)
+ @call = ttt_call(test_start_twilio_index_path, @our_number, @their_number)
end
it "should have the expected gather methods" do
@@ -206,7 +206,7 @@
# We should be in a gather
gather.gather?.should be_true
# And we should have an action
- gather.gather_action.should == testaction_twilio_index_path
+ gather.gather_action.should == test_action_twilio_index_path
# And we should have the right root call
gather.root_call.should == @call
@@ -216,7 +216,7 @@
end
# This should update the path
- @call.current_path.should == testaction_twilio_index_path
+ @call.current_path.should == test_action_twilio_index_path
# This view says the digits we pressed - make sure
@call.should have_say "You entered 98765"
@@ -228,13 +228,13 @@
end
# We should still be on the same page
- @call.current_path.should == teststart_twilio_index_path
+ @call.current_path.should == test_start_twilio_index_path
end
end
describe "failure" do
before(:each) do
- @call = ttt_call(testsay_twilio_index_path, @our_number, @their_number)
+ @call = ttt_call(test_say_twilio_index_path, @our_number, @their_number)
end
it "should have the right value for has_gather?" do
View
16 spec/requests/dsl_spec.rb
@@ -9,7 +9,7 @@
describe "ttt_call" do
describe "basics" do
before(:each) do
- @call = ttt_call(teststart_twilio_index_path, @our_number, @their_number)
+ @call = ttt_call(test_start_twilio_index_path, @our_number, @their_number)
end
it "should assign the call" do
@@ -20,18 +20,22 @@
@call.sid.should_not be_blank
end
+ it "should default the method to post" do
+ @call.method.should == :post
+ end
+
it "should have the right properties" do
- @call.initial_path.should == teststart_twilio_index_path
+ @call.initial_path.should == test_start_twilio_index_path
@call.from_number.should == @our_number
@call.to_number.should == @their_number
@call.is_machine.should be_false
end
end
- describe "with a sid and machine override" do
+ describe "with a sid, method and machine override" do
before(:each) do
@mysid = "1234567"
- @call = ttt_call(teststart_twilio_index_path, @our_number, @their_number, @mysid, true)
+ @call = ttt_call(test_start_twilio_index_path, @our_number, @their_number, :call_sid => @mysid, :is_machine => true, :method => :get)
end
it "should have the right sid" do
@@ -41,6 +45,10 @@
it "should be a machine call" do
@call.is_machine.should be_true
end
+
+ it "should be a get call" do
+ @call.method.should == :get
+ end
end
end
end

0 comments on commit 4d5d611

Please sign in to comment.