require 'rack/utils' module Spec module Rails module Matchers USAGE = ArgumentError.new( 'usage: { :method => "path" }.should route_to( :controller => "controller", :action => "action", [ args ] )' ) class PathDecomposer def self.decompose_path(path) method, path = if Hash === path raise USAGE if path.keys.size > 1 path.entries.first else [:get, path] end path, querystring = path.split('?') return method, path, querystring end end class RouteTo #:nodoc: def initialize(expected, example) @route, @example = expected,example end def matches?(path) begin @actual = path method, path, querystring = PathDecomposer.decompose_path(path) params = querystring.blank? ? {} : Rack::Utils.parse_query(querystring).symbolize_keys! @example.assert_routing({ :method => method, :path => path }, @route, {}, params) true rescue ActionController::RoutingError, ::Test::Unit::AssertionFailedError, ActionController::MethodNotAllowed => e raise e.class, "#{e}\nIf you're expecting this failure, we suggest { :#{method} => \"#{path}\" }.should_not be_routable" rescue Exception => e raise e.class, "#{e}\n#{e.backtrace.join( "\n" )}" end end def does_not_match(path) raise ArgumentError, "Don't test a negative route like this." end def failure_message_for_should "Expected #{@expected.inspect} to route to #{@actual.inspect}, but it didn't.\n"+ "In this case, we expected you to get an exception. So this message probably means something weird happened." end def failure_message_for_should_not "Expected a routing error, but the route passed instead. \nNote, when expecting routes to fail, you should use 'should_not be_routable' instead." end def description "route to #{@expected.inspect}" end private attr_reader :expected attr_reader :actual end # :call-seq: # "path".should route_to(expected) # assumes GET # { :get => "path" }.should route_to(expected) # { :put => "path" }.should route_to(expected) # # Uses ActionController::Routing::Routes to verify that # the path-and-method routes to a given set of options. # Also verifies route-generation, so that the expected options # do generate a pathname consisten with the indicated path/method. # # For negative tests, only the route recognition failure can be # tested; since route generation via path_to() will always generate # a path as requested. Use .should_not be_routable() in this case. # # == Examples # { :get => '/registrations/1/edit' }. # should route_to(:controller => 'registrations', :action => 'edit', :id => '1') # { :put => "/registrations/1" }.should # route_to(:controller => 'registrations', :action => 'update', :id => 1) # { :post => "/registrations/" }.should # route_to(:controller => 'registrations', :action => 'create') def route_to(expected) RouteTo.new(expected, self) end class BeRoutable def initialize(example) @example = example end def matches?(path) begin @actual = path method, path = PathDecomposer.decompose_path(path) @example.assert_recognizes({}, { :method => method, :path => path }, {} ) true rescue ActionController::RoutingError, ActionController::MethodNotAllowed false rescue ::Test::Unit::AssertionFailedError => e # the second thingy will always be "<{}>" becaues of the way we called assert_recognizes({}...) above. e.to_s =~ /<(.*)> did not match <\{\}>/m and @actual_place = $1 or raise true end end def failure_message_for_should "Expected '#{@actual.keys.first.to_s.upcase} #{@actual.values.first}' to be routable, but it wasn't.\n"+ "To really test routability, we recommend #{@actual.inspect}.\n"+ " should route_to( :action => 'action', :controller => 'controller' )\n\n"+ "That way, you'll verify where your route goes to. Plus, we'll verify\n"+ "the generation of the expected path from the action/controller, as in\n"+ "the url_for() helper." end def failure_message_for_should_not "Expected '#{@actual.keys.first.to_s.upcase} #{@actual.values.first}' to fail, but it routed to #{@actual_place} instead" end end # :call-seq: # { "path" }.should_not be_routable # assumes GET # { :get => "path" }.should_not be_routable # { :put => "path" }.should_not be_routable # # Uses ActionController::Routing::Routes to verify that # the path-and-method cannot be routed to a controller. # Since url_for() will always generate a path, even if that # path is not routable, the negative test only needs to be # performed on the route recognition. # # Don't use this matcher for testing expected routability - # use .should route_to( :controller => "controller", :action => "action" ) instead # # == Examples # { :get => '/registrations/1/attendees/3/edit' }.should_not be_routable # { :get => '/attendees/3/edit' }.should route_to( ...... ) def be_routable BeRoutable.new(self) end alias_method :be_routeable, :be_routable end end end