Skip to content
This repository
tag: v3.1.4.rc1
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 217 lines (197 sloc) 10.388 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
require 'uri'
require 'active_support/core_ext/hash/diff'
require 'active_support/core_ext/hash/indifferent_access'

module ActionDispatch
  module Assertions
    # Suite of assertions to test routes generated by \Rails and the handling of requests made to them.
    module RoutingAssertions
      # Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
      # match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+.
      #
      # Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
      # requiring a specific HTTP method. The hash should contain a :path with the incoming request path
      # and a :method containing the required HTTP verb.
      #
      # # assert that POSTing to /items will call the create action on ItemsController
      # assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post})
      #
      # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
      # to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
      # extras argument, appending the query string on the path directly will not work. For example:
      #
      # # assert that a path of '/items/list/1?view=print' returns the correct options
      # assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
      #
      # The +message+ parameter allows you to pass in an error message that is displayed upon failure.
      #
      # ==== Examples
      # # Check the default route (i.e., the index action)
      # assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
      #
      # # Test a specific action
      # assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list')
      #
      # # Test an action with a parameter
      # assert_recognizes({:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1')
      #
      # # Test a custom route
      # assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
      def assert_recognizes(expected_options, path, extras={}, message=nil)
        request = recognized_request_for(path)

        expected_options = expected_options.clone
        extras.each_key { |key| expected_options.delete key } unless extras.nil?

        expected_options.stringify_keys!
        msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
            request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
        assert_equal(expected_options, request.path_parameters, msg)
      end

      # Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
      # The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in
      # a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
      #
      # The +defaults+ parameter is unused.
      #
      # ==== Examples
      # # Asserts that the default action is generated for a route with no action
      # assert_generates "/items", :controller => "items", :action => "index"
      #
      # # Tests that the list action is properly routed
      # assert_generates "/items/list", :controller => "items", :action => "list"
      #
      # # Tests the generation of a route with a parameter
      # assert_generates "/items/list/1", { :controller => "items", :action => "list", :id => "1" }
      #
      # # Asserts that the generated route gives us our custom route
      # assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" }
      def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
        if expected_path =~ %r{://}
          begin
            uri = URI.parse(expected_path)
            expected_path = uri.path.to_s.empty? ? "/" : uri.path
          rescue URI::InvalidURIError => e
            raise ActionController::RoutingError, e.message
          end
        else
          expected_path = "/#{expected_path}" unless expected_path.first == '/'
        end
        # Load routes.rb if it hasn't been loaded.

        generated_path, extra_keys = @routes.generate_extras(options, defaults)
        found_extras = options.reject {|k, v| ! extra_keys.include? k}

        msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
        assert_equal(extras, found_extras, msg)

        msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
            expected_path)
        assert_equal(expected_path, generated_path, msg)
      end

      # Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
      # <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
      # and +assert_generates+ into one step.
      #
      # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
      # +message+ parameter allows you to specify a custom error message to display upon failure.
      #
      # ==== Examples
      # # Assert a basic route: a controller with the default action (index)
      # assert_routing '/home', :controller => 'home', :action => 'index'
      #
      # # Test a route generated with a specific controller, action, and parameter (id)
      # assert_routing '/entries/show/23', :controller => 'entries', :action => 'show', :id => 23
      #
      # # Assert a basic route (controller + default action), with an error message if it fails
      # assert_routing '/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly'
      #
      # # Tests a route, providing a defaults hash
      # assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"}
      #
      # # Tests a route with a HTTP method
      # assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
      def assert_routing(path, options, defaults={}, extras={}, message=nil)
        assert_recognizes(options, path, extras, message)

        controller, default_controller = options[:controller], defaults[:controller]
        if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
          options[:controller] = "/#{controller}"
        end

        generate_options = options.dup.delete_if{ |k,v| defaults.key?(k) }
        assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
      end

      # A helper to make it easier to test different route configurations.
      # This method temporarily replaces @routes
      # with a new RouteSet instance.
      #
      # The new instance is yielded to the passed block. Typically the block
      # will create some routes using <tt>map.draw { map.connect ... }</tt>:
      #
      # with_routing do |set|
      # set.draw do |map|
      # map.connect ':controller/:action/:id'
      # assert_equal(
      # ['/content/10/show', {}],
      # map.generate(:controller => 'content', :id => 10, :action => 'show')
      # end
      # end
      # end
      #
      def with_routing
        old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new
        if defined?(@controller) && @controller
          old_controller, @controller = @controller, @controller.clone
          _routes = @routes

          # Unfortunately, there is currently an abstraction leak between AC::Base
          # and AV::Base which requires having the URL helpers in both AC and AV.
          # To do this safely at runtime for tests, we need to bump up the helper serial
          # to that the old AV subclass isn't cached.
          #
          # TODO: Make this unnecessary
          @controller.singleton_class.send(:include, _routes.url_helpers)
          @controller.view_context_class = Class.new(@controller.view_context_class) do
            include _routes.url_helpers
          end
        end
        yield @routes
      ensure
        @routes = old_routes
        if defined?(@controller) && @controller
          @controller = old_controller
        end
      end

      # ROUTES TODO: These assertions should really work in an integration context
      def method_missing(selector, *args, &block)
        if defined?(@controller) && @controller && @routes && @routes.named_routes.helpers.include?(selector)
          @controller.send(selector, *args, &block)
        else
          super
        end
      end

      private
        # Recognizes the route for a given path.
        def recognized_request_for(path)
          if path.is_a?(Hash)
            method = path[:method]
            path = path[:path]
          else
            method = :get
          end

          # Assume given controller
          request = ActionController::TestRequest.new

          if path =~ %r{://}
            begin
              uri = URI.parse(path)
              request.env["rack.url_scheme"] = uri.scheme || "http"
              request.host = uri.host if uri.host
              request.port = uri.port if uri.port
              request.path = uri.path.to_s.empty? ? "/" : uri.path
            rescue URI::InvalidURIError => e
              raise ActionController::RoutingError, e.message
            end
          else
            path = "/#{path}" unless path.first == "/"
            request.path = path
          end

          request.request_method = method if method

          params = @routes.recognize_path(path, { :method => method })
          request.path_parameters = params.with_indifferent_access

          request
        end
    end
  end
end
Something went wrong with that request. Please try again.