diff options
Diffstat (limited to 'actionpack')
57 files changed, 988 insertions, 3230 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 44b8fa843d..e2731d0ee5 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,87 @@ +* `ActionController::Parameters` will stop inheriting from `Hash` and + `HashWithIndifferentAccess` in the next major release. If you use any method + that is not available on `ActionController::Parameters` you should consider + calling `#to_h` to convert it to a `Hash` first before calling that method. + + *Prem Sichanugrist* + +* `ActionController::Parameters#to_h` now returns a `Hash` with unpermitted + keys removed. This change is to reflect on a security concern where some + method performed on an `ActionController::Parameters` may yield a `Hash` + object which does not maintain `permitted?` status. If you would like to + get a `Hash` with all the keys intact, duplicate and mark it as permitted + before calling `#to_h`. + + params = ActionController::Parameters.new({ + name: 'Senjougahara Hitagi', + oddity: 'Heavy stone crab' + }) + params.to_h + # => {} + + unsafe_params = params.dup.permit! + unsafe_params.to_h + # => {"name"=>"Senjougahara Hitagi", "oddity"=>"Heavy stone crab"} + + safe_params = params.permit(:name) + safe_params.to_h + # => {"name"=>"Senjougahara Hitagi"} + + This change is consider a stopgap as we cannot change the code to stop + `ActionController::Parameters` to inherit from `HashWithIndifferentAccess` + in the next minor release. + + *Prem Sichanugrist* + +* Deprecated TagAssertions. + + *Kasper Timm Hansen* + +* Use the Active Support JSON encoder for cookie jars using the `:json` or + `:hybrid` serializer. This allows you to serialize custom Ruby objects into + cookies by defining the `#as_json` hook on such objects. + + Fixes #16520. + + *Godfrey Chan* + +* Add `config.action_dispatch.cookies_digest` option for setting custom + digest. The default remains the same - 'SHA1'. + + *Łukasz Strzałkowski* + +* Move `respond_with` (and the class-level `respond_to`) to + the `responders` gem. + + *José Valim* + +* When your templates change, browser caches bust automatically. + + New default: the template digest is automatically included in your ETags. + When you call `fresh_when @post`, the digest for `posts/show.html.erb` + is mixed in so future changes to the HTML will blow HTTP caches for you. + This makes it easy to HTTP-cache many more of your actions. + + If you render a different template, you can now pass the `:template` + option to include its digest instead: + + fresh_when @post, template: 'widgets/show' + + Pass `template: false` to skip the lookup. To turn this off entirely, set: + + config.action_controller.etag_with_template_digest = false + + *Jeremy Kemper* + +* Remove deprecated `AbstractController::Helpers::ClassMethods::MissingHelperError` + in favor of `AbstractController::Helpers::MissingHelperError`. + + *Yves Senn* + +* Fix `assert_template` not being able to assert that no files were rendered. + + *Guo Xiang Tan* + * Extract source code for the entire exception stack trace for better debugging and diagnosis. diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 7eab972595..d12213a2d8 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -40,27 +40,9 @@ task :release => :package do end task :lines do - lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 - - FileList["lib/**/*.rb"].each do |file_name| - next if file_name =~ /vendor/ - File.open(file_name, 'r') do |f| - while line = f.gets - lines += 1 - next if line =~ /^\s*$/ - next if line =~ /^\s*#/ - codelines += 1 - end - end - puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}" - - total_lines += lines - total_codelines += codelines - - lines, codelines = 0, 0 - end - - puts "Total: Lines #{total_lines}, LOC #{total_codelines}" + load File.expand_path('..', File.dirname(__FILE__)) + '/tools/line_statistics' + files = FileList["lib/**/*.rb"] + CodeTools::LineStatistics.new(files).print_loc end rule '.rb' => '.y' do |t| diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index d509891fe3..722e874c7e 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,8 +21,9 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version - s.add_dependency 'rack', '~> 1.6.0.alpha' + s.add_dependency 'rack', '~> 1.6.0.beta' s.add_dependency 'rack-test', '~> 0.6.2' + s.add_dependency 'rails-deprecated_sanitizer' s.add_dependency 'actionview', version s.add_development_dependency 'activemodel', version diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index e77e4e01e9..95c67d482b 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -27,9 +27,6 @@ module AbstractController end module ClassMethods - MissingHelperError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('AbstractController::Helpers::ClassMethods::MissingHelperError', - 'AbstractController::Helpers::MissingHelperError') - # When a class is inherited, wrap its helper module in a new module. # This ensures that the parent class's module can be changed # independently of the child class's. diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 50bc26a80f..7f1aeafe8b 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -17,6 +17,7 @@ module ActionController autoload :ConditionalGet autoload :Cookies autoload :DataStreaming + autoload :EtagWithTemplateDigest autoload :Flash autoload :ForceSSL autoload :Head diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index e6fe6b0b00..7bbf938987 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -213,6 +213,7 @@ module ActionController Rendering, Renderers::All, ConditionalGet, + EtagWithTemplateDigest, RackDelegation, Caching, MimeResponds, diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 12d798d0c1..de85e0c1a7 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -16,7 +16,7 @@ module ActionController # All the caching stores from ActiveSupport::Cache are available to be used as backends # for Action Controller caching. # - # Configuration examples (MemoryStore is the default): + # Configuration examples (FileStore is the default): # # config.action_controller.cache_store = :memory_store # config.action_controller.cache_store = :file_store, '/path/to/cache/directory' diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 6e0cd51d8b..a93727df90 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -41,6 +41,11 @@ module ActionController # * <tt>:last_modified</tt>. # * <tt>:public</tt> By default the Cache-Control header is private, set this to # +true+ if you want your application to be cachable by other devices (proxy caches). + # * <tt>:template</tt> By default, the template digest for the current + # controller/action is included in ETags. If the action renders a + # different template, you can include its digest instead. If the action + # doesn't render a template at all, you can pass <tt>template: false</tt> + # to skip any attempt to check for a template digest. # # === Example: # @@ -66,18 +71,24 @@ module ActionController # @article = Article.find(params[:id]) # fresh_when(@article, public: true) # end + # + # When rendering a different template than the default controller/action + # style, you can indicate which digest to include in the ETag: + # + # before_action { fresh_when @article, template: 'widgets/show' } + # def fresh_when(record_or_options, additional_options = {}) if record_or_options.is_a? Hash options = record_or_options - options.assert_valid_keys(:etag, :last_modified, :public) + options.assert_valid_keys(:etag, :last_modified, :public, :template) else record = record_or_options options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options) end - response.etag = combine_etags(options[:etag]) if options[:etag] - response.last_modified = options[:last_modified] if options[:last_modified] - response.cache_control[:public] = true if options[:public] + response.etag = combine_etags(options) if options[:etag] || options[:template] + response.last_modified = options[:last_modified] if options[:last_modified] + response.cache_control[:public] = true if options[:public] head :not_modified if request.fresh?(response) end @@ -93,6 +104,11 @@ module ActionController # * <tt>:last_modified</tt>. # * <tt>:public</tt> By default the Cache-Control header is private, set this to # +true+ if you want your application to be cachable by other devices (proxy caches). + # * <tt>:template</tt> By default, the template digest for the current + # controller/action is included in ETags. If the action renders a + # different template, you can include its digest instead. If the action + # doesn't render a template at all, you can pass <tt>template: false</tt> + # to skip any attempt to check for a template digest. # # === Example: # @@ -133,6 +149,14 @@ module ActionController # end # end # end + # + # When rendering a different template than the default controller/action + # style, you can indicate which digest to include in the ETag: + # + # def show + # super if stale? @article, template: 'widgets/show' + # end + # def stale?(record_or_options, additional_options = {}) fresh_when(record_or_options, additional_options) !request.fresh?(response) @@ -168,8 +192,9 @@ module ActionController end private - def combine_etags(etag) - [ etag, *etaggers.map { |etagger| instance_exec(&etagger) }.compact ] + def combine_etags(options) + etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact + etags.unshift options[:etag] end end end diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb new file mode 100644 index 0000000000..3ca0c6837a --- /dev/null +++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb @@ -0,0 +1,50 @@ +module ActionController + # When our views change, they should bubble up into HTTP cache freshness + # and bust browser caches. So the template digest for the current action + # is automatically included in the ETag. + # + # Enabled by default for apps that use Action View. Disable by setting + # + # config.action_controller.etag_with_template_digest = false + # + # Override the template to digest by passing `:template` to `fresh_when` + # and `stale?` calls. For example: + # + # # We're going to render widgets/show, not posts/show + # fresh_when @post, template: 'widgets/show' + # + # # We're not going to render a template, so omit it from the ETag. + # fresh_when @post, template: false + # + module EtagWithTemplateDigest + extend ActiveSupport::Concern + + include ActionController::ConditionalGet + + included do + class_attribute :etag_with_template_digest + self.etag_with_template_digest = true + + ActiveSupport.on_load :action_view, yield: true do |action_view_base| + etag do |options| + determine_template_etag(options) if etag_with_template_digest + end + end + end + + private + def determine_template_etag(options) + if template = pick_template_for_etag(options) + lookup_and_digest_template(template) + end + end + + def pick_template_for_etag(options) + options.fetch(:template) { "#{controller_name}/#{action_name}" } + end + + def lookup_and_digest_template(template) + ActionView::Digestor.digest name: template, finder: lookup_context + end + end +end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 00e7e980f8..dc572f13d2 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -5,56 +5,22 @@ module ActionController #:nodoc: module MimeResponds extend ActiveSupport::Concern - included do - class_attribute :responder, :mimes_for_respond_to - self.responder = ActionController::Responder - clear_respond_to - end - module ClassMethods - # Defines mime types that are rendered by default when invoking - # <tt>respond_with</tt>. - # - # respond_to :html, :xml, :json - # - # Specifies that all actions in the controller respond to requests - # for <tt>:html</tt>, <tt>:xml</tt> and <tt>:json</tt>. - # - # To specify on per-action basis, use <tt>:only</tt> and - # <tt>:except</tt> with an array of actions or a single action: - # - # respond_to :html - # respond_to :xml, :json, except: [ :edit ] - # - # This specifies that all actions respond to <tt>:html</tt> - # and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and - # <tt>:json</tt>. - # - # respond_to :json, only: :create - # - # This specifies that the <tt>:create</tt> action and no other responds - # to <tt>:json</tt>. - def respond_to(*mimes) - options = mimes.extract_options! - - only_actions = Array(options.delete(:only)).map(&:to_s) - except_actions = Array(options.delete(:except)).map(&:to_s) - - new = mimes_for_respond_to.dup - mimes.each do |mime| - mime = mime.to_sym - new[mime] = {} - new[mime][:only] = only_actions unless only_actions.empty? - new[mime][:except] = except_actions unless except_actions.empty? - end - self.mimes_for_respond_to = new.freeze + def respond_to(*) + raise NoMethodError, "The controller-level `respond_to' feature has " \ + "been extracted to the `responders` gem. Add it to your Gemfile to " \ + "continue using this feature:\n" \ + " gem 'responders', '~> 2.0'\n" \ + "Consult the Rails upgrade guide for details." end + end - # Clear all mime types in <tt>respond_to</tt>. - # - def clear_respond_to - self.mimes_for_respond_to = Hash.new.freeze - end + def respond_with(*) + raise NoMethodError, "The `respond_with' feature has been extracted " \ + "to the `responders` gem. Add it to your Gemfile to continue using " \ + "this feature:\n" \ + " gem 'responders', '~> 2.0'\n" \ + "Consult the Rails upgrade guide for details." end # Without web-service support, an action which collects the data for displaying a list of people @@ -217,7 +183,7 @@ module ActionController #:nodoc: # format.html.phone { redirect_to progress_path } # format.html.none { render "trash" } # end - # + # # Variants also support common `any`/`all` block that formats have. # # It works for both inline: @@ -253,189 +219,13 @@ module ActionController #:nodoc: def respond_to(*mimes, &block) raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? - if collector = retrieve_collector_from_mimes(mimes, &block) - response = collector.response - response ? response.call : render({}) - end - end - - # For a given controller action, respond_with generates an appropriate - # response based on the mime-type requested by the client. - # - # If the method is called with just a resource, as in this example - - # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json - # - # def index - # @people = Person.all - # respond_with @people - # end - # end - # - # then the mime-type of the response is typically selected based on the - # request's Accept header and the set of available formats declared - # by previous calls to the controller's class method +respond_to+. Alternatively - # the mime-type can be selected by explicitly setting <tt>request.format</tt> in - # the controller. - # - # If an acceptable format is not identified, the application returns a - # '406 - not acceptable' status. Otherwise, the default response is to render - # a template named after the current action and the selected format, - # e.g. <tt>index.html.erb</tt>. If no template is available, the behavior - # depends on the selected format: - # - # * for an html response - if the request method is +get+, an exception - # is raised but for other requests such as +post+ the response - # depends on whether the resource has any validation errors (i.e. - # assuming that an attempt has been made to save the resource, - # e.g. by a +create+ action) - - # 1. If there are no errors, i.e. the resource - # was saved successfully, the response +redirect+'s to the resource - # i.e. its +show+ action. - # 2. If there are validation errors, the response - # renders a default action, which is <tt>:new</tt> for a - # +post+ request or <tt>:edit</tt> for +patch+ or +put+. - # Thus an example like this - - # - # respond_to :html, :xml - # - # def create - # @user = User.new(params[:user]) - # flash[:notice] = 'User was successfully created.' if @user.save - # respond_with(@user) - # end - # - # is equivalent, in the absence of <tt>create.html.erb</tt>, to - - # - # def create - # @user = User.new(params[:user]) - # respond_to do |format| - # if @user.save - # flash[:notice] = 'User was successfully created.' - # format.html { redirect_to(@user) } - # format.xml { render xml: @user } - # else - # format.html { render action: "new" } - # format.xml { render xml: @user } - # end - # end - # end - # - # * for a JavaScript request - if the template isn't found, an exception is - # raised. - # * for other requests - i.e. data formats such as xml, json, csv etc, if - # the resource passed to +respond_with+ responds to <code>to_<format></code>, - # the method attempts to render the resource in the requested format - # directly, e.g. for an xml request, the response is equivalent to calling - # <code>render xml: resource</code>. - # - # === Nested resources - # - # As outlined above, the +resources+ argument passed to +respond_with+ - # can play two roles. It can be used to generate the redirect url - # for successful html requests (e.g. for +create+ actions when - # no template exists), while for formats other than html and JavaScript - # it is the object that gets rendered, by being converted directly to the - # required format (again assuming no template exists). - # - # For redirecting successful html requests, +respond_with+ also supports - # the use of nested resources, which are supplied in the same way as - # in <code>form_for</code> and <code>polymorphic_url</code>. For example - - # - # def create - # @project = Project.find(params[:project_id]) - # @task = @project.comments.build(params[:task]) - # flash[:notice] = 'Task was successfully created.' if @task.save - # respond_with(@project, @task) - # end - # - # This would cause +respond_with+ to redirect to <code>project_task_url</code> - # instead of <code>task_url</code>. For request formats other than html or - # JavaScript, if multiple resources are passed in this way, it is the last - # one specified that is rendered. - # - # === Customizing response behavior - # - # Like +respond_to+, +respond_with+ may also be called with a block that - # can be used to overwrite any of the default responses, e.g. - - # - # def create - # @user = User.new(params[:user]) - # flash[:notice] = "User was successfully created." if @user.save - # - # respond_with(@user) do |format| - # format.html { render } - # end - # end - # - # The argument passed to the block is an ActionController::MimeResponds::Collector - # object which stores the responses for the formats defined within the - # block. Note that formats with responses defined explicitly in this way - # do not have to first be declared using the class method +respond_to+. - # - # Also, a hash passed to +respond_with+ immediately after the specified - # resource(s) is interpreted as a set of options relevant to all - # formats. Any option accepted by +render+ can be used, e.g. - # respond_with @people, status: 200 - # However, note that these options are ignored after an unsuccessful attempt - # to save a resource, e.g. when automatically rendering <tt>:new</tt> - # after a post request. - # - # Two additional options are relevant specifically to +respond_with+ - - # 1. <tt>:location</tt> - overwrites the default redirect location used after - # a successful html +post+ request. - # 2. <tt>:action</tt> - overwrites the default render action used after an - # unsuccessful html +post+ request. - def respond_with(*resources, &block) - if self.class.mimes_for_respond_to.empty? - raise "In order to use respond_with, first you need to declare the " \ - "formats your controller responds to in the class level." - end - - if collector = retrieve_collector_from_mimes(&block) - options = resources.size == 1 ? {} : resources.extract_options! - options = options.clone - options[:default_response] = collector.response - (options.delete(:responder) || self.class.responder).call(self, resources, options) - end - end - - protected - - # Collect mimes declared in the class method respond_to valid for the - # current action. - def collect_mimes_from_class_level #:nodoc: - action = action_name.to_s - - self.class.mimes_for_respond_to.keys.select do |mime| - config = self.class.mimes_for_respond_to[mime] - - if config[:except] - !config[:except].include?(action) - elsif config[:only] - config[:only].include?(action) - else - true - end - end - end - - # Returns a Collector object containing the appropriate mime-type response - # for the current request, based on the available responses defined by a block. - # In typical usage this is the block passed to +respond_with+ or +respond_to+. - # - # Sends :not_acceptable to the client and returns nil if no suitable format - # is available. - def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc: - mimes ||= collect_mimes_from_class_level collector = Collector.new(mimes, request.variant) block.call(collector) if block_given? - format = collector.negotiate_format(request) - if format + if format = collector.negotiate_format(request) _process_format(format) - collector + response = collector.response + response ? response.call : render({}) else raise ActionController::UnknownFormat end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 0efa0fb259..7afbd767ce 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -9,7 +9,7 @@ module ActionController #:nodoc: end # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks - # by including a token in the rendered html for your application. This token is + # by including a token in the rendered HTML for your application. This token is # stored as a random string in the session, to which an attacker does not have # access. When a request reaches your application, \Rails verifies the received # token with the token in the session. Only HTML and JavaScript requests are checked, @@ -44,7 +44,7 @@ module ActionController #:nodoc: # # The token parameter is named <tt>authenticity_token</tt> by default. The name and # value of this token must be added to every layout that renders forms by including - # <tt>csrf_meta_tags</tt> in the html +head+. + # <tt>csrf_meta_tags</tt> in the HTML +head+. # # Learn more about CSRF attacks and securing your application in the # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html]. diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb deleted file mode 100644 index 5096558c67..0000000000 --- a/actionpack/lib/action_controller/metal/responder.rb +++ /dev/null @@ -1,297 +0,0 @@ -require 'active_support/json' - -module ActionController #:nodoc: - # Responsible for exposing a resource to different mime requests, - # usually depending on the HTTP verb. The responder is triggered when - # <code>respond_with</code> is called. The simplest case to study is a GET request: - # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json - # - # def index - # @people = Person.all - # respond_with(@people) - # end - # end - # - # When a request comes in, for example for an XML response, three steps happen: - # - # 1) the responder searches for a template at people/index.xml; - # - # 2) if the template is not available, it will invoke <code>#to_xml</code> on the given resource; - # - # 3) if the responder does not <code>respond_to :to_xml</code>, call <code>#to_format</code> on it. - # - # === Built-in HTTP verb semantics - # - # The default \Rails responder holds semantics for each HTTP verb. Depending on the - # content type, verb and the resource status, it will behave differently. - # - # Using \Rails default responder, a POST request for creating an object could - # be written as: - # - # def create - # @user = User.new(params[:user]) - # flash[:notice] = 'User was successfully created.' if @user.save - # respond_with(@user) - # end - # - # Which is exactly the same as: - # - # def create - # @user = User.new(params[:user]) - # - # respond_to do |format| - # if @user.save - # flash[:notice] = 'User was successfully created.' - # format.html { redirect_to(@user) } - # format.xml { render xml: @user, status: :created, location: @user } - # else - # format.html { render action: "new" } - # format.xml { render xml: @user.errors, status: :unprocessable_entity } - # end - # end - # end - # - # The same happens for PATCH/PUT and DELETE requests. - # - # === Nested resources - # - # You can supply nested resources as you do in <code>form_for</code> and <code>polymorphic_url</code>. - # Consider the project has many tasks example. The create action for - # TasksController would be like: - # - # def create - # @project = Project.find(params[:project_id]) - # @task = @project.tasks.build(params[:task]) - # flash[:notice] = 'Task was successfully created.' if @task.save - # respond_with(@project, @task) - # end - # - # Giving several resources ensures that the responder will redirect to - # <code>project_task_url</code> instead of <code>task_url</code>. - # - # Namespaced and singleton resources require a symbol to be given, as in - # polymorphic urls. If a project has one manager which has many tasks, it - # should be invoked as: - # - # respond_with(@project, :manager, @task) - # - # Note that if you give an array, it will be treated as a collection, - # so the following is not equivalent: - # - # respond_with [@project, :manager, @task] - # - # === Custom options - # - # <code>respond_with</code> also allows you to pass options that are forwarded - # to the underlying render call. Those options are only applied for success - # scenarios. For instance, you can do the following in the create method above: - # - # def create - # @project = Project.find(params[:project_id]) - # @task = @project.tasks.build(params[:task]) - # flash[:notice] = 'Task was successfully created.' if @task.save - # respond_with(@project, @task, status: 201) - # end - # - # This will return status 201 if the task was saved successfully. If not, - # it will simply ignore the given options and return status 422 and the - # resource errors. You can also override the location to redirect to: - # - # respond_with(@project, location: root_path) - # - # To customize the failure scenario, you can pass a block to - # <code>respond_with</code>: - # - # def create - # @project = Project.find(params[:project_id]) - # @task = @project.tasks.build(params[:task]) - # respond_with(@project, @task, status: 201) do |format| - # if @task.save - # flash[:notice] = 'Task was successfully created.' - # else - # format.html { render "some_special_template" } - # end - # end - # end - # - # Using <code>respond_with</code> with a block follows the same syntax as <code>respond_to</code>. - class Responder - attr_reader :controller, :request, :format, :resource, :resources, :options - - DEFAULT_ACTIONS_FOR_VERBS = { - :post => :new, - :patch => :edit, - :put => :edit - } - - def initialize(controller, resources, options={}) - @controller = controller - @request = @controller.request - @format = @controller.formats.first - @resource = resources.last - @resources = resources - @options = options - @action = options.delete(:action) - @default_response = options.delete(:default_response) - end - - delegate :head, :render, :redirect_to, :to => :controller - delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request - - # Undefine :to_json and :to_yaml since it's defined on Object - undef_method(:to_json) if method_defined?(:to_json) - undef_method(:to_yaml) if method_defined?(:to_yaml) - - # Initializes a new responder and invokes the proper format. If the format is - # not defined, call to_format. - # - def self.call(*args) - new(*args).respond - end - - # Main entry point for responder responsible to dispatch to the proper format. - # - def respond - method = "to_#{format}" - respond_to?(method) ? send(method) : to_format - end - - # HTML format does not render the resource, it always attempt to render a - # template. - # - def to_html - default_render - rescue ActionView::MissingTemplate => e - navigation_behavior(e) - end - - # to_js simply tries to render a template. If no template is found, raises the error. - def to_js - default_render - end - - # All other formats follow the procedure below. First we try to render a - # template, if the template is not available, we verify if the resource - # responds to :to_format and display it. - # - def to_format - if get? || !has_errors? || response_overridden? - default_render - else - display_errors - end - rescue ActionView::MissingTemplate => e - api_behavior(e) - end - - protected - - # This is the common behavior for formats associated with browsing, like :html, :iphone and so forth. - def navigation_behavior(error) - if get? - raise error - elsif has_errors? && default_action - render :action => default_action - else - redirect_to navigation_location - end - end - - # This is the common behavior for formats associated with APIs, such as :xml and :json. - def api_behavior(error) - raise error unless resourceful? - raise MissingRenderer.new(format) unless has_renderer? - - if get? - display resource - elsif post? - display resource, :status => :created, :location => api_location - else - head :no_content - end - end - - # Checks whether the resource responds to the current format or not. - # - def resourceful? - resource.respond_to?("to_#{format}") - end - - # Returns the resource location by retrieving it from the options or - # returning the resources array. - # - def resource_location - options[:location] || resources - end - alias :navigation_location :resource_location - alias :api_location :resource_location - - # If a response block was given, use it, otherwise call render on - # controller. - # - def default_render - if @default_response - @default_response.call(options) - else - controller.default_render(options) - end - end - - # Display is just a shortcut to render a resource with the current format. - # - # display @user, status: :ok - # - # For XML requests it's equivalent to: - # - # render xml: @user, status: :ok - # - # Options sent by the user are also used: - # - # respond_with(@user, status: :created) - # display(@user, status: :ok) - # - # Results in: - # - # render xml: @user, status: :created - # - def display(resource, given_options={}) - controller.render given_options.merge!(options).merge!(format => resource) - end - - def display_errors - controller.render format => resource_errors, :status => :unprocessable_entity - end - - # Check whether the resource has errors. - # - def has_errors? - resource.respond_to?(:errors) && !resource.errors.empty? - end - - # Check whether the necessary Renderer is available - def has_renderer? - Renderers::RENDERERS.include?(format) - end - - # By default, render the <code>:edit</code> action for HTML requests with errors, unless - # the verb was POST. - # - def default_action - @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol] - end - - def resource_errors - respond_to?("#{format}_resource_errors", true) ? send("#{format}_resource_errors") : resource.errors - end - - def json_resource_errors - {:errors => resource.errors} - end - - def response_overridden? - @default_response.present? - end - end -end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index bc27ecaa20..7038f0997f 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -141,6 +141,37 @@ module ActionController @permitted = self.class.permit_all_parameters end + # Returns a safe +Hash+ representation of this parameter with all + # unpermitted keys removed. + # + # params = ActionController::Parameters.new({ + # name: 'Senjougahara Hitagi', + # oddity: 'Heavy stone crab' + # }) + # params.to_h # => {} + # + # safe_params = params.permit(:name) + # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} + def to_h + if permitted? + to_hash + else + slice(*self.class.always_permitted_parameters).permit!.to_h + end + end + + # Convert all hashes in values into parameters, then yield each pair like + # the same way as <tt>Hash#each_pair</tt> + def each_pair(&block) + super do |key, value| + convert_hashes_to_parameters(key, value) + end + + super + end + + alias_method :each, :each_pair + # Attribute that keeps track of converted arrays, if any, to avoid double # looping in the common use case permit + mass-assignment. Defined in a # method to instantiate it only if needed. @@ -176,7 +207,6 @@ module ActionController # Person.new(params) # => #<Person id: nil, name: "Francesco"> def permit! each_pair do |key, value| - value = convert_hashes_to_parameters(key, value) Array.wrap(value).each do |v| v.permit! if v.respond_to? :permit! end @@ -331,11 +361,56 @@ module ActionController # params.slice(:a, :b) # => {"a"=>1, "b"=>2} # params.slice(:d) # => {} def slice(*keys) - self.class.new(super).tap do |new_instance| - new_instance.permitted = @permitted + new_instance_with_inherited_permitted_status(super) + end + + # Removes and returns the key/value pairs matching the given keys. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.extract!(:a, :b) # => {"a"=>1, "b"=>2} + # params # => {"c"=>3} + def extract!(*keys) + new_instance_with_inherited_permitted_status(super) + end + + # Returns a new <tt>ActionController::Parameters</tt> with the results of + # running +block+ once for every value. The keys are unchanged. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.transform_values { |x| x * 2 } + # # => {"a"=>2, "b"=>4, "c"=>6} + def transform_values + if block_given? + new_instance_with_inherited_permitted_status(super) + else + super + end + end + + # This method is here only to make sure that the returned object has the + # correct +permitted+ status. It should not matter since the parent of + # this object is +HashWithIndifferentAccess+ + def transform_keys # :nodoc: + if block_given? + new_instance_with_inherited_permitted_status(super) + else + super end end + # Deletes and returns a key-value pair from +Parameters+ whose key is equal + # to key. If the key is not found, returns the default value. If the + # optional code block is given and the key is not found, pass in the key + # and return the result of block. + def delete(key, &block) + convert_hashes_to_parameters(key, super, false) + end + + # Equivalent to Hash#keep_if, but returns nil if no changes were made. + def select!(&block) + convert_value_to_parameters(super) + end + # Returns an exact copy of the <tt>ActionController::Parameters</tt> # instance. +permitted+ state is kept on the duped object. # @@ -356,6 +431,12 @@ module ActionController end private + def new_instance_with_inherited_permitted_status(hash) + self.class.new(hash).tap do |new_instance| + new_instance.permitted = @permitted + end + end + def convert_hashes_to_parameters(key, value, assign_if_converted=true) converted = convert_value_to_parameters(value) self[key] = converted if assign_if_converted && !converted.equal?(value) diff --git a/actionpack/lib/action_controller/model_naming.rb b/actionpack/lib/action_controller/model_naming.rb index 785221dc3d..2b33f67263 100644 --- a/actionpack/lib/action_controller/model_naming.rb +++ b/actionpack/lib/action_controller/model_naming.rb @@ -6,7 +6,7 @@ module ActionController end def model_name_from_record_or_class(record_or_class) - (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name + convert_to_model(record_or_class).model_name end end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index eb5d824cbc..8c10c3e7b0 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -3,6 +3,8 @@ require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/hash/keys' +require 'rails-dom-testing' + module ActionController module TemplateAssertions extend ActiveSupport::Concern @@ -91,6 +93,13 @@ module ActionController # # assert that no partials were rendered # assert_template partial: false # + # # assert that a file was rendered + # assert_template file: "README.rdoc" + # + # # assert that no file was rendered + # assert_template file: nil + # assert_template file: false + # # In a view test case, you can also assert that specific locals are passed # to partials: # @@ -140,6 +149,8 @@ module ActionController if options[:file] assert_includes @_files.keys, options[:file] + elsif options.key?(:file) + assert @_files.blank?, "expected no files but #{@_files.keys} was rendered" end if expected_partial = options[:partial] @@ -433,6 +444,7 @@ module ActionController extend ActiveSupport::Concern include ActionDispatch::TestProcess include ActiveSupport::Testing::ConstantLookup + include Rails::Dom::Testing::Assertions attr_reader :response, :request @@ -675,6 +687,11 @@ module ActionController end private + + def document_root_element + html_document.root + end + def check_required_ivars # Sanity check for required instance variables so we can give an # understandable error message. diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 8c035c3c6c..f35289253b 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -292,7 +292,7 @@ module ActionDispatch # Override Rack's GET method to support indifferent access def GET @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {})) - rescue TypeError => e + rescue TypeError, Rack::Utils::InvalidParameterError => e raise ActionController::BadRequest.new(:query, e) end alias :query_parameters :GET @@ -300,7 +300,7 @@ module ActionDispatch # Override Rack's POST method to support indifferent access def POST @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {})) - rescue TypeError => e + rescue TypeError, Rack::Utils::InvalidParameterError => e raise ActionController::BadRequest.new(:request, e) end alias :request_parameters :POST diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index ac9e5effe2..83ac62a83d 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/object/blank' require 'active_support/key_generator' require 'active_support/message_verifier' +require 'active_support/json' module ActionDispatch class Request < Rack::Request @@ -90,6 +91,7 @@ module ActionDispatch SECRET_TOKEN = "action_dispatch.secret_token".freeze SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze + COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze # Cookies can typically store 4096 bytes. MAX_COOKIE_SIZE = 4096 @@ -173,10 +175,14 @@ module ActionDispatch end end + # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream + # to the Message{Encryptor,Verifier} allows us to handle the + # (de)serialization step within the cookie jar, which gives us the + # opportunity to detect and migrate legacy cookies. module VerifyAndUpgradeLegacySignedMessage def initialize(*args) super - @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: NullSerializer) + @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer) end def verify_and_upgrade_legacy_signed_message(name, signed_message) @@ -212,7 +218,8 @@ module ActionDispatch secret_token: env[SECRET_TOKEN], secret_key_base: env[SECRET_KEY_BASE], upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?, - serializer: env[COOKIES_SERIALIZER] + serializer: env[COOKIES_SERIALIZER], + digest: env[COOKIES_DIGEST] } end @@ -385,24 +392,11 @@ module ActionDispatch class JsonSerializer def self.load(value) - JSON.parse(value, quirks_mode: true) + ActiveSupport::JSON.decode(value) end def self.dump(value) - JSON.generate(value, quirks_mode: true) - end - end - - # Passing the NullSerializer downstream to the Message{Encryptor,Verifier} - # allows us to handle the (de)serialization step within the cookie jar, - # which gives us the opportunity to detect and migrate legacy cookies. - class NullSerializer - def self.load(value) - value - end - - def self.dump(value) - value + ActiveSupport::JSON.encode(value) end end @@ -441,6 +435,10 @@ module ActionDispatch serializer end end + + def digest + @options[:digest] || 'SHA1' + end end class SignedCookieJar #:nodoc: @@ -451,7 +449,7 @@ module ActionDispatch @parent_jar = parent_jar @options = options secret = key_generator.generate_key(@options[:signed_cookie_salt]) - @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: NullSerializer) + @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) end def [](name) @@ -508,7 +506,7 @@ module ActionDispatch @options = options secret = key_generator.generate_key(@options[:encrypted_cookie_salt]) sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt]) - @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: NullSerializer) + @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) end def [](name) diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index 6c8944e067..040cb215b7 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -1,4 +1,14 @@ module ActionDispatch + # When called, this middleware renders an error page. By default if an HTML + # response is expected it will render static error pages from the `/public` + # directory. For example when this middleware receives a 500 response it will + # render the template found in `/public/500.html`. + # If an internationalized locale is set, this middleware will attempt to render + # the template in `/public/500.<locale>.html`. If an internationalized template + # is not found it will fall back on `/public/500.html`. + # + # When a request with a content type other than HTML is made, this middleware + # will attempt to convert error information into the appropriate response type. class PublicExceptions attr_accessor :public_path diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 2764584fe9..25e32cdef8 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -2,6 +2,16 @@ require 'rack/utils' require 'active_support/core_ext/uri' module ActionDispatch + # This middleware returns a file's contents from disk in the body response. + # When initialized it can accept an optional 'Cache-Control' header which + # will be set when a response containing a file's contents is delivered. + # + # This middleware will render the file specified in `env["PATH_INFO"]` + # where the base path is in the +root+ directory. For example if the +root+ + # is set to `public/` then a request with `env["PATH_INFO"]` of + # `assets/application.js` will return a response with contents of a file + # located at `public/assets/application.js` if the file exists. If the file + # does not exist a 404 "File not Found" response will be returned. class FileHandler def initialize(root, cache_control) @root = root.chomp('/') @@ -45,6 +55,15 @@ module ActionDispatch end end + # This middleware will attempt to return the contents of a file's body from + # disk in the response. If a file is not found on disk, the request will be + # delegated to the application stack. This middleware is commonly initialized + # to serve assets from a server's `public/` directory. + # + # This middleware verifies the path to ensure that only files + # living in the root directory can be rendered. A request cannot + # produce a directory traversal using this middleware. Only 'GET' and 'HEAD' + # requests will result in a file being returned. class Static def initialize(app, path, cache_control=nil) @app = app diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb index 36b01bf952..c0b53068f7 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb @@ -3,7 +3,7 @@ Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unse <% @traces.each do |name, trace| %> <% if trace.any? %> <%= name %> -<%= trace.map(&:trace).join("\n") %> +<%= trace.map { |t| t[:trace] }.join("\n") %> <% end %> <% end %> diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index cd94f35e8f..e92baa5aa7 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -63,7 +63,7 @@ module ActionDispatch attr_reader :requirements, :conditions, :defaults attr_reader :to, :default_controller, :default_action, :as, :anchor - def self.build(scope, set, path, options) + def self.build(scope, set, path, as, options) options = scope[:options].merge(options) if scope[:options] options.delete :only @@ -74,10 +74,10 @@ module ActionDispatch defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {} - new scope, set, path, defaults, options + new scope, set, path, defaults, as, options end - def initialize(scope, set, path, defaults, options) + def initialize(scope, set, path, defaults, as, options) @requirements, @conditions = {}, {} @defaults = defaults @set = set @@ -85,7 +85,7 @@ module ActionDispatch @to = options.delete :to @default_controller = options.delete(:controller) || scope[:controller] @default_action = options.delete(:action) || scope[:action] - @as = options.delete :as + @as = as @anchor = options.delete :anchor formatted = options.delete :format @@ -1046,8 +1046,6 @@ module ActionDispatch VALID_ON_OPTIONS = [:new, :collection, :member] RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns] CANONICAL_ACTIONS = %w(index create new show update destroy) - RESOURCE_METHOD_SCOPES = [:collection, :member, :new] - RESOURCE_SCOPES = [:resource, :resources] class Resource #:nodoc: attr_reader :controller, :path, :options, :param @@ -1521,7 +1519,7 @@ module ActionDispatch if on = options.delete(:on) send(on) { decomposed_match(path, options) } else - case @scope[:scope_level] + case @scope.scope_level when :resources nested { decomposed_match(path, options) } when :resource @@ -1544,13 +1542,13 @@ module ActionDispatch action = nil end - if !options.fetch(:as, true) # if it's set to nil or false - options.delete(:as) - else - options[:as] = name_for_action(options[:as], action) - end + as = if !options.fetch(:as, true) # if it's set to nil or false + options.delete(:as) + else + name_for_action(options.delete(:as), action) + end - mapping = Mapping.build(@scope, @set, URI.parser.escape(path), options) + mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options) app, conditions, requirements, defaults, as, anchor = mapping.to_route @set.add_route(app, conditions, requirements, defaults, as, anchor) end @@ -1564,7 +1562,7 @@ module ActionDispatch raise ArgumentError, "must be called with a path and/or options" end - if @scope[:scope_level] == :resources + if @scope.resources? with_scope_level(:root) do scope(parent_resource.path) do super(options) @@ -1631,15 +1629,15 @@ module ActionDispatch end def resource_scope? #:nodoc: - RESOURCE_SCOPES.include? @scope[:scope_level] + @scope.resource_scope? end def resource_method_scope? #:nodoc: - RESOURCE_METHOD_SCOPES.include? @scope[:scope_level] + @scope.resource_method_scope? end def nested_scope? #:nodoc: - @scope[:scope_level] == :nested + @scope.nested? end def with_exclusive_scope @@ -1655,7 +1653,7 @@ module ActionDispatch end def with_scope_level(kind) - @scope = @scope.new(:scope_level => kind) + @scope = @scope.new_level(kind) yield ensure @scope = @scope.parent @@ -1699,8 +1697,8 @@ module ActionDispatch @scope[:constraints][parent_resource.param] end - def canonical_action?(action, flag) #:nodoc: - flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s) + def canonical_action?(action) #:nodoc: + resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s) end def shallow_scope(path, options = {}) #:nodoc: @@ -1714,7 +1712,7 @@ module ActionDispatch end def path_for_action(action, path) #:nodoc: - if canonical_action?(action, path.blank?) + if path.blank? && canonical_action?(action) @scope[:path].to_s else "#{@scope[:path]}/#{action_path(action, path)}" @@ -1729,15 +1727,17 @@ module ActionDispatch def prefix_name_for_action(as, action) #:nodoc: if as prefix = as - elsif !canonical_action?(action, @scope[:scope_level]) + elsif !canonical_action?(action) prefix = action end - prefix.to_s.tr('-', '_') if prefix + + if prefix && prefix != '/' && !prefix.empty? + Mapper.normalize_name prefix.to_s.tr('-', '_') + end end def name_for_action(as, action) #:nodoc: prefix = prefix_name_for_action(as, action) - prefix = Mapper.normalize_name(prefix) if prefix name_prefix = @scope[:as] if parent_resource @@ -1747,22 +1747,9 @@ module ActionDispatch member_name = parent_resource.member_name end - name = case @scope[:scope_level] - when :nested - [name_prefix, prefix] - when :collection - [prefix, name_prefix, collection_name] - when :new - [prefix, :new, name_prefix, member_name] - when :member - [prefix, name_prefix, member_name] - when :root - [name_prefix, collection_name, prefix] - else - [name_prefix, member_name, prefix] - end + name = @scope.action_name(name_prefix, prefix, collection_name, member_name) - if candidate = name.select(&:present?).join("_").presence + if candidate = name.compact.join("_").presence # If a name was not explicitly given, we check if it is valid # and return nil in case it isn't. Otherwise, we pass the invalid name # forward so the underlying router engine treats it and raises an exception. @@ -1897,11 +1884,48 @@ module ActionDispatch :controller, :action, :path_names, :constraints, :shallow, :blocks, :defaults, :options] - attr_reader :parent + RESOURCE_SCOPES = [:resource, :resources] + RESOURCE_METHOD_SCOPES = [:collection, :member, :new] + + attr_reader :parent, :scope_level - def initialize(hash, parent = {}) + def initialize(hash, parent = {}, scope_level = nil) @hash = hash @parent = parent + @scope_level = scope_level + end + + def nested? + scope_level == :nested + end + + def resources? + scope_level == :resources + end + + def resource_method_scope? + RESOURCE_METHOD_SCOPES.include? scope_level + end + + def action_name(name_prefix, prefix, collection_name, member_name) + case scope_level + when :nested + [name_prefix, prefix] + when :collection + [prefix, name_prefix, collection_name] + when :new + [prefix, :new, name_prefix, member_name] + when :member + [prefix, name_prefix, member_name] + when :root + [name_prefix, collection_name, prefix] + else + [name_prefix, member_name, prefix] + end + end + + def resource_scope? + RESOURCE_SCOPES.include? scope_level end def options @@ -1909,7 +1933,15 @@ module ActionDispatch end def new(hash) - self.class.new hash, self + self.class.new hash, self, scope_level + end + + def new_level(level) + self.class.new(self, self, level) + end + + def fetch(key, &block) + @hash.fetch(key, &block) end def [](key) diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index bd3696cda1..427a5674bd 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -142,22 +142,27 @@ module ActionDispatch %w(edit new).each do |action| module_eval <<-EOT, __FILE__, __LINE__ + 1 - def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {}) - polymorphic_url( # polymorphic_url( - record_or_hash, # record_or_hash, - options.merge(:action => "#{action}")) # options.merge(:action => "edit")) - end # end - # - def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {}) - polymorphic_url( # polymorphic_url( - record_or_hash, # record_or_hash, - options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path)) - end # end + def #{action}_polymorphic_url(record_or_hash, options = {}) + polymorphic_url_for_action("#{action}", record_or_hash, options) + end + + def #{action}_polymorphic_path(record_or_hash, options = {}) + polymorphic_path_for_action("#{action}", record_or_hash, options) + end EOT end private + def polymorphic_url_for_action(action, record_or_hash, options) + polymorphic_url(record_or_hash, options.merge(:action => action)) + end + + def polymorphic_path_for_action(action, record_or_hash, options) + options = options.merge(:action => action, :routing_type => :path) + polymorphic_path(record_or_hash, options) + end + class HelperMethodBuilder # :nodoc: CACHE = { 'path' => {}, 'url' => {} } @@ -249,9 +254,9 @@ module ActionDispatch model = record.to_model name = if record.persisted? args << model - model.class.model_name.singular_route_key + model.model_name.singular_route_key else - @key_strategy.call model.class.model_name + @key_strategy.call model.model_name end named_route = prefix + "#{name}_#{suffix}" @@ -279,7 +284,7 @@ module ActionDispatch parent.model_name.singular_route_key else args << parent.to_model - parent.to_model.class.model_name.singular_route_key + parent.to_model.model_name.singular_route_key end } @@ -292,9 +297,9 @@ module ActionDispatch else if record.persisted? args << record.to_model - record.to_model.class.model_name.singular_route_key + record.to_model.model_name.singular_route_key else - @key_strategy.call record.to_model.class.model_name + @key_strategy.call record.to_model.model_name end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 5b3651aaee..f51bee3b31 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -101,6 +101,11 @@ module ActionDispatch @path_helpers.include?(key) || @url_helpers.include?(key) end + def helpers + ActiveSupport::Deprecation.warn("`named_routes.helpers` is deprecated, please use `route_defined?(route_name)` to see if a named route was defined.") + @path_helpers + @url_helpers + end + def helper_names @path_helpers.map(&:to_s) + @url_helpers.map(&:to_s) end diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index 226baf9ad0..f325c35b57 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -1,18 +1,22 @@ +require 'rails-dom-testing' + module ActionDispatch module Assertions - autoload :DomAssertions, 'action_dispatch/testing/assertions/dom' autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response' autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing' - autoload :SelectorAssertions, 'action_dispatch/testing/assertions/selector' - autoload :TagAssertions, 'action_dispatch/testing/assertions/tag' extend ActiveSupport::Concern - include DomAssertions include ResponseAssertions include RoutingAssertions - include SelectorAssertions - include TagAssertions + include Rails::Dom::Testing::Assertions + + def html_document + @html_document ||= if @response.content_type =~ /xml$/ + Nokogiri::XML::Document.parse(@response.body) + else + Nokogiri::HTML::Document.parse(@response.body) + end + end end end - diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb index 241a39393a..fb579b52fe 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/dom.rb @@ -1,27 +1,3 @@ -require 'action_view/vendor/html-scanner' +require 'active_support/deprecation' -module ActionDispatch - module Assertions - module DomAssertions - # \Test two HTML strings for equivalency (e.g., identical up to reordering of attributes) - # - # # assert that the referenced method generates the appropriate HTML string - # assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com") - def assert_dom_equal(expected, actual, message = nil) - expected_dom = HTML::Document.new(expected).root - actual_dom = HTML::Document.new(actual).root - assert_equal expected_dom, actual_dom, message - end - - # The negated form of +assert_dom_equivalent+. - # - # # assert that the referenced method does not generate the specified HTML string - # assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com") - def assert_dom_not_equal(expected, actual, message = nil) - expected_dom = HTML::Document.new(expected).root - actual_dom = HTML::Document.new(actual).root - assert_not_equal expected_dom, actual_dom, message - end - end - end -end +ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::DomAssertions has been extracted to the rails-dom-testing gem.")
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index 12023e6f77..19eca60f70 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -1,430 +1,3 @@ -require 'action_view/vendor/html-scanner' -require 'active_support/core_ext/object/inclusion' +require 'active_support/deprecation' -#-- -# Copyright (c) 2006 Assaf Arkin (http://labnotes.org) -# Under MIT and/or CC By license. -#++ - -module ActionDispatch - module Assertions - NO_STRIP = %w{pre script style textarea} - - # Adds the +assert_select+ method for use in Rails functional - # test cases, which can be used to make assertions on the response HTML of a controller - # action. You can also call +assert_select+ within another +assert_select+ to - # make assertions on elements selected by the enclosing assertion. - # - # Use +css_select+ to select elements without making an assertions, either - # from the response HTML or elements selected by the enclosing assertion. - # - # In addition to HTML responses, you can make the following assertions: - # - # * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions. - # * +assert_select_email+ - Assertions on the HTML body of an e-mail. - # - # Also see HTML::Selector to learn how to use selectors. - module SelectorAssertions - # Select and return all matching elements. - # - # If called with a single argument, uses that argument as a selector - # to match all elements of the current page. Returns an empty array - # if no match is found. - # - # If called with two arguments, uses the first argument as the base - # element and the second argument as the selector. Attempts to match the - # base element and any of its children. Returns an empty array if no - # match is found. - # - # The selector may be a CSS selector expression (String), an expression - # with substitution values (Array) or an HTML::Selector object. - # - # # Selects all div tags - # divs = css_select("div") - # - # # Selects all paragraph tags and does something interesting - # pars = css_select("p") - # pars.each do |par| - # # Do something fun with paragraphs here... - # end - # - # # Selects all list items in unordered lists - # items = css_select("ul>li") - # - # # Selects all form tags and then all inputs inside the form - # forms = css_select("form") - # forms.each do |form| - # inputs = css_select(form, "input") - # ... - # end - def css_select(*args) - # See assert_select to understand what's going on here. - arg = args.shift - - if arg.is_a?(HTML::Node) - root = arg - arg = args.shift - elsif arg == nil - raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?" - elsif defined?(@selected) && @selected - matches = [] - - @selected.each do |selected| - subset = css_select(selected, HTML::Selector.new(arg.dup, args.dup)) - subset.each do |match| - matches << match unless matches.any? { |m| m.equal?(match) } - end - end - - return matches - else - root = response_from_page - end - - case arg - when String - selector = HTML::Selector.new(arg, args) - when Array - selector = HTML::Selector.new(*arg) - when HTML::Selector - selector = arg - else raise ArgumentError, "Expecting a selector as the first argument" - end - - selector.select(root) - end - - # An assertion that selects elements and makes one or more equality tests. - # - # If the first argument is an element, selects all matching elements - # starting from (and including) that element and all its children in - # depth-first order. - # - # If no element if specified, calling +assert_select+ selects from the - # response HTML unless +assert_select+ is called from within an +assert_select+ block. - # - # When called with a block +assert_select+ passes an array of selected elements - # to the block. Calling +assert_select+ from the block, with no element specified, - # runs the assertion on the complete set of elements selected by the enclosing assertion. - # Alternatively the array may be iterated through so that +assert_select+ can be called - # separately for each element. - # - # - # ==== Example - # If the response contains two ordered lists, each with four list elements then: - # assert_select "ol" do |elements| - # elements.each do |element| - # assert_select element, "li", 4 - # end - # end - # - # will pass, as will: - # assert_select "ol" do - # assert_select "li", 8 - # end - # - # The selector may be a CSS selector expression (String), an expression - # with substitution values, or an HTML::Selector object. - # - # === Equality Tests - # - # The equality test may be one of the following: - # * <tt>true</tt> - Assertion is true if at least one element selected. - # * <tt>false</tt> - Assertion is true if no element selected. - # * <tt>String/Regexp</tt> - Assertion is true if the text value of at least - # one element matches the string or regular expression. - # * <tt>Integer</tt> - Assertion is true if exactly that number of - # elements are selected. - # * <tt>Range</tt> - Assertion is true if the number of selected - # elements fit the range. - # If no equality test specified, the assertion is true if at least one - # element selected. - # - # To perform more than one equality tests, use a hash with the following keys: - # * <tt>:text</tt> - Narrow the selection to elements that have this text - # value (string or regexp). - # * <tt>:html</tt> - Narrow the selection to elements that have this HTML - # content (string or regexp). - # * <tt>:count</tt> - Assertion is true if the number of selected elements - # is equal to this value. - # * <tt>:minimum</tt> - Assertion is true if the number of selected - # elements is at least this value. - # * <tt>:maximum</tt> - Assertion is true if the number of selected - # elements is at most this value. - # - # If the method is called with a block, once all equality tests are - # evaluated the block is called with an array of all matched elements. - # - # # At least one form element - # assert_select "form" - # - # # Form element includes four input fields - # assert_select "form input", 4 - # - # # Page title is "Welcome" - # assert_select "title", "Welcome" - # - # # Page title is "Welcome" and there is only one title element - # assert_select "title", {count: 1, text: "Welcome"}, - # "Wrong title or more than one title element" - # - # # Page contains no forms - # assert_select "form", false, "This page must contain no forms" - # - # # Test the content and style - # assert_select "body div.header ul.menu" - # - # # Use substitution values - # assert_select "ol>li#?", /item-\d+/ - # - # # All input fields in the form have a name - # assert_select "form input" do - # assert_select "[name=?]", /.+/ # Not empty - # end - def assert_select(*args, &block) - # Start with optional element followed by mandatory selector. - arg = args.shift - @selected ||= nil - - if arg.is_a?(HTML::Node) - # First argument is a node (tag or text, but also HTML root), - # so we know what we're selecting from. - root = arg - arg = args.shift - elsif arg == nil - # This usually happens when passing a node/element that - # happens to be nil. - raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?" - elsif @selected - root = HTML::Node.new(nil) - root.children.concat @selected - else - # Otherwise just operate on the response document. - root = response_from_page - end - - # First or second argument is the selector: string and we pass - # all remaining arguments. Array and we pass the argument. Also - # accepts selector itself. - case arg - when String - selector = HTML::Selector.new(arg, args) - when Array - selector = HTML::Selector.new(*arg) - when HTML::Selector - selector = arg - else raise ArgumentError, "Expecting a selector as the first argument" - end - - # Next argument is used for equality tests. - equals = {} - case arg = args.shift - when Hash - equals = arg - when String, Regexp - equals[:text] = arg - when Integer - equals[:count] = arg - when Range - equals[:minimum] = arg.begin - equals[:maximum] = arg.end - when FalseClass - equals[:count] = 0 - when NilClass, TrueClass - equals[:minimum] = 1 - else raise ArgumentError, "I don't understand what you're trying to match" - end - - # By default we're looking for at least one match. - if equals[:count] - equals[:minimum] = equals[:maximum] = equals[:count] - else - equals[:minimum] = 1 unless equals[:minimum] - end - - # Last argument is the message we use if the assertion fails. - message = args.shift - #- message = "No match made with selector #{selector.inspect}" unless message - if args.shift - raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type" - end - - matches = selector.select(root) - # If text/html, narrow down to those elements that match it. - content_mismatch = nil - if match_with = equals[:text] - matches.delete_if do |match| - text = "" - stack = match.children.reverse - while node = stack.pop - if node.tag? - stack.concat node.children.reverse - else - content = node.content - text << content - end - end - text.strip! unless NO_STRIP.include?(match.name) - text.sub!(/\A\n/, '') if match.name == "textarea" - unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s) - content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, text) - true - end - end - elsif match_with = equals[:html] - matches.delete_if do |match| - html = match.children.map(&:to_s).join - html.strip! unless NO_STRIP.include?(match.name) - unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s) - content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, html) - true - end - end - end - # Expecting foo found bar element only if found zero, not if - # found one but expecting two. - message ||= content_mismatch if matches.empty? - # Test minimum/maximum occurrence. - min, max, count = equals[:minimum], equals[:maximum], equals[:count] - - # FIXME: minitest provides messaging when we use assert_operator, - # so is this custom message really needed? - message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}) - if count - assert_equal count, matches.size, message - else - assert_operator matches.size, :>=, min, message if min - assert_operator matches.size, :<=, max, message if max - end - - # If a block is given call that block. Set @selected to allow - # nested assert_select, which can be nested several levels deep. - if block_given? && !matches.empty? - begin - in_scope, @selected = @selected, matches - yield matches - ensure - @selected = in_scope - end - end - - # Returns all matches elements. - matches - end - - def count_description(min, max, count) #:nodoc: - pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')} - - if min && max && (max != min) - "between #{min} and #{max} elements" - elsif min && max && max == min && count - "exactly #{count} #{pluralize['element', min]}" - elsif min && !(min == 1 && max == 1) - "at least #{min} #{pluralize['element', min]}" - elsif max - "at most #{max} #{pluralize['element', max]}" - end - end - - # Extracts the content of an element, treats it as encoded HTML and runs - # nested assertion on it. - # - # You typically call this method within another assertion to operate on - # all currently selected elements. You can also pass an element or array - # of elements. - # - # The content of each element is un-encoded, and wrapped in the root - # element +encoded+. It then calls the block with all un-encoded elements. - # - # # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix) - # assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do - # # Select each entry item and then the title item - # assert_select "entry>title" do - # # Run assertions on the encoded title elements - # assert_select_encoded do - # assert_select "b" - # end - # end - # end - # - # - # # Selects all paragraph tags from within the description of an RSS feed - # assert_select "rss[version=2.0]" do - # # Select description element of each feed item. - # assert_select "channel>item>description" do - # # Run assertions on the encoded elements. - # assert_select_encoded do - # assert_select "p" - # end - # end - # end - def assert_select_encoded(element = nil, &block) - case element - when Array - elements = element - when HTML::Node - elements = [element] - when nil - unless elements = @selected - raise ArgumentError, "First argument is optional, but must be called from a nested assert_select" - end - else - raise ArgumentError, "Argument is optional, and may be node or array of nodes" - end - - fix_content = lambda do |node| - # Gets around a bug in the Rails 1.1 HTML parser. - node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { Rack::Utils.escapeHTML($1) } - end - - selected = elements.map do |elem| - text = elem.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join - root = HTML::Document.new(CGI.unescapeHTML("<encoded>#{text}</encoded>")).root - css_select(root, "encoded:root", &block)[0] - end - - begin - old_selected, @selected = @selected, selected - assert_select ":root", &block - ensure - @selected = old_selected - end - end - - # Extracts the body of an email and runs nested assertions on it. - # - # You must enable deliveries for this assertion to work, use: - # ActionMailer::Base.perform_deliveries = true - # - # assert_select_email do - # assert_select "h1", "Email alert" - # end - # - # assert_select_email do - # items = assert_select "ol>li" - # items.each do - # # Work with items here... - # end - # end - def assert_select_email(&block) - deliveries = ActionMailer::Base.deliveries - assert !deliveries.empty?, "No e-mail in delivery list" - - deliveries.each do |delivery| - (delivery.parts.empty? ? [delivery] : delivery.parts).each do |part| - if part["Content-Type"].to_s =~ /^text\/html\W/ - root = HTML::Document.new(part.body.to_s).root - assert_select root, ":root", &block - end - end - end - end - - protected - # +assert_select+ and +css_select+ call this to obtain the content in the HTML page. - def response_from_page - html_document.root - end - end - end -end +ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::SelectorAssertions has been has been extracted to the rails-dom-testing gem.")
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb index e5fe30ba82..d5348d80e1 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/tag.rb @@ -1,135 +1,3 @@ -require 'action_view/vendor/html-scanner' +require 'active_support/deprecation' -module ActionDispatch - module Assertions - # Pair of assertions to testing elements in the HTML output of the response. - module TagAssertions - # Asserts that there is a tag/node/element in the body of the response - # that meets all of the given conditions. The +conditions+ parameter must - # be a hash of any of the following keys (all are optional): - # - # * <tt>:tag</tt>: the node type must match the corresponding value - # * <tt>:attributes</tt>: a hash. The node's attributes must match the - # corresponding values in the hash. - # * <tt>:parent</tt>: a hash. The node's parent must match the - # corresponding hash. - # * <tt>:child</tt>: a hash. At least one of the node's immediate children - # must meet the criteria described by the hash. - # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must - # meet the criteria described by the hash. - # * <tt>:descendant</tt>: a hash. At least one of the node's descendants - # must meet the criteria described by the hash. - # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must - # meet the criteria described by the hash. - # * <tt>:after</tt>: a hash. The node must be after any sibling meeting - # the criteria described by the hash, and at least one sibling must match. - # * <tt>:before</tt>: a hash. The node must be before any sibling meeting - # the criteria described by the hash, and at least one sibling must match. - # * <tt>:children</tt>: a hash, for counting children of a node. Accepts - # the keys: - # * <tt>:count</tt>: either a number or a range which must equal (or - # include) the number of children that match. - # * <tt>:less_than</tt>: the number of matching children must be less - # than this number. - # * <tt>:greater_than</tt>: the number of matching children must be - # greater than this number. - # * <tt>:only</tt>: another hash consisting of the keys to use - # to match on the children, and only matching children will be - # counted. - # * <tt>:content</tt>: the textual content of the node must match the - # given value. This will not match HTML tags in the body of a - # tag--only text. - # - # Conditions are matched using the following algorithm: - # - # * if the condition is a string, it must be a substring of the value. - # * if the condition is a regexp, it must match the value. - # * if the condition is a number, the value must match number.to_s. - # * if the condition is +true+, the value must not be +nil+. - # * if the condition is +false+ or +nil+, the value must be +nil+. - # - # # Assert that there is a "span" tag - # assert_tag tag: "span" - # - # # Assert that there is a "span" tag with id="x" - # assert_tag tag: "span", attributes: { id: "x" } - # - # # Assert that there is a "span" tag using the short-hand - # assert_tag :span - # - # # Assert that there is a "span" tag with id="x" using the short-hand - # assert_tag :span, attributes: { id: "x" } - # - # # Assert that there is a "span" inside of a "div" - # assert_tag tag: "span", parent: { tag: "div" } - # - # # Assert that there is a "span" somewhere inside a table - # assert_tag tag: "span", ancestor: { tag: "table" } - # - # # Assert that there is a "span" with at least one "em" child - # assert_tag tag: "span", child: { tag: "em" } - # - # # Assert that there is a "span" containing a (possibly nested) - # # "strong" tag. - # assert_tag tag: "span", descendant: { tag: "strong" } - # - # # Assert that there is a "span" containing between 2 and 4 "em" tags - # # as immediate children - # assert_tag tag: "span", - # children: { count: 2..4, only: { tag: "em" } } - # - # # Get funky: assert that there is a "div", with an "ul" ancestor - # # and an "li" parent (with "class" = "enum"), and containing a - # # "span" descendant that contains text matching /hello world/ - # assert_tag tag: "div", - # ancestor: { tag: "ul" }, - # parent: { tag: "li", - # attributes: { class: "enum" } }, - # descendant: { tag: "span", - # child: /hello world/ } - # - # <b>Please note</b>: +assert_tag+ and +assert_no_tag+ only work - # with well-formed XHTML. They recognize a few tags as implicitly self-closing - # (like br and hr and such) but will not work correctly with tags - # that allow optional closing tags (p, li, td). <em>You must explicitly - # close all of your tags to use these assertions.</em> - def assert_tag(*opts) - opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first - tag = find_tag(opts) - assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}" - end - - # Identical to +assert_tag+, but asserts that a matching tag does _not_ - # exist. (See +assert_tag+ for a full discussion of the syntax.) - # - # # Assert that there is not a "div" containing a "p" - # assert_no_tag tag: "div", descendant: { tag: "p" } - # - # # Assert that an unordered list is empty - # assert_no_tag tag: "ul", descendant: { tag: "li" } - # - # # Assert that there is not a "p" tag with between 1 to 3 "img" tags - # # as immediate children - # assert_no_tag tag: "p", - # children: { count: 1..3, only: { tag: "img" } } - def assert_no_tag(*opts) - opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first - tag = find_tag(opts) - assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}" - end - - def find_tag(conditions) - html_document.find(conditions) - end - - def find_all_tag(conditions) - html_document.find_all(conditions) - end - - def html_document - xml = @response.content_type =~ /xml$/ - @html_document ||= HTML::Document.new(@response.body, false, xml) - end - end - end -end +ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::TagAssertions has been has been extracted to the rails-dom-testing gem.") diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 192ccdb9d5..2d1c3ac5c7 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -495,5 +495,9 @@ module ActionDispatch reset! unless integration_session integration_session.url_options end + + def document_root_element + html_document.root + end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 4e17d57dad..674fb253f4 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -484,6 +484,9 @@ class ForkingExecutor method = job[1] reporter = job[2] result = Minitest.run_one_method klass, method + if result.error? + translate_exceptions result + end queue.record reporter, result end } @@ -491,6 +494,20 @@ class ForkingExecutor @size.times { @queue << nil } pool.each { |pid| Process.waitpid pid } end + + private + def translate_exceptions(result) + result.failures.map! { |e| + begin + Marshal.dump e + e + rescue TypeError + ex = Exception.new e.message + ex.set_backtrace e.backtrace + Minitest::UnexpectedError.new ex + end + } + end end if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0 diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index b6b5a218cc..f2a4503f13 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'action_view/vendor/html-scanner' require 'controller/fake_controllers' class ActionPackAssertionsController < ActionController::Base @@ -147,11 +146,6 @@ end class ActionPackAssertionsControllerTest < ActionController::TestCase - def test_assert_tag_and_url_for - get :render_url - assert_tag :content => "/action_pack_assertions/flash_me" - end - def test_render_file_absolute_path get :render_file_absolute_path assert_match(/\A= Action Pack/, @response.body) @@ -165,24 +159,24 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_get_request assert_raise(RuntimeError) { get :raise_exception_on_get } get :raise_exception_on_post - assert_equal @response.body, 'request method: GET' + assert_equal 'request method: GET', @response.body end def test_post_request assert_raise(RuntimeError) { post :raise_exception_on_post } post :raise_exception_on_get - assert_equal @response.body, 'request method: POST' + assert_equal 'request method: POST', @response.body end def test_get_post_request_switch post :raise_exception_on_get - assert_equal @response.body, 'request method: POST' + assert_equal 'request method: POST', @response.body get :raise_exception_on_post - assert_equal @response.body, 'request method: GET' + assert_equal 'request method: GET', @response.body post :raise_exception_on_get - assert_equal @response.body, 'request method: POST' + assert_equal 'request method: POST', @response.body get :raise_exception_on_post - assert_equal @response.body, 'request method: GET' + assert_equal 'request method: GET', @response.body end def test_string_constraint @@ -302,7 +296,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_session_exist process :session_stuffing - assert_equal session['xmas'], 'turkey' + assert_equal 'turkey', session['xmas'] end def session_does_not_exist @@ -488,6 +482,11 @@ class AssertTemplateTest < ActionController::TestCase assert_raise(ActiveSupport::TestCase::Assertion) do assert_template :file => 'test/hello_world' end + + get :render_file_absolute_path + assert_raise(ActiveSupport::TestCase::Assertion) do + assert_template file: nil + end end def test_with_nil_passes_when_no_template_rendered @@ -612,6 +611,24 @@ class AssertTemplateTest < ActionController::TestCase get :nothing assert_template nil + + get :partial + assert_template partial: 'test/_partial' + + get :nothing + assert_template partial: nil + + get :render_with_layout + assert_template layout: 'layouts/standard' + + get :nothing + assert_template layout: nil + + get :render_file_relative_path + assert_template file: 'README.rdoc' + + get :nothing + assert_template file: nil end end diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb deleted file mode 100644 index f07d201563..0000000000 --- a/actionpack/test/controller/assert_select_test.rb +++ /dev/null @@ -1,356 +0,0 @@ -# encoding: utf-8 -#-- -# Copyright (c) 2006 Assaf Arkin (http://labnotes.org) -# Under MIT and/or CC By license. -#++ - -require 'abstract_unit' -require 'controller/fake_controllers' - -require 'action_mailer' -require 'action_view' - -ActionMailer::Base.send(:include, ActionView::Layouts) -ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH - -class AssertSelectTest < ActionController::TestCase - Assertion = ActiveSupport::TestCase::Assertion - - class AssertSelectMailer < ActionMailer::Base - def test(html) - mail :body => html, :content_type => "text/html", - :subject => "Test e-mail", :from => "test@test.host", :to => "test <test@test.host>" - end - end - - class AssertMultipartSelectMailer < ActionMailer::Base - def test(options) - mail :subject => "Test e-mail", :from => "test@test.host", :to => "test <test@test.host>" do |format| - format.text { render :text => options[:text] } - format.html { render :text => options[:html] } - end - end - end - - class AssertSelectController < ActionController::Base - def response_with=(content) - @content = content - end - - def response_with(&block) - @update = block - end - - def html() - render :text=>@content, :layout=>false, :content_type=>Mime::HTML - @content = nil - end - - def xml() - render :text=>@content, :layout=>false, :content_type=>Mime::XML - @content = nil - end - end - - tests AssertSelectController - - def setup - super - @old_delivery_method = ActionMailer::Base.delivery_method - @old_perform_deliveries = ActionMailer::Base.perform_deliveries - ActionMailer::Base.delivery_method = :test - ActionMailer::Base.perform_deliveries = true - end - - def teardown - super - ActionMailer::Base.delivery_method = @old_delivery_method - ActionMailer::Base.perform_deliveries = @old_perform_deliveries - ActionMailer::Base.deliveries.clear - end - - def assert_failure(message, &block) - e = assert_raise(Assertion, &block) - assert_match(message, e.message) if Regexp === message - assert_equal(message, e.message) if String === message - end - - # - # Test assert select. - # - - def test_assert_select - render_html %Q{<div id="1"></div><div id="2"></div>} - assert_select "div", 2 - assert_failure(/\AExpected at least 1 element matching \"p\", found 0\.$/) { assert_select "p" } - end - - def test_equality_integer - render_html %Q{<div id="1"></div><div id="2"></div>} - assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) { assert_select "div", 3 } - assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", 0 } - end - - def test_equality_true_false - render_html %Q{<div id="1"></div><div id="2"></div>} - assert_nothing_raised { assert_select "div" } - assert_raise(Assertion) { assert_select "p" } - assert_nothing_raised { assert_select "div", true } - assert_raise(Assertion) { assert_select "p", true } - assert_raise(Assertion) { assert_select "div", false } - assert_nothing_raised { assert_select "p", false } - end - - def test_equality_false_message - render_html %Q{<div id="1"></div><div id="2"></div>} - assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", false } - end - - def test_equality_string_and_regexp - render_html %Q{<div id="1">foo</div><div id="2">foo</div>} - assert_nothing_raised { assert_select "div", "foo" } - assert_raise(Assertion) { assert_select "div", "bar" } - assert_failure(/\A<bar> expected but was\n<foo>\.$/) { assert_select "div", "bar" } - assert_nothing_raised { assert_select "div", :text=>"foo" } - assert_raise(Assertion) { assert_select "div", :text=>"bar" } - assert_nothing_raised { assert_select "div", /(foo|bar)/ } - assert_raise(Assertion) { assert_select "div", /foobar/ } - assert_nothing_raised { assert_select "div", :text=>/(foo|bar)/ } - assert_raise(Assertion) { assert_select "div", :text=>/foobar/ } - assert_raise(Assertion) { assert_select "p", :text=>/foobar/ } - end - - def test_equality_of_html - render_html %Q{<p>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</p>} - text = "\"This is not a big problem,\" he said." - html = "<em>\"This is <strong>not</strong> a big problem,\"</em> he said." - assert_nothing_raised { assert_select "p", text } - assert_raise(Assertion) { assert_select "p", html } - assert_nothing_raised { assert_select "p", :html=>html } - assert_raise(Assertion) { assert_select "p", :html=>text } - assert_failure(/\A<#{text}> expected but was\n<#{html}>\.$/) { assert_select "p", :html=>text } - # No stripping for pre. - render_html %Q{<pre>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</pre>} - text = "\n\"This is not a big problem,\" he said.\n" - html = "\n<em>\"This is <strong>not</strong> a big problem,\"</em> he said.\n" - assert_nothing_raised { assert_select "pre", text } - assert_raise(Assertion) { assert_select "pre", html } - assert_nothing_raised { assert_select "pre", :html=>html } - assert_raise(Assertion) { assert_select "pre", :html=>text } - end - - def test_strip_textarea - render_html %Q{<textarea>\n\nfoo\n</textarea>} - assert_select "textarea", "\nfoo\n" - render_html %Q{<textarea>\nfoo</textarea>} - assert_select "textarea", "foo" - end - - def test_counts - render_html %Q{<div id="1">foo</div><div id="2">foo</div>} - assert_nothing_raised { assert_select "div", 2 } - assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do - assert_select "div", 3 - end - assert_nothing_raised { assert_select "div", 1..2 } - assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do - assert_select "div", 3..4 - end - assert_nothing_raised { assert_select "div", :count=>2 } - assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do - assert_select "div", :count=>3 - end - assert_nothing_raised { assert_select "div", :minimum=>1 } - assert_nothing_raised { assert_select "div", :minimum=>2 } - assert_failure(/\AExpected at least 3 elements matching \"div\", found 2\.$/) do - assert_select "div", :minimum=>3 - end - assert_nothing_raised { assert_select "div", :maximum=>2 } - assert_nothing_raised { assert_select "div", :maximum=>3 } - assert_failure(/\AExpected at most 1 element matching \"div\", found 2\.$/) do - assert_select "div", :maximum=>1 - end - assert_nothing_raised { assert_select "div", :minimum=>1, :maximum=>2 } - assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do - assert_select "div", :minimum=>3, :maximum=>4 - end - end - - def test_substitution_values - render_html %Q{<div id="1">foo</div><div id="2">foo</div>} - assert_select "div#?", /\d+/ do |elements| - assert_equal 2, elements.size - end - assert_select "div" do - assert_select "div#?", /\d+/ do |elements| - assert_equal 2, elements.size - assert_select "#1" - assert_select "#2" - end - end - end - - def test_nested_assert_select - render_html %Q{<div id="1">foo</div><div id="2">foo</div>} - assert_select "div" do |elements| - assert_equal 2, elements.size - assert_select elements[0], "#1" - assert_select elements[1], "#2" - end - assert_select "div" do - assert_select "div" do |elements| - assert_equal 2, elements.size - # Testing in a group is one thing - assert_select "#1,#2" - # Testing individually is another. - assert_select "#1" - assert_select "#2" - assert_select "#3", false - end - end - - assert_failure(/\AExpected at least 1 element matching \"#4\", found 0\.$/) do - assert_select "div" do - assert_select "#4" - end - end - end - - def test_assert_select_text_match - render_html %Q{<div id="1"><span>foo</span></div><div id="2"><span>bar</span></div>} - assert_select "div" do - assert_nothing_raised { assert_select "div", "foo" } - assert_nothing_raised { assert_select "div", "bar" } - assert_nothing_raised { assert_select "div", /\w*/ } - assert_nothing_raised { assert_select "div", :text => /\w*/, :count=>2 } - assert_raise(Assertion) { assert_select "div", :text=>"foo", :count=>2 } - assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" } - assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" } - assert_nothing_raised { assert_select "div", :html=>/\w*/ } - assert_nothing_raised { assert_select "div", :html=>/\w*/, :count=>2 } - assert_raise(Assertion) { assert_select "div", :html=>"<span>foo</span>", :count=>2 } - end - end - - def test_elect_with_xml_namespace_attributes - render_html %Q{<link xlink:href="http://nowhere.com"></link>} - assert_nothing_raised { assert_select "link[xlink:href=http://nowhere.com]" } - end - - # - # Test css_select. - # - - def test_css_select - render_html %Q{<div id="1"></div><div id="2"></div>} - assert_equal 2, css_select("div").size - assert_equal 0, css_select("p").size - end - - def test_nested_css_select - render_html %Q{<div id="1">foo</div><div id="2">foo</div>} - assert_select "div#?", /\d+/ do |elements| - assert_equal 1, css_select(elements[0], "div").size - assert_equal 1, css_select(elements[1], "div").size - end - assert_select "div" do - assert_equal 2, css_select("div").size - css_select("div").each do |element| - # Testing as a group is one thing - assert !css_select("#1,#2").empty? - # Testing individually is another - assert !css_select("#1").empty? - assert !css_select("#2").empty? - end - end - end - - def test_feed_item_encoded - render_xml <<-EOF -<rss version="2.0"> - <channel> - <item> - <description> - <![CDATA[ - <p>Test 1</p> - ]]> - </description> - </item> - <item> - <description> - <![CDATA[ - <p>Test 2</p> - ]]> - </description> - </item> - </channel> -</rss> -EOF - assert_select "channel item description" do - # Test element regardless of wrapper. - assert_select_encoded do - assert_select "p", :count=>2, :text=>/Test/ - end - # Test through encoded wrapper. - assert_select_encoded do - assert_select "encoded p", :count=>2, :text=>/Test/ - end - # Use :root instead (recommended) - assert_select_encoded do - assert_select ":root p", :count=>2, :text=>/Test/ - end - # Test individually. - assert_select "description" do |elements| - assert_select_encoded elements[0] do - assert_select "p", "Test 1" - end - assert_select_encoded elements[1] do - assert_select "p", "Test 2" - end - end - end - - # Test that we only un-encode element itself. - assert_select "channel item" do - assert_select_encoded do - assert_select "p", 0 - end - end - end - - # - # Test assert_select_email - # - - def test_assert_select_email - assert_raise(Assertion) { assert_select_email {} } - AssertSelectMailer.test("<div><p>foo</p><p>bar</p></div>").deliver - assert_select_email do - assert_select "div:root" do - assert_select "p:first-child", "foo" - assert_select "p:last-child", "bar" - end - end - end - - def test_assert_select_email_multipart - AssertMultipartSelectMailer.test(:html => "<div><p>foo</p><p>bar</p></div>", :text => 'foo bar').deliver - assert_select_email do - assert_select "div:root" do - assert_select "p:first-child", "foo" - assert_select "p:last-child", "bar" - end - end - end - - protected - def render_html(html) - @controller.response_with = html - get :html - end - - def render_xml(xml) - @controller.response_with = xml - get :xml - end -end diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index b2bfdae174..950788743e 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -158,7 +158,7 @@ class PerformActionTest < ActionController::TestCase exception = assert_raise AbstractController::ActionNotFound do get :non_existent end - assert_equal exception.message, "The action 'non_existent' could not be found for EmptyController" + assert_equal "The action 'non_existent' could not be found for EmptyController", exception.message end def test_get_on_hidden_should_fail diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 78cce3aa64..99b53e6fd2 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -1,6 +1,5 @@ require 'abstract_unit' require 'controller/fake_controllers' -require 'action_view/vendor/html-scanner' require 'rails/engine' class SessionTest < ActiveSupport::TestCase @@ -293,7 +292,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest assert_equal({}, cookies.to_hash) assert_equal "OK", body assert_equal "OK", response.body - assert_kind_of HTML::Document, html_document + assert_kind_of Nokogiri::HTML::Document, html_document assert_equal 1, request_count end end @@ -309,7 +308,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest assert_equal({}, cookies.to_hash) assert_equal "Created", body assert_equal "Created", response.body - assert_kind_of HTML::Document, html_document + assert_kind_of Nokogiri::HTML::Document, html_document assert_equal 1, request_count end end @@ -369,7 +368,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest assert_response :redirect assert_response :found assert_equal "<html><body>You are being <a href=\"http://www.example.com/get\">redirected</a>.</body></html>", response.body - assert_kind_of HTML::Document, html_document + assert_kind_of Nokogiri::HTML::Document, html_document assert_equal 1, request_count follow_redirect! diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 0500b7c789..7fd1276e98 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -162,7 +162,7 @@ module ActionController end def with_stale - render :text => 'stale' if stale?(:etag => "123") + render text: 'stale' if stale?(etag: "123", template: false) end def exception_in_view diff --git a/actionpack/test/controller/mime/respond_with_test.rb b/actionpack/test/controller/mime/respond_with_test.rb deleted file mode 100644 index 115f3b2f41..0000000000 --- a/actionpack/test/controller/mime/respond_with_test.rb +++ /dev/null @@ -1,737 +0,0 @@ -require 'abstract_unit' -require 'controller/fake_models' - -class RespondWithController < ActionController::Base - class CustomerWithJson < Customer - def to_json; super; end - end - - respond_to :html, :json, :touch - respond_to :xml, :except => :using_resource_with_block - respond_to :js, :only => [ :using_resource_with_block, :using_resource, 'using_hash_resource' ] - - def using_resource - respond_with(resource) - end - - def using_hash_resource - respond_with({:result => resource}) - end - - def using_resource_with_block - respond_with(resource) do |format| - format.csv { render :text => "CSV" } - end - end - - def using_resource_with_overwrite_block - respond_with(resource) do |format| - format.html { render :text => "HTML" } - end - end - - def using_resource_with_collection - respond_with([resource, Customer.new("jamis", 9)]) - end - - def using_resource_with_parent - respond_with(Quiz::Store.new("developer?", 11), Customer.new("david", 13)) - end - - def using_resource_with_status_and_location - respond_with(resource, :location => "http://test.host/", :status => :created) - end - - def using_resource_with_json - respond_with(CustomerWithJson.new("david", request.delete? ? nil : 13)) - end - - def using_invalid_resource_with_template - respond_with(resource) - end - - def using_options_with_template - @customer = resource - respond_with(@customer, :status => 123, :location => "http://test.host/") - end - - def using_resource_with_responder - responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" } - respond_with(resource, :responder => responder) - end - - def using_resource_with_action - respond_with(resource, :action => :foo) do |format| - format.html { raise ActionView::MissingTemplate.new([], "bar", ["foo"], {}, false) } - end - end - - def using_responder_with_respond - responder = Class.new(ActionController::Responder) do - def respond; @controller.render :text => "respond #{format}"; end - end - respond_with(resource, :responder => responder) - end - - def respond_with_additional_params - @params = RespondWithController.params - respond_with({:result => resource}, @params) - end - -protected - def self.params - { - :foo => 'bar' - } - end - - def resource - Customer.new("david", request.delete? ? nil : 13) - end -end - -class InheritedRespondWithController < RespondWithController - clear_respond_to - respond_to :xml, :json - - def index - respond_with(resource) do |format| - format.json { render :text => "JSON" } - end - end -end - -class RenderJsonRespondWithController < RespondWithController - clear_respond_to - respond_to :json - - def index - respond_with(resource) do |format| - format.json { render :json => RenderJsonTestException.new('boom') } - end - end - - def create - resource = ValidatedCustomer.new(params[:name], 1) - respond_with(resource) do |format| - format.json do - if resource.errors.empty? - render :json => { :valid => true } - else - render :json => { :valid => false } - end - end - end - end -end - -class CsvRespondWithController < ActionController::Base - respond_to :csv - - class RespondWithCsv - def to_csv - "c,s,v" - end - end - - def index - respond_with(RespondWithCsv.new) - end -end - -class EmptyRespondWithController < ActionController::Base - def index - respond_with(Customer.new("david", 13)) - end -end - -class RespondWithControllerTest < ActionController::TestCase - def setup - super - @request.host = "www.example.com" - Mime::Type.register_alias('text/html', :iphone) - Mime::Type.register_alias('text/html', :touch) - Mime::Type.register('text/x-mobile', :mobile) - end - - def teardown - super - Mime::Type.unregister(:iphone) - Mime::Type.unregister(:touch) - Mime::Type.unregister(:mobile) - end - - def test_respond_with_shouldnt_modify_original_hash - get :respond_with_additional_params - assert_equal RespondWithController.params, assigns(:params) - end - - def test_using_resource - @request.accept = "application/xml" - get :using_resource - assert_equal "application/xml", @response.content_type - assert_equal "<name>david</name>", @response.body - - @request.accept = "application/json" - assert_raise ActionView::MissingTemplate do - get :using_resource - end - end - - def test_using_resource_with_js_simply_tries_to_render_the_template - @request.accept = "text/javascript" - get :using_resource - assert_equal "text/javascript", @response.content_type - assert_equal "alert(\"Hi\");", @response.body - end - - def test_using_hash_resource_with_js_raises_an_error_if_template_cant_be_found - @request.accept = "text/javascript" - assert_raise ActionView::MissingTemplate do - get :using_hash_resource - end - end - - def test_using_hash_resource - @request.accept = "application/xml" - get :using_hash_resource - assert_equal "application/xml", @response.content_type - assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<hash>\n <name>david</name>\n</hash>\n", @response.body - - @request.accept = "application/json" - get :using_hash_resource - assert_equal "application/json", @response.content_type - assert @response.body.include?("result") - assert @response.body.include?('"name":"david"') - assert @response.body.include?('"id":13') - end - - def test_using_hash_resource_with_post - @request.accept = "application/json" - assert_raise ArgumentError, "Nil location provided. Can't build URI." do - post :using_hash_resource - end - end - - def test_using_resource_with_block - @request.accept = "*/*" - get :using_resource_with_block - assert_equal "text/html", @response.content_type - assert_equal 'Hello world!', @response.body - - @request.accept = "text/csv" - get :using_resource_with_block - assert_equal "text/csv", @response.content_type - assert_equal "CSV", @response.body - - @request.accept = "application/xml" - get :using_resource - assert_equal "application/xml", @response.content_type - assert_equal "<name>david</name>", @response.body - end - - def test_using_resource_with_overwrite_block - get :using_resource_with_overwrite_block - assert_equal "text/html", @response.content_type - assert_equal "HTML", @response.body - end - - def test_not_acceptable - @request.accept = "application/xml" - assert_raises(ActionController::UnknownFormat) do - get :using_resource_with_block - end - - @request.accept = "text/javascript" - assert_raises(ActionController::UnknownFormat) do - get :using_resource_with_overwrite_block - end - end - - def test_using_resource_for_post_with_html_redirects_on_success - with_test_route_set do - post :using_resource - assert_equal "text/html", @response.content_type - assert_equal 302, @response.status - assert_equal "http://www.example.com/customers/13", @response.location - assert @response.redirect? - end - end - - def test_using_resource_for_post_with_html_rerender_on_failure - with_test_route_set do - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - post :using_resource - assert_equal "text/html", @response.content_type - assert_equal 200, @response.status - assert_equal "New world!\n", @response.body - assert_nil @response.location - end - end - - def test_using_resource_for_post_with_xml_yields_created_on_success - with_test_route_set do - @request.accept = "application/xml" - post :using_resource - assert_equal "application/xml", @response.content_type - assert_equal 201, @response.status - assert_equal "<name>david</name>", @response.body - assert_equal "http://www.example.com/customers/13", @response.location - end - end - - def test_using_resource_for_post_with_xml_yields_unprocessable_entity_on_failure - with_test_route_set do - @request.accept = "application/xml" - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - post :using_resource - assert_equal "application/xml", @response.content_type - assert_equal 422, @response.status - assert_equal errors.to_xml, @response.body - assert_nil @response.location - end - end - - def test_using_resource_for_post_with_json_yields_unprocessable_entity_on_failure - with_test_route_set do - @request.accept = "application/json" - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - post :using_resource - assert_equal "application/json", @response.content_type - assert_equal 422, @response.status - errors = {:errors => errors} - assert_equal errors.to_json, @response.body - assert_nil @response.location - end - end - - def test_using_resource_for_patch_with_html_redirects_on_success - with_test_route_set do - patch :using_resource - assert_equal "text/html", @response.content_type - assert_equal 302, @response.status - assert_equal "http://www.example.com/customers/13", @response.location - assert @response.redirect? - end - end - - def test_using_resource_for_patch_with_html_rerender_on_failure - with_test_route_set do - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - patch :using_resource - assert_equal "text/html", @response.content_type - assert_equal 200, @response.status - assert_equal "Edit world!\n", @response.body - assert_nil @response.location - end - end - - def test_using_resource_for_patch_with_html_rerender_on_failure_even_on_method_override - with_test_route_set do - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - @request.env["rack.methodoverride.original_method"] = "POST" - patch :using_resource - assert_equal "text/html", @response.content_type - assert_equal 200, @response.status - assert_equal "Edit world!\n", @response.body - assert_nil @response.location - end - end - - def test_using_resource_for_put_with_html_redirects_on_success - with_test_route_set do - put :using_resource - assert_equal "text/html", @response.content_type - assert_equal 302, @response.status - assert_equal "http://www.example.com/customers/13", @response.location - assert @response.redirect? - end - end - - def test_using_resource_for_put_with_html_rerender_on_failure - with_test_route_set do - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - put :using_resource - - assert_equal "text/html", @response.content_type - assert_equal 200, @response.status - assert_equal "Edit world!\n", @response.body - assert_nil @response.location - end - end - - def test_using_resource_for_put_with_html_rerender_on_failure_even_on_method_override - with_test_route_set do - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - @request.env["rack.methodoverride.original_method"] = "POST" - put :using_resource - assert_equal "text/html", @response.content_type - assert_equal 200, @response.status - assert_equal "Edit world!\n", @response.body - assert_nil @response.location - end - end - - def test_using_resource_for_put_with_xml_yields_no_content_on_success - @request.accept = "application/xml" - put :using_resource - assert_equal "application/xml", @response.content_type - assert_equal 204, @response.status - assert_equal "", @response.body - end - - def test_using_resource_for_put_with_json_yields_no_content_on_success - @request.accept = "application/json" - put :using_resource_with_json - assert_equal "application/json", @response.content_type - assert_equal 204, @response.status - assert_equal "", @response.body - end - - def test_using_resource_for_put_with_xml_yields_unprocessable_entity_on_failure - @request.accept = "application/xml" - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - put :using_resource - assert_equal "application/xml", @response.content_type - assert_equal 422, @response.status - assert_equal errors.to_xml, @response.body - assert_nil @response.location - end - - def test_using_resource_for_put_with_json_yields_unprocessable_entity_on_failure - @request.accept = "application/json" - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - put :using_resource - assert_equal "application/json", @response.content_type - assert_equal 422, @response.status - errors = {:errors => errors} - assert_equal errors.to_json, @response.body - assert_nil @response.location - end - - def test_using_resource_for_delete_with_html_redirects_on_success - with_test_route_set do - Customer.any_instance.stubs(:destroyed?).returns(true) - delete :using_resource - assert_equal "text/html", @response.content_type - assert_equal 302, @response.status - assert_equal "http://www.example.com/customers", @response.location - end - end - - def test_using_resource_for_delete_with_xml_yields_no_content_on_success - Customer.any_instance.stubs(:destroyed?).returns(true) - @request.accept = "application/xml" - delete :using_resource - assert_equal "application/xml", @response.content_type - assert_equal 204, @response.status - assert_equal "", @response.body - end - - def test_using_resource_for_delete_with_json_yields_no_content_on_success - Customer.any_instance.stubs(:destroyed?).returns(true) - @request.accept = "application/json" - delete :using_resource_with_json - assert_equal "application/json", @response.content_type - assert_equal 204, @response.status - assert_equal "", @response.body - end - - def test_using_resource_for_delete_with_html_redirects_on_failure - with_test_route_set do - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - Customer.any_instance.stubs(:destroyed?).returns(false) - delete :using_resource - assert_equal "text/html", @response.content_type - assert_equal 302, @response.status - assert_equal "http://www.example.com/customers", @response.location - end - end - - def test_using_resource_with_parent_for_get - @request.accept = "application/xml" - get :using_resource_with_parent - assert_equal "application/xml", @response.content_type - assert_equal 200, @response.status - assert_equal "<name>david</name>", @response.body - end - - def test_using_resource_with_parent_for_post - with_test_route_set do - @request.accept = "application/xml" - - post :using_resource_with_parent - assert_equal "application/xml", @response.content_type - assert_equal 201, @response.status - assert_equal "<name>david</name>", @response.body - assert_equal "http://www.example.com/quiz_stores/11/customers/13", @response.location - - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - post :using_resource - assert_equal "application/xml", @response.content_type - assert_equal 422, @response.status - assert_equal errors.to_xml, @response.body - assert_nil @response.location - end - end - - def test_using_resource_with_collection - @request.accept = "application/xml" - get :using_resource_with_collection - assert_equal "application/xml", @response.content_type - assert_equal 200, @response.status - assert_match(/<name>david<\/name>/, @response.body) - assert_match(/<name>jamis<\/name>/, @response.body) - end - - def test_using_resource_with_action - @controller.instance_eval do - def render(params={}) - self.response_body = "#{params[:action]} - #{formats}" - end - end - - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - - post :using_resource_with_action - assert_equal "foo - #{[:html].to_s}", @controller.response.body - end - - def test_respond_as_responder_entry_point - @request.accept = "text/html" - get :using_responder_with_respond - assert_equal "respond html", @response.body - - @request.accept = "application/xml" - get :using_responder_with_respond - assert_equal "respond xml", @response.body - end - - def test_clear_respond_to - @controller = InheritedRespondWithController.new - @request.accept = "text/html" - assert_raises(ActionController::UnknownFormat) do - get :index - end - end - - def test_first_in_respond_to_has_higher_priority - @controller = InheritedRespondWithController.new - @request.accept = "*/*" - get :index - assert_equal "application/xml", @response.content_type - assert_equal "<name>david</name>", @response.body - end - - def test_block_inside_respond_with_is_rendered - @controller = InheritedRespondWithController.new - @request.accept = "application/json" - get :index - assert_equal "JSON", @response.body - end - - def test_render_json_object_responds_to_str_still_produce_json - @controller = RenderJsonRespondWithController.new - @request.accept = "application/json" - get :index, :format => :json - assert_match(/"message":"boom"/, @response.body) - assert_match(/"error":"RenderJsonTestException"/, @response.body) - end - - def test_api_response_with_valid_resource_respect_override_block - @controller = RenderJsonRespondWithController.new - post :create, :name => "sikachu", :format => :json - assert_equal '{"valid":true}', @response.body - end - - def test_api_response_with_invalid_resource_respect_override_block - @controller = RenderJsonRespondWithController.new - post :create, :name => "david", :format => :json - assert_equal '{"valid":false}', @response.body - end - - def test_no_double_render_is_raised - @request.accept = "text/html" - assert_raise ActionView::MissingTemplate do - get :using_resource - end - end - - def test_using_resource_with_status_and_location - @request.accept = "text/html" - post :using_resource_with_status_and_location - assert @response.redirect? - assert_equal "http://test.host/", @response.location - - @request.accept = "application/xml" - get :using_resource_with_status_and_location - assert_equal 201, @response.status - end - - def test_using_resource_with_status_and_location_with_invalid_resource - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - - @request.accept = "text/xml" - - post :using_resource_with_status_and_location - assert_equal errors.to_xml, @response.body - assert_equal 422, @response.status - assert_equal nil, @response.location - - put :using_resource_with_status_and_location - assert_equal errors.to_xml, @response.body - assert_equal 422, @response.status - assert_equal nil, @response.location - end - - def test_using_invalid_resource_with_template - errors = { :name => :invalid } - Customer.any_instance.stubs(:errors).returns(errors) - - @request.accept = "text/xml" - - post :using_invalid_resource_with_template - assert_equal errors.to_xml, @response.body - assert_equal 422, @response.status - assert_equal nil, @response.location - - put :using_invalid_resource_with_template - assert_equal errors.to_xml, @response.body - assert_equal 422, @response.status - assert_equal nil, @response.location - end - - def test_using_options_with_template - @request.accept = "text/xml" - - post :using_options_with_template - assert_equal "<customer-name>david</customer-name>", @response.body - assert_equal 123, @response.status - assert_equal "http://test.host/", @response.location - - put :using_options_with_template - assert_equal "<customer-name>david</customer-name>", @response.body - assert_equal 123, @response.status - assert_equal "http://test.host/", @response.location - end - - def test_using_resource_with_responder - get :using_resource_with_responder - assert_equal "Resource name is david", @response.body - end - - def test_using_resource_with_set_responder - RespondWithController.responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" } - get :using_resource - assert_equal "Resource name is david", @response.body - ensure - RespondWithController.responder = ActionController::Responder - end - - def test_uses_renderer_if_an_api_behavior - ActionController::Renderers.add :csv do |obj, options| - send_data obj.to_csv, type: Mime::CSV - end - @controller = CsvRespondWithController.new - get :index, format: 'csv' - assert_equal Mime::CSV, @response.content_type - assert_equal "c,s,v", @response.body - ensure - ActionController::Renderers.remove :csv - end - - def test_raises_missing_renderer_if_an_api_behavior_with_no_renderer - @controller = CsvRespondWithController.new - assert_raise ActionController::MissingRenderer do - get :index, format: 'csv' - end - end - - def test_removing_renderers - ActionController::Renderers.add :csv do |obj, options| - send_data obj.to_csv, type: Mime::CSV - end - @controller = CsvRespondWithController.new - @request.accept = "text/csv" - get :index, format: 'csv' - assert_equal Mime::CSV, @response.content_type - - ActionController::Renderers.remove :csv - assert_raise ActionController::MissingRenderer do - get :index, format: 'csv' - end - ensure - ActionController::Renderers.remove :csv - end - - def test_error_is_raised_if_no_respond_to_is_declared_and_respond_with_is_called - @controller = EmptyRespondWithController.new - @request.accept = "*/*" - assert_raise RuntimeError do - get :index - end - end - - private - def with_test_route_set - with_routing do |set| - set.draw do - resources :customers - resources :quiz_stores do - resources :customers - end - get ":controller/:action" - end - yield - end - end -end - -class FlashResponder < ActionController::Responder - def initialize(controller, resources, options={}) - super - end - - def to_html - controller.flash[:notice] = 'Success' - super - end -end - -class FlashResponderController < ActionController::Base - self.responder = FlashResponder - respond_to :html - - def index - respond_with Object.new do |format| - format.html { render :text => 'HTML' } - end - end -end - -class FlashResponderControllerTest < ActionController::TestCase - tests FlashResponderController - - def test_respond_with_block_executed - get :index - assert_equal 'HTML', @response.body - end - - def test_flash_responder_executed - get :index - assert_equal 'Success', flash[:notice] - end -end diff --git a/actionpack/test/controller/mime/responders_test.rb b/actionpack/test/controller/mime/responders_test.rb new file mode 100644 index 0000000000..032b4c0ab1 --- /dev/null +++ b/actionpack/test/controller/mime/responders_test.rb @@ -0,0 +1,32 @@ +require 'abstract_unit' +require 'controller/fake_models' + +class ResponderTest < ActionController::TestCase + def test_class_level_respond_to + e = assert_raises(NoMethodError) do + Class.new(ActionController::Base) do + respond_to :json + end + end + + assert_includes e.message, '`responders` gem' + assert_includes e.message, '~> 2.0' + end + + def test_respond_with + klass = Class.new(ActionController::Base) do + def index + respond_with Customer.new("david", 13) + end + end + + @controller = klass.new + + e = assert_raises(NoMethodError) do + get :index + end + + assert_includes e.message, '`responders` gem' + assert_includes e.message, '~> 2.0' + end +end diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb new file mode 100644 index 0000000000..97875c3cbb --- /dev/null +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -0,0 +1,125 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' +require 'active_support/core_ext/hash/transform_values' + +class ParametersAccessorsTest < ActiveSupport::TestCase + setup do + @params = ActionController::Parameters.new( + person: { + age: '32', + name: { + first: 'David', + last: 'Heinemeier Hansson' + }, + addresses: [{city: 'Chicago', state: 'Illinois'}] + } + ) + end + + test "[] retains permitted status" do + @params.permit! + assert @params[:person].permitted? + assert @params[:person][:name].permitted? + end + + test "[] retains unpermitted status" do + assert_not @params[:person].permitted? + assert_not @params[:person][:name].permitted? + end + + test "each carries permitted status" do + @params.permit! + @params.each { |key, value| assert(value.permitted?) if key == "person" } + end + + test "each carries unpermitted status" do + @params.each { |key, value| assert_not(value.permitted?) if key == "person" } + end + + test "each_pair carries permitted status" do + @params.permit! + @params.each_pair { |key, value| assert(value.permitted?) if key == "person" } + end + + test "each_pair carries unpermitted status" do + @params.each_pair { |key, value| assert_not(value.permitted?) if key == "person" } + end + + test "except retains permitted status" do + @params.permit! + assert @params.except(:person).permitted? + assert @params[:person].except(:name).permitted? + end + + test "except retains unpermitted status" do + assert_not @params.except(:person).permitted? + assert_not @params[:person].except(:name).permitted? + end + + test "fetch retains permitted status" do + @params.permit! + assert @params.fetch(:person).permitted? + assert @params[:person].fetch(:name).permitted? + end + + test "fetch retains unpermitted status" do + assert_not @params.fetch(:person).permitted? + assert_not @params[:person].fetch(:name).permitted? + end + + test "reject retains permitted status" do + assert_not @params.reject { |k| k == "person" }.permitted? + end + + test "reject retains unpermitted status" do + @params.permit! + assert @params.reject { |k| k == "person" }.permitted? + end + + test "select retains permitted status" do + @params.permit! + assert @params.select { |k| k == "person" }.permitted? + end + + test "select retains unpermitted status" do + assert_not @params.select { |k| k == "person" }.permitted? + end + + test "slice retains permitted status" do + @params.permit! + assert @params.slice(:person).permitted? + end + + test "slice retains unpermitted status" do + assert_not @params.slice(:person).permitted? + end + + test "transform_keys retains permitted status" do + @params.permit! + assert @params.transform_keys { |k| k }.permitted? + end + + test "transform_keys retains unpermitted status" do + assert_not @params.transform_keys { |k| k }.permitted? + end + + test "transform_values retains permitted status" do + @params.permit! + assert @params.transform_values { |v| v }.permitted? + end + + test "transform_values retains unpermitted status" do + assert_not @params.transform_values { |v| v }.permitted? + end + + test "values_at retains permitted status" do + @params.permit! + assert @params.values_at(:person).first.permitted? + assert @params[:person].values_at(:name).first.permitted? + end + + test "values_at retains unpermitted status" do + assert_not @params.values_at(:person).first.permitted? + assert_not @params[:person].values_at(:name).first.permitted? + end +end diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb new file mode 100644 index 0000000000..744d8664be --- /dev/null +++ b/actionpack/test/controller/parameters/mutators_test.rb @@ -0,0 +1,99 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' +require 'active_support/core_ext/hash/transform_values' + +class ParametersMutatorsTest < ActiveSupport::TestCase + setup do + @params = ActionController::Parameters.new( + person: { + age: '32', + name: { + first: 'David', + last: 'Heinemeier Hansson' + }, + addresses: [{city: 'Chicago', state: 'Illinois'}] + } + ) + end + + test "delete retains permitted status" do + @params.permit! + assert @params.delete(:person).permitted? + end + + test "delete retains unpermitted status" do + assert_not @params.delete(:person).permitted? + end + + test "delete_if retains permitted status" do + @params.permit! + assert @params.delete_if { |k| k == "person" }.permitted? + end + + test "delete_if retains unpermitted status" do + assert_not @params.delete_if { |k| k == "person" }.permitted? + end + + test "extract! retains permitted status" do + @params.permit! + assert @params.extract!(:person).permitted? + end + + test "extract! retains unpermitted status" do + assert_not @params.extract!(:person).permitted? + end + + test "keep_if retains permitted status" do + @params.permit! + assert @params.keep_if { |k,v| k == "person" }.permitted? + end + + test "keep_if retains unpermitted status" do + assert_not @params.keep_if { |k,v| k == "person" }.permitted? + end + + test "reject! retains permitted status" do + @params.permit! + assert @params.reject! { |k| k == "person" }.permitted? + end + + test "reject! retains unpermitted status" do + assert_not @params.reject! { |k| k == "person" }.permitted? + end + + test "select! retains permitted status" do + @params.permit! + assert @params.select! { |k| k != "person" }.permitted? + end + + test "select! retains unpermitted status" do + assert_not @params.select! { |k| k != "person" }.permitted? + end + + test "slice! retains permitted status" do + @params.permit! + assert @params.slice!(:person).permitted? + end + + test "slice! retains unpermitted status" do + assert_not @params.slice!(:person).permitted? + end + + test "transform_keys! retains permitted status" do + @params.permit! + assert @params.transform_keys! { |k| k }.permitted? + end + + test "transform_keys! retains unpermitted status" do + assert_not @params.transform_keys! { |k| k }.permitted? + end + + test "transform_values! retains permitted status" do + @params.permit! + assert @params.transform_values! { |v| v }.permitted? + end + + test "transform_values! retains unpermitted status" do + assert_not @params.transform_values! { |v| v }.permitted? + end +end diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index aa894ffa17..ba98ad7605 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -194,42 +194,6 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal "monkey", @params.fetch(:foo) { "monkey" } end - test "not permitted is sticky on accessors" do - assert !@params.slice(:person).permitted? - assert !@params[:person][:name].permitted? - assert !@params[:person].except(:name).permitted? - - @params.each { |key, value| assert(!value.permitted?) if key == "person" } - - assert !@params.fetch(:person).permitted? - - assert !@params.values_at(:person).first.permitted? - end - - test "permitted is sticky on accessors" do - @params.permit! - assert @params.slice(:person).permitted? - assert @params[:person][:name].permitted? - assert @params[:person].except(:name).permitted? - - @params.each { |key, value| assert(value.permitted?) if key == "person" } - - assert @params.fetch(:person).permitted? - - assert @params.values_at(:person).first.permitted? - end - - test "not permitted is sticky on mutators" do - assert !@params.delete_if { |k| k == "person" }.permitted? - assert !@params.keep_if { |k,v| k == "person" }.permitted? - end - - test "permitted is sticky on mutators" do - @params.permit! - assert @params.delete_if { |k| k == "person" }.permitted? - assert @params.keep_if { |k,v| k == "person" }.permitted? - end - test "not permitted is sticky beyond merges" do assert !@params.merge(a: "b").permitted? end @@ -277,4 +241,43 @@ class ParametersPermitTest < ActiveSupport::TestCase test "permitting parameters as an array" do assert_equal "32", @params[:person].permit([ :age ])[:age] end + + test "to_h returns empty hash on unpermitted params" do + assert @params.to_h.is_a? Hash + assert_not @params.to_h.is_a? ActionController::Parameters + assert @params.to_h.empty? + end + + test "to_h returns converted hash on permitted params" do + @params.permit! + + assert @params.to_h.is_a? Hash + assert_not @params.to_h.is_a? ActionController::Parameters + assert_equal @params.to_hash, @params.to_h + end + + test "to_h returns converted hash when .permit_all_parameters is set" do + begin + ActionController::Parameters.permit_all_parameters = true + params = ActionController::Parameters.new(crab: "Senjougahara Hitagi") + + assert params.to_h.is_a? Hash + assert_not @params.to_h.is_a? ActionController::Parameters + assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_h) + ensure + ActionController::Parameters.permit_all_parameters = false + end + end + + test "to_h returns always permitted parameter on unpermitted params" do + params = ActionController::Parameters.new( + controller: "users", + action: "create", + user: { + name: "Sengoku Nadeko" + } + ) + + assert_equal({ "controller" => "users", "action" => "create" }, params.to_h) + end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 9926130c02..b036b6c08e 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -10,11 +10,17 @@ class TestControllerWithExtraEtags < ActionController::Base etag { nil } def fresh - render text: "stale" if stale?(etag: '123') + render text: "stale" if stale?(etag: '123', template: false) end def array - render text: "stale" if stale?(etag: %w(1 2 3)) + render text: "stale" if stale?(etag: %w(1 2 3), template: false) + end + + def with_template + if stale? template: 'test/hello_world' + render text: 'stale' + end end end @@ -409,6 +415,32 @@ class EtagRenderTest < ActionController::TestCase assert_response :success end + def test_etag_reflects_template_digest + get :with_template + assert_response :ok + assert_not_nil etag = @response.etag + + request.if_none_match = etag + get :with_template + assert_response :not_modified + + # Modify the template digest + path = File.expand_path('../../fixtures/test/hello_world.erb', __FILE__) + old = File.read(path) + + begin + File.write path, 'foo' + ActionView::Digestor.cache.clear + + request.if_none_match = etag + get :with_template + assert_response :ok + assert_not_equal etag, @response.etag + ensure + File.write path, old + end + end + def etag(record) Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record)).inspect end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 2a5aad9c0e..05ad8b6cdc 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -389,7 +389,8 @@ class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController SecureRandom.stubs(:base64).returns(@token + '<=?') get :meta assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token' - assert_select 'meta[name=?][content=?]', 'csrf-token', 'cf50faa3fe97702ca1ae<=?' + assert_select 'meta[name=?]', 'csrf-token' + assert_match(/cf50faa3fe97702ca1ae<=\?/, @response.body) end end diff --git a/actionpack/test/controller/selector_test.rb b/actionpack/test/controller/selector_test.rb deleted file mode 100644 index 1e80c8601c..0000000000 --- a/actionpack/test/controller/selector_test.rb +++ /dev/null @@ -1,629 +0,0 @@ -#-- -# Copyright (c) 2006 Assaf Arkin (http://labnotes.org) -# Under MIT and/or CC By license. -#++ - -require 'abstract_unit' -require 'controller/fake_controllers' -require 'action_view/vendor/html-scanner' - -class SelectorTest < ActiveSupport::TestCase - # - # Basic selector: element, id, class, attributes. - # - - def test_element - parse(%Q{<div id="1"></div><p></p><div id="2"></div>}) - # Match element by name. - select("div") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "2", @matches[1].attributes["id"] - # Not case sensitive. - select("DIV") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "2", @matches[1].attributes["id"] - # Universal match (all elements). - select("*") - assert_equal 3, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal nil, @matches[1].attributes["id"] - assert_equal "2", @matches[2].attributes["id"] - end - - - def test_identifier - parse(%Q{<div id="1"></div><p></p><div id="2"></div>}) - # Match element by ID. - select("div#1") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - # Match element by ID, substitute value. - select("div#?", 2) - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - # Element name does not match ID. - select("p#?", 2) - assert_equal 0, @matches.size - # Use regular expression. - select("#?", /\d/) - assert_equal 2, @matches.size - end - - - def test_class_name - parse(%Q{<div id="1" class=" foo "></div><p id="2" class=" foo bar "></p><div id="3" class="bar"></div>}) - # Match element with specified class. - select("div.foo") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - # Match any element with specified class. - select("*.foo") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "2", @matches[1].attributes["id"] - # Match elements with other class. - select("*.bar") - assert_equal 2, @matches.size - assert_equal "2", @matches[0].attributes["id"] - assert_equal "3", @matches[1].attributes["id"] - # Match only element with both class names. - select("*.bar.foo") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - end - - - def test_attribute - parse(%Q{<div id="1"></div><p id="2" title="" bar="foo"></p><div id="3" title="foo"></div>}) - # Match element with attribute. - select("div[title]") - assert_equal 1, @matches.size - assert_equal "3", @matches[0].attributes["id"] - # Match any element with attribute. - select("*[title]") - assert_equal 2, @matches.size - assert_equal "2", @matches[0].attributes["id"] - assert_equal "3", @matches[1].attributes["id"] - # Match element with attribute value. - select("*[title=foo]") - assert_equal 1, @matches.size - assert_equal "3", @matches[0].attributes["id"] - # Match element with attribute and attribute value. - select("[bar=foo][title]") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - # Not case sensitive. - select("[BAR=foo][TiTle]") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - end - - - def test_attribute_quoted - parse(%Q{<div id="1" title="foo"></div><div id="2" title="bar"></div><div id="3" title=" bar "></div>}) - # Match without quotes. - select("[title = bar]") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - # Match with single quotes. - select("[title = 'bar' ]") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - # Match with double quotes. - select("[title = \"bar\" ]") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - # Match with spaces. - select("[title = \" bar \" ]") - assert_equal 1, @matches.size - assert_equal "3", @matches[0].attributes["id"] - end - - - def test_attribute_equality - parse(%Q{<div id="1" title="foo bar"></div><div id="2" title="barbaz"></div>}) - # Match (fail) complete value. - select("[title=bar]") - assert_equal 0, @matches.size - # Match space-separate word. - select("[title~=foo]") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - select("[title~=bar]") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - # Match beginning of value. - select("[title^=ba]") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - # Match end of value. - select("[title$=ar]") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - # Match text in value. - select("[title*=bar]") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "2", @matches[1].attributes["id"] - # Match first space separated word. - select("[title|=foo]") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - select("[title|=bar]") - assert_equal 0, @matches.size - end - - - # - # Selector composition: groups, sibling, children - # - - - def test_selector_group - parse(%Q{<h1 id="1"></h1><h2 id="2"></h2><h3 id="3"></h3>}) - # Simple group selector. - select("h1,h3") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "3", @matches[1].attributes["id"] - select("h1 , h3") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "3", @matches[1].attributes["id"] - # Complex group selector. - parse(%Q{<h1 id="1"><a href="foo"></a></h1><h2 id="2"><a href="bar"></a></h2><h3 id="2"><a href="baz"></a></h3>}) - select("h1 a, h3 a") - assert_equal 2, @matches.size - assert_equal "foo", @matches[0].attributes["href"] - assert_equal "baz", @matches[1].attributes["href"] - # And now for the three selector challenge. - parse(%Q{<h1 id="1"><a href="foo"></a></h1><h2 id="2"><a href="bar"></a></h2><h3 id="2"><a href="baz"></a></h3>}) - select("h1 a, h2 a, h3 a") - assert_equal 3, @matches.size - assert_equal "foo", @matches[0].attributes["href"] - assert_equal "bar", @matches[1].attributes["href"] - assert_equal "baz", @matches[2].attributes["href"] - end - - - def test_sibling_selector - parse(%Q{<h1 id="1"></h1><h2 id="2"></h2><h3 id="3"></h3>}) - # Test next sibling. - select("h1+*") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - select("h1+h2") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - select("h1+h3") - assert_equal 0, @matches.size - select("*+h3") - assert_equal 1, @matches.size - assert_equal "3", @matches[0].attributes["id"] - # Test any sibling. - select("h1~*") - assert_equal 2, @matches.size - assert_equal "2", @matches[0].attributes["id"] - assert_equal "3", @matches[1].attributes["id"] - select("h2~*") - assert_equal 1, @matches.size - assert_equal "3", @matches[0].attributes["id"] - end - - - def test_children_selector - parse(%Q{<div><p id="1"><span id="2"></span></p></div><div><p id="3"><span id="4" class="foo"></span></p></div>}) - # Test child selector. - select("div>p") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "3", @matches[1].attributes["id"] - select("div>span") - assert_equal 0, @matches.size - select("div>p#3") - assert_equal 1, @matches.size - assert_equal "3", @matches[0].attributes["id"] - select("div>p>span") - assert_equal 2, @matches.size - assert_equal "2", @matches[0].attributes["id"] - assert_equal "4", @matches[1].attributes["id"] - # Test descendant selector. - select("div p") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "3", @matches[1].attributes["id"] - select("div span") - assert_equal 2, @matches.size - assert_equal "2", @matches[0].attributes["id"] - assert_equal "4", @matches[1].attributes["id"] - select("div *#3") - assert_equal 1, @matches.size - assert_equal "3", @matches[0].attributes["id"] - select("div *#4") - assert_equal 1, @matches.size - assert_equal "4", @matches[0].attributes["id"] - # This is here because it failed before when whitespaces - # were not properly stripped. - select("div .foo") - assert_equal 1, @matches.size - assert_equal "4", @matches[0].attributes["id"] - end - - - # - # Pseudo selectors: root, nth-child, empty, content, etc - # - - - def test_root_selector - parse(%Q{<div id="1"><div id="2"></div></div>}) - # Can only find element if it's root. - select(":root") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - select("#1:root") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - select("#2:root") - assert_equal 0, @matches.size - # Opposite for nth-child. - select("#1:nth-child(1)") - assert_equal 0, @matches.size - end - - - def test_nth_child_odd_even - parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) - # Test odd nth children. - select("tr:nth-child(odd)") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "3", @matches[1].attributes["id"] - # Test even nth children. - select("tr:nth-child(even)") - assert_equal 2, @matches.size - assert_equal "2", @matches[0].attributes["id"] - assert_equal "4", @matches[1].attributes["id"] - end - - - def test_nth_child_a_is_zero - parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) - # Test the third child. - select("tr:nth-child(0n+3)") - assert_equal 1, @matches.size - assert_equal "3", @matches[0].attributes["id"] - # Same but an can be omitted when zero. - select("tr:nth-child(3)") - assert_equal 1, @matches.size - assert_equal "3", @matches[0].attributes["id"] - # Second element (but not every second element). - select("tr:nth-child(0n+2)") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - # Before first and past last returns nothing.: - assert_raise(ArgumentError) { select("tr:nth-child(-1)") } - select("tr:nth-child(0)") - assert_equal 0, @matches.size - select("tr:nth-child(5)") - assert_equal 0, @matches.size - end - - - def test_nth_child_a_is_one - parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) - # a is group of one, pick every element in group. - select("tr:nth-child(1n+0)") - assert_equal 4, @matches.size - # Same but a can be omitted when one. - select("tr:nth-child(n+0)") - assert_equal 4, @matches.size - # Same but b can be omitted when zero. - select("tr:nth-child(n)") - assert_equal 4, @matches.size - end - - - def test_nth_child_b_is_zero - parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) - # If b is zero, pick the n-th element (here each one). - select("tr:nth-child(n+0)") - assert_equal 4, @matches.size - # If b is zero, pick the n-th element (here every second). - select("tr:nth-child(2n+0)") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "3", @matches[1].attributes["id"] - # If a and b are both zero, no element selected. - select("tr:nth-child(0n+0)") - assert_equal 0, @matches.size - select("tr:nth-child(0)") - assert_equal 0, @matches.size - end - - - def test_nth_child_a_is_negative - parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) - # Since a is -1, picks the first three elements. - select("tr:nth-child(-n+3)") - assert_equal 3, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "2", @matches[1].attributes["id"] - assert_equal "3", @matches[2].attributes["id"] - # Since a is -2, picks the first in every second of first four elements. - select("tr:nth-child(-2n+3)") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "3", @matches[1].attributes["id"] - # Since a is -2, picks the first in every second of first three elements. - select("tr:nth-child(-2n+2)") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - end - - - def test_nth_child_b_is_negative - parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) - # Select last of four. - select("tr:nth-child(4n-1)") - assert_equal 1, @matches.size - assert_equal "4", @matches[0].attributes["id"] - # Select first of four. - select("tr:nth-child(4n-4)") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - # Select last of every second. - select("tr:nth-child(2n-1)") - assert_equal 2, @matches.size - assert_equal "2", @matches[0].attributes["id"] - assert_equal "4", @matches[1].attributes["id"] - # Select nothing since an+b always < 0 - select("tr:nth-child(-1n-1)") - assert_equal 0, @matches.size - end - - - def test_nth_child_substitution_values - parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) - # Test with ?n?. - select("tr:nth-child(?n?)", 2, 1) - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "3", @matches[1].attributes["id"] - select("tr:nth-child(?n?)", 2, 2) - assert_equal 2, @matches.size - assert_equal "2", @matches[0].attributes["id"] - assert_equal "4", @matches[1].attributes["id"] - select("tr:nth-child(?n?)", 4, 2) - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - # Test with ? (b only). - select("tr:nth-child(?)", 3) - assert_equal 1, @matches.size - assert_equal "3", @matches[0].attributes["id"] - select("tr:nth-child(?)", 5) - assert_equal 0, @matches.size - end - - - def test_nth_last_child - parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) - # Last two elements. - select("tr:nth-last-child(-n+2)") - assert_equal 2, @matches.size - assert_equal "3", @matches[0].attributes["id"] - assert_equal "4", @matches[1].attributes["id"] - # All old elements counting from last one. - select("tr:nth-last-child(odd)") - assert_equal 2, @matches.size - assert_equal "2", @matches[0].attributes["id"] - assert_equal "4", @matches[1].attributes["id"] - end - - - def test_nth_of_type - parse(%Q{<table><thead></thead><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) - # First two elements. - select("tr:nth-of-type(-n+2)") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "2", @matches[1].attributes["id"] - # All old elements counting from last one. - select("tr:nth-last-of-type(odd)") - assert_equal 2, @matches.size - assert_equal "2", @matches[0].attributes["id"] - assert_equal "4", @matches[1].attributes["id"] - end - - - def test_first_and_last - parse(%Q{<table><thead></thead><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) - # First child. - select("tr:first-child") - assert_equal 0, @matches.size - select(":first-child") - assert_equal 1, @matches.size - assert_equal "thead", @matches[0].name - # First of type. - select("tr:first-of-type") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - select("thead:first-of-type") - assert_equal 1, @matches.size - assert_equal "thead", @matches[0].name - select("div:first-of-type") - assert_equal 0, @matches.size - # Last child. - select("tr:last-child") - assert_equal 1, @matches.size - assert_equal "4", @matches[0].attributes["id"] - # Last of type. - select("tr:last-of-type") - assert_equal 1, @matches.size - assert_equal "4", @matches[0].attributes["id"] - select("thead:last-of-type") - assert_equal 1, @matches.size - assert_equal "thead", @matches[0].name - select("div:last-of-type") - assert_equal 0, @matches.size - end - - - def test_only_child_and_only_type_first_and_last - # Only child. - parse(%Q{<table><tr></tr></table>}) - select("table:only-child") - assert_equal 0, @matches.size - select("tr:only-child") - assert_equal 1, @matches.size - assert_equal "tr", @matches[0].name - parse(%Q{<table><tr></tr><tr></tr></table>}) - select("tr:only-child") - assert_equal 0, @matches.size - # Only of type. - parse(%Q{<table><thead></thead><tr></tr><tr></tr></table>}) - select("thead:only-of-type") - assert_equal 1, @matches.size - assert_equal "thead", @matches[0].name - select("td:only-of-type") - assert_equal 0, @matches.size - end - - - def test_empty - parse(%Q{<table><tr></tr></table>}) - select("table:empty") - assert_equal 0, @matches.size - select("tr:empty") - assert_equal 1, @matches.size - parse(%Q{<div> </div>}) - select("div:empty") - assert_equal 1, @matches.size - end - - - def test_content - parse(%Q{<div> </div>}) - select("div:content()") - assert_equal 1, @matches.size - parse(%Q{<div>something </div>}) - select("div:content()") - assert_equal 0, @matches.size - select("div:content(something)") - assert_equal 1, @matches.size - select("div:content( 'something' )") - assert_equal 1, @matches.size - select("div:content( \"something\" )") - assert_equal 1, @matches.size - select("div:content(?)", "something") - assert_equal 1, @matches.size - select("div:content(?)", /something/) - assert_equal 1, @matches.size - end - - - # - # Test negation. - # - - - def test_element_negation - parse(%Q{<p></p><div></div>}) - select("*") - assert_equal 2, @matches.size - select("*:not(p)") - assert_equal 1, @matches.size - assert_equal "div", @matches[0].name - select("*:not(div)") - assert_equal 1, @matches.size - assert_equal "p", @matches[0].name - select("*:not(span)") - assert_equal 2, @matches.size - end - - - def test_id_negation - parse(%Q{<p id="1"></p><p id="2"></p>}) - select("p") - assert_equal 2, @matches.size - select(":not(#1)") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - select(":not(#2)") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - end - - - def test_class_name_negation - parse(%Q{<p class="foo"></p><p class="bar"></p>}) - select("p") - assert_equal 2, @matches.size - select(":not(.foo)") - assert_equal 1, @matches.size - assert_equal "bar", @matches[0].attributes["class"] - select(":not(.bar)") - assert_equal 1, @matches.size - assert_equal "foo", @matches[0].attributes["class"] - end - - - def test_attribute_negation - parse(%Q{<p title="foo"></p><p title="bar"></p>}) - select("p") - assert_equal 2, @matches.size - select(":not([title=foo])") - assert_equal 1, @matches.size - assert_equal "bar", @matches[0].attributes["title"] - select(":not([title=bar])") - assert_equal 1, @matches.size - assert_equal "foo", @matches[0].attributes["title"] - end - - - def test_pseudo_class_negation - parse(%Q{<div><p id="1"></p><p id="2"></p></div>}) - select("p") - assert_equal 2, @matches.size - select("p:not(:first-child)") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - select("p:not(:nth-child(2))") - assert_equal 1, @matches.size - assert_equal "1", @matches[0].attributes["id"] - end - - - def test_negation_details - parse(%Q{<p id="1"></p><p id="2"></p><p id="3"></p>}) - assert_raise(ArgumentError) { select(":not(") } - assert_raise(ArgumentError) { select(":not(:not())") } - select("p:not(#1):not(#3)") - assert_equal 1, @matches.size - assert_equal "2", @matches[0].attributes["id"] - end - - - def test_select_from_element - parse(%Q{<div><p id="1"></p><p id="2"></p></div>}) - select("div") - @matches = @matches[0].select("p") - assert_equal 2, @matches.size - assert_equal "1", @matches[0].attributes["id"] - assert_equal "2", @matches[1].attributes["id"] - end - - -protected - - def parse(html) - @html = HTML::Document.new(html).root - end - - def select(*selector) - @matches = HTML.selector(*selector).select(@html) - end - -end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 060c940100..1280e0d9a3 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -353,168 +353,6 @@ XML assert_equal "bar", assigns[:bar] end - def test_assert_tag_tag - process :test_html_output - - # there is a 'form' tag - assert_tag :tag => 'form' - # there is not an 'hr' tag - assert_no_tag :tag => 'hr' - end - - def test_assert_tag_attributes - process :test_html_output - - # there is a tag with an 'id' of 'bar' - assert_tag :attributes => { :id => "bar" } - # there is no tag with a 'name' of 'baz' - assert_no_tag :attributes => { :name => "baz" } - end - - def test_assert_tag_parent - process :test_html_output - - # there is a tag with a parent 'form' tag - assert_tag :parent => { :tag => "form" } - # there is no tag with a parent of 'input' - assert_no_tag :parent => { :tag => "input" } - end - - def test_assert_tag_child - process :test_html_output - - # there is a tag with a child 'input' tag - assert_tag :child => { :tag => "input" } - # there is no tag with a child 'strong' tag - assert_no_tag :child => { :tag => "strong" } - end - - def test_assert_tag_ancestor - process :test_html_output - - # there is a 'li' tag with an ancestor having an id of 'foo' - assert_tag :ancestor => { :attributes => { :id => "foo" } }, :tag => "li" - # there is no tag of any kind with an ancestor having an href matching 'foo' - assert_no_tag :ancestor => { :attributes => { :href => /foo/ } } - end - - def test_assert_tag_descendant - process :test_html_output - - # there is a tag with a descendant 'li' tag - assert_tag :descendant => { :tag => "li" } - # there is no tag with a descendant 'html' tag - assert_no_tag :descendant => { :tag => "html" } - end - - def test_assert_tag_sibling - process :test_html_output - - # there is a tag with a sibling of class 'item' - assert_tag :sibling => { :attributes => { :class => "item" } } - # there is no tag with a sibling 'ul' tag - assert_no_tag :sibling => { :tag => "ul" } - end - - def test_assert_tag_after - process :test_html_output - - # there is a tag following a sibling 'div' tag - assert_tag :after => { :tag => "div" } - # there is no tag following a sibling tag with id 'bar' - assert_no_tag :after => { :attributes => { :id => "bar" } } - end - - def test_assert_tag_before - process :test_html_output - - # there is a tag preceding a tag with id 'bar' - assert_tag :before => { :attributes => { :id => "bar" } } - # there is no tag preceding a 'form' tag - assert_no_tag :before => { :tag => "form" } - end - - def test_assert_tag_children_count - process :test_html_output - - # there is a tag with 2 children - assert_tag :children => { :count => 2 } - # in particular, there is a <ul> tag with two children (a nameless pair of <li>s) - assert_tag :tag => 'ul', :children => { :count => 2 } - # there is no tag with 4 children - assert_no_tag :children => { :count => 4 } - end - - def test_assert_tag_children_less_than - process :test_html_output - - # there is a tag with less than 5 children - assert_tag :children => { :less_than => 5 } - # there is no 'ul' tag with less than 2 children - assert_no_tag :children => { :less_than => 2 }, :tag => "ul" - end - - def test_assert_tag_children_greater_than - process :test_html_output - - # there is a 'body' tag with more than 1 children - assert_tag :children => { :greater_than => 1 }, :tag => "body" - # there is no tag with more than 10 children - assert_no_tag :children => { :greater_than => 10 } - end - - def test_assert_tag_children_only - process :test_html_output - - # there is a tag containing only one child with an id of 'foo' - assert_tag :children => { :count => 1, - :only => { :attributes => { :id => "foo" } } } - # there is no tag containing only one 'li' child - assert_no_tag :children => { :count => 1, :only => { :tag => "li" } } - end - - def test_assert_tag_content - process :test_html_output - - # the output contains the string "Name" - assert_tag :content => /Name/ - # the output does not contain the string "test" - assert_no_tag :content => /test/ - end - - def test_assert_tag_multiple - process :test_html_output - - # there is a 'div', id='bar', with an immediate child whose 'action' - # attribute matches the regexp /somewhere/. - assert_tag :tag => "div", :attributes => { :id => "bar" }, - :child => { :attributes => { :action => /somewhere/ } } - - # there is no 'div', id='foo', with a 'ul' child with more than - # 2 "li" children. - assert_no_tag :tag => "div", :attributes => { :id => "foo" }, - :child => { - :tag => "ul", - :children => { :greater_than => 2, - :only => { :tag => "li" } } } - end - - def test_assert_tag_children_without_content - process :test_html_output - - # there is a form tag with an 'input' child which is a self closing tag - assert_tag :tag => "form", - :children => { :count => 1, - :only => { :tag => "input" } } - - # the body tag has an 'a' child which in turn has an 'img' child - assert_tag :tag => "body", - :children => { :count => 1, - :only => { :tag => "a", - :children => { :count => 1, - :only => { :tag => "img" } } } } - end - def test_should_not_impose_childless_html_tags_in_xml process :test_xml_output @@ -529,23 +367,6 @@ XML assert err.empty? end - def test_assert_tag_attribute_matching - @response.body = '<input type="text" name="my_name">' - assert_tag :tag => 'input', - :attributes => { :name => /my/, :type => 'text' } - assert_no_tag :tag => 'input', - :attributes => { :name => 'my', :type => 'text' } - assert_no_tag :tag => 'input', - :attributes => { :name => /^my$/, :type => 'text' } - end - - def test_assert_tag_content_matching - @response.body = "<p>hello world</p>" - assert_tag :tag => "p", :content => "hello world" - assert_tag :tag => "p", :content => /hello/ - assert_no_tag :tag => "p", :content => "hello" - end - def test_assert_generates assert_generates 'controller/action/5', :controller => 'controller', :action => 'action', :id => '5' assert_generates 'controller/action/7', {:id => "7"}, {:controller => "controller", :action => "action"} diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 9f086af664..969129d9ba 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -231,8 +231,8 @@ module AbstractController def test_trailing_slash_with_params url = W.new.url_for(:trailing_slash => true, :only_path => true, :controller => 'cont', :action => 'act', :p1 => 'cafe', :p2 => 'link') params = extract_params(url) - assert_equal params[0], { :p1 => 'cafe' }.to_query - assert_equal params[1], { :p2 => 'link' }.to_query + assert_equal({p1: 'cafe'}.to_query, params[0]) + assert_equal({p2: 'link'}.to_query, params[1]) end def test_relative_url_root_is_respected @@ -305,40 +305,40 @@ module AbstractController def test_two_parameters url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2') params = extract_params(url) - assert_equal params[0], { :p1 => 'X1' }.to_query - assert_equal params[1], { :p2 => 'Y2' }.to_query + assert_equal({p1: 'X1'}.to_query, params[0]) + assert_equal({p2: 'Y2'}.to_query, params[1]) end def test_hash_parameter url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'}) params = extract_params(url) - assert_equal params[0], { 'query[category]' => 'prof' }.to_query - assert_equal params[1], { 'query[name]' => 'Bob' }.to_query + assert_equal({'query[category]' => 'prof'}.to_query, params[0]) + assert_equal({'query[name]' => 'Bob'}.to_query, params[1]) end def test_array_parameter url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof']) params = extract_params(url) - assert_equal params[0], { 'query[]' => 'Bob' }.to_query - assert_equal params[1], { 'query[]' => 'prof' }.to_query + assert_equal({'query[]' => 'Bob'}.to_query, params[0]) + assert_equal({'query[]' => 'prof'}.to_query, params[1]) end def test_hash_recursive_parameters url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'}) params = extract_params(url) - assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query - assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query - assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query + assert_equal({'query[hobby]' => 'piercing'}.to_query, params[0]) + assert_equal({'query[person][name]' => 'Bob' }.to_query, params[1]) + assert_equal({'query[person][position]' => 'prof' }.to_query, params[2]) end def test_hash_recursive_and_array_parameters url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'}) assert_match(%r(^/c/a/101), url) params = extract_params(url) - assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query - assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query - assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query - assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query + assert_equal({'query[hobby]' => 'piercing' }.to_query, params[0]) + assert_equal({'query[person][name]' => 'Bob' }.to_query, params[1]) + assert_equal({'query[person][position][]' => 'art director'}.to_query, params[2]) + assert_equal({'query[person][position][]' => 'prof' }.to_query, params[3]) end def test_path_generation_for_symbol_parameter_keys diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 0f145666d1..f62e194ca9 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -21,6 +21,16 @@ class CookiesTest < ActionController::TestCase end end + class JSONWrapper + def initialize(obj) + @obj = obj + end + + def as_json(options = nil) + "wrapped: #{@obj.as_json(options)}" + end + end + class TestController < ActionController::Base def authenticate cookies["user_name"] = "david" @@ -85,6 +95,11 @@ class CookiesTest < ActionController::TestCase head :ok end + def set_wrapped_signed_cookie + cookies.signed[:user_id] = JSONWrapper.new(45) + head :ok + end + def get_signed_cookie cookies.signed[:user_id] head :ok @@ -95,6 +110,11 @@ class CookiesTest < ActionController::TestCase head :ok end + def set_wrapped_encrypted_cookie + cookies.encrypted[:foo] = JSONWrapper.new('bar') + head :ok + end + def get_encrypted_cookie cookies.encrypted[:foo] head :ok @@ -263,7 +283,7 @@ class CookiesTest < ActionController::TestCase def test_setting_the_same_value_to_permanent_cookie request.cookies[:user_name] = 'Jamie' get :set_permanent_cookie - assert_equal response.cookies, 'user_name' => 'Jamie' + assert_equal({'user_name' => 'Jamie'}, response.cookies) end def test_setting_with_escapable_characters @@ -369,6 +389,35 @@ class CookiesTest < ActionController::TestCase assert_equal 'Jamie', @controller.send(:cookies).permanent[:user_name] end + def test_signed_cookie_using_default_digest + get :set_signed_cookie + cookies = @controller.send :cookies + assert_not_equal 45, cookies[:user_id] + assert_equal 45, cookies.signed[:user_id] + + key_generator = @request.env["action_dispatch.key_generator"] + signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] + secret = key_generator.generate_key(signed_cookie_salt) + + verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: 'SHA1') + assert_equal verifier.generate(45), cookies[:user_id] + end + + def test_signed_cookie_using_custom_digest + @request.env["action_dispatch.cookies_digest"] = 'SHA256' + get :set_signed_cookie + cookies = @controller.send :cookies + assert_not_equal 45, cookies[:user_id] + assert_equal 45, cookies.signed[:user_id] + + key_generator = @request.env["action_dispatch.key_generator"] + signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] + secret = key_generator.generate_key(signed_cookie_salt) + + verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: 'SHA256') + assert_equal verifier.generate(45), cookies[:user_id] + end + def test_signed_cookie_using_default_serializer get :set_signed_cookie cookies = @controller.send :cookies @@ -392,6 +441,14 @@ class CookiesTest < ActionController::TestCase assert_equal 45, cookies.signed[:user_id] end + def test_wrapped_signed_cookie_using_json_serializer + @request.env["action_dispatch.cookies_serializer"] = :json + get :set_wrapped_signed_cookie + cookies = @controller.send :cookies + assert_not_equal 'wrapped: 45', cookies[:user_id] + assert_equal 'wrapped: 45', cookies.signed[:user_id] + end + def test_signed_cookie_using_custom_serializer @request.env["action_dispatch.cookies_serializer"] = CustomSerializer get :set_signed_cookie @@ -474,6 +531,17 @@ class CookiesTest < ActionController::TestCase assert_equal 'bar', cookies.encrypted[:foo] end + def test_wrapped_encrypted_cookie_using_json_serializer + @request.env["action_dispatch.cookies_serializer"] = :json + get :set_wrapped_encrypted_cookie + cookies = @controller.send :cookies + assert_not_equal 'wrapped: bar', cookies[:foo] + assert_raises ::JSON::ParserError do + cookies.signed[:foo] + end + assert_equal 'wrapped: bar', cookies.encrypted[:foo] + end + def test_encrypted_cookie_using_custom_serializer @request.env["action_dispatch.cookies_serializer"] = CustomSerializer get :set_encrypted_cookie @@ -481,6 +549,27 @@ class CookiesTest < ActionController::TestCase assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] end + def test_encrypted_cookie_using_custom_digest + @request.env["action_dispatch.cookies_digest"] = 'SHA256' + get :set_encrypted_cookie + cookies = @controller.send :cookies + assert_not_equal 'bar', cookies[:foo] + assert_equal 'bar', cookies.encrypted[:foo] + + sign_secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) + + sha1_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: 'SHA1') + sha256_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: 'SHA256') + + assert_raises(ActiveSupport::MessageVerifier::InvalidSignature) do + sha1_verifier.verify(cookies[:foo]) + end + + assert_nothing_raised do + sha256_verifier.verify(cookies[:foo]) + end + end + def test_encrypted_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json @request.env["action_dispatch.cookies_serializer"] = :hybrid diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 24526fb00e..0add9fa3b0 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -151,37 +151,37 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_response 500 assert_no_match(/<header>/, body) assert_no_match(/<body>/, body) - assert_equal response.content_type, "text/plain" + assert_equal "text/plain", response.content_type assert_match(/RuntimeError\npuke/, body) get "/not_found", {}, xhr_request_env assert_response 404 assert_no_match(/<body>/, body) - assert_equal response.content_type, "text/plain" + assert_equal "text/plain", response.content_type assert_match(/#{AbstractController::ActionNotFound.name}/, body) get "/method_not_allowed", {}, xhr_request_env assert_response 405 assert_no_match(/<body>/, body) - assert_equal response.content_type, "text/plain" + assert_equal "text/plain", response.content_type assert_match(/ActionController::MethodNotAllowed/, body) get "/unknown_http_method", {}, xhr_request_env assert_response 405 assert_no_match(/<body>/, body) - assert_equal response.content_type, "text/plain" + assert_equal "text/plain", response.content_type assert_match(/ActionController::UnknownHttpMethod/, body) get "/bad_request", {}, xhr_request_env assert_response 400 assert_no_match(/<body>/, body) - assert_equal response.content_type, "text/plain" + assert_equal "text/plain", response.content_type assert_match(/ActionController::BadRequest/, body) get "/parameter_missing", {}, xhr_request_env assert_response 400 assert_no_match(/<body>/, body) - assert_equal response.content_type, "text/plain" + assert_equal "text/plain", response.content_type assert_match(/ActionController::ParameterMissing/, body) end diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb index 3e554a9cf6..889f9a4736 100644 --- a/actionpack/test/dispatch/mapper_test.rb +++ b/actionpack/test/dispatch/mapper_test.rb @@ -38,7 +38,7 @@ module ActionDispatch def test_mapping_requirements options = { :controller => 'foo', :action => 'bar', :via => :get } - m = Mapper::Mapping.build({}, FakeSet.new, '/store/:name(*rest)', options) + m = Mapper::Mapping.build({}, FakeSet.new, '/store/:name(*rest)', nil, options) _, _, requirements, _ = m.to_route assert_equal(/.+?/, requirements[:rest]) end diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index d29cc8473e..7f340ced41 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -124,7 +124,7 @@ class MimeTypeTest < ActiveSupport::TestCase end Mime::Type.register("text/foo", :foo) - assert_equal registered_mimes, [Mime::FOO] + assert_equal [Mime::FOO], registered_mimes ensure Mime::Type.unregister(:FOO) end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index fe9ee6f73d..84bd392fd9 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -909,6 +909,31 @@ class RequestParameters < BaseRequestTest end end + test "parameters not accessible after rack parse error of invalid UTF8 character" do + request = stub_request("QUERY_STRING" => "foo%81E=1") + + 2.times do + assert_raises(ActionController::BadRequest) do + # rack will raise a Rack::Utils::InvalidParameterError when parsing this query string + request.parameters + end + end + end + + test "parameters not accessible after rack parse error 1" do + request = stub_request( + 'REQUEST_METHOD' => 'POST', + 'CONTENT_LENGTH' => "a%=".length, + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'rack.input' => StringIO.new("a%=") + ) + + assert_raises(ActionController::BadRequest) do + # rack will raise a TypeError when parsing this query string + request.parameters + end + end + test "we have access to the original exception" do request = stub_request("QUERY_STRING" => "x[y]=1&x[y][][w]=2") diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index e99ff46edf..c5cd24d06e 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -86,7 +86,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest cookies[SessionKey] = SignedBar get '/persistent_session_id' assert_response :success - assert_equal response.body.size, 32 + assert_equal 32, response.body.size session_id = response.body get '/get_session_id' @@ -247,7 +247,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest cookies[SessionKey] = SignedBar get '/persistent_session_id' assert_response :success - assert_equal response.body.size, 32 + assert_equal 32, response.body.size session_id = response.body get '/persistent_session_id' assert_equal session_id, response.body @@ -263,7 +263,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest get "/get_session_id" sid = response.body - assert_equal sid.size, 36 + assert_equal 36, sid.size get "/change_session_id" assert_not_equal sid, response.body diff --git a/actionpack/test/fixtures/respond_with/edit.html.erb b/actionpack/test/fixtures/respond_with/edit.html.erb deleted file mode 100644 index ae82dfa4fc..0000000000 --- a/actionpack/test/fixtures/respond_with/edit.html.erb +++ /dev/null @@ -1 +0,0 @@ -Edit world! diff --git a/actionpack/test/fixtures/respond_with/new.html.erb b/actionpack/test/fixtures/respond_with/new.html.erb deleted file mode 100644 index 96c8f1b88b..0000000000 --- a/actionpack/test/fixtures/respond_with/new.html.erb +++ /dev/null @@ -1 +0,0 @@ -New world! diff --git a/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb b/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb deleted file mode 100644 index e69de29bb2..0000000000 --- a/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb +++ /dev/null diff --git a/actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb b/actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb deleted file mode 100644 index bf5869ed22..0000000000 --- a/actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb +++ /dev/null @@ -1 +0,0 @@ -<content>I should not be displayed</content>
\ No newline at end of file diff --git a/actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb b/actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb deleted file mode 100644 index b313017913..0000000000 --- a/actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb +++ /dev/null @@ -1 +0,0 @@ -<customer-name><%= @customer.name %></customer-name>
\ No newline at end of file diff --git a/actionpack/test/fixtures/respond_with/using_resource.js.erb b/actionpack/test/fixtures/respond_with/using_resource.js.erb deleted file mode 100644 index 4417680bce..0000000000 --- a/actionpack/test/fixtures/respond_with/using_resource.js.erb +++ /dev/null @@ -1 +0,0 @@ -alert("Hi");
\ No newline at end of file diff --git a/actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb b/actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb deleted file mode 100644 index 6769dd60bd..0000000000 --- a/actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb +++ /dev/null @@ -1 +0,0 @@ -Hello world!
\ No newline at end of file |