2014-04-16 08:58:05 +00:00
|
|
|
require 'delegate'
|
|
|
|
|
2014-01-24 21:14:49 +00:00
|
|
|
begin
|
|
|
|
require 'strong_parameters'
|
|
|
|
rescue LoadError
|
|
|
|
end
|
|
|
|
|
2014-04-16 08:58:05 +00:00
|
|
|
require 'active_support/hash_with_indifferent_access'
|
|
|
|
|
2014-01-24 21:14:49 +00:00
|
|
|
module Shoulda
|
|
|
|
module Matchers
|
|
|
|
module ActionController
|
2014-01-23 18:07:36 +00:00
|
|
|
# The `permit` matcher tests that an action in your controller receives a
|
|
|
|
# whitelist of parameters using Rails' Strong Parameters feature
|
|
|
|
# (specifically that `permit` was called with the correct arguments).
|
|
|
|
#
|
|
|
|
# Here's an example:
|
|
|
|
#
|
|
|
|
# class UsersController < ApplicationController
|
|
|
|
# def create
|
|
|
|
# user = User.create(user_params)
|
|
|
|
# # ...
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# private
|
|
|
|
#
|
|
|
|
# def user_params
|
|
|
|
# params.require(:user).permit(
|
|
|
|
# :first_name,
|
|
|
|
# :last_name,
|
|
|
|
# :email,
|
|
|
|
# :password
|
|
|
|
# )
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe UsersController do
|
|
|
|
# it do
|
|
|
|
# should permit(:first_name, :last_name, :email, :password).
|
|
|
|
# for(:create)
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # Test::Unit
|
|
|
|
# class UsersControllerTest < ActionController::TestCase
|
|
|
|
# should permit(:first_name, :last_name, :email, :password).
|
|
|
|
# for(:create)
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# If your action requires query parameters in order to work, then you'll
|
|
|
|
# need to supply them:
|
|
|
|
#
|
|
|
|
# class UsersController < ApplicationController
|
|
|
|
# def update
|
|
|
|
# user = User.find(params[:id])
|
|
|
|
#
|
|
|
|
# if user.update_attributes(user_params)
|
|
|
|
# # ...
|
|
|
|
# else
|
|
|
|
# # ...
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# private
|
|
|
|
#
|
|
|
|
# def user_params
|
|
|
|
# params.require(:user).permit(
|
|
|
|
# :first_name,
|
|
|
|
# :last_name,
|
|
|
|
# :email,
|
|
|
|
# :password
|
|
|
|
# )
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe UsersController do
|
|
|
|
# before do
|
|
|
|
# create(:user, id: 1)
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# it do
|
|
|
|
# should permit(:first_name, :last_name, :email, :password).
|
|
|
|
# for(:update, params: { id: 1 })
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # Test::Unit
|
|
|
|
# class UsersControllerTest < ActionController::TestCase
|
|
|
|
# setup do
|
|
|
|
# create(:user, id: 1)
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# should permit(:first_name, :last_name, :email, :password).
|
|
|
|
# for(:update, params: { id: 1 })
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# Finally, if you have an action that isn't one of the seven resourceful
|
|
|
|
# actions, then you'll need to provide the HTTP verb that it responds to:
|
|
|
|
#
|
|
|
|
# Rails.application.routes.draw do
|
|
|
|
# resources :users do
|
|
|
|
# member do
|
|
|
|
# put :toggle
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# class UsersController < ApplicationController
|
|
|
|
# def toggle
|
|
|
|
# user = User.find(params[:id])
|
|
|
|
#
|
|
|
|
# if user.update_attributes(user_params)
|
|
|
|
# # ...
|
|
|
|
# else
|
|
|
|
# # ...
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# private
|
|
|
|
#
|
|
|
|
# def user_params
|
|
|
|
# params.require(:user).permit(:activated)
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe UsersController do
|
|
|
|
# before do
|
|
|
|
# create(:user, id: 1)
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# it do
|
|
|
|
# should permit(:activated).for(:toggle,
|
|
|
|
# params: { id: 1 },
|
|
|
|
# verb: :put
|
|
|
|
# )
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # Test::Unit
|
|
|
|
# class UsersControllerTest < ActionController::TestCase
|
|
|
|
# setup do
|
|
|
|
# create(:user, id: 1)
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# should permit(:activated).for(:toggle,
|
|
|
|
# params: { id: 1 },
|
|
|
|
# verb: :put
|
|
|
|
# )
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# @return [StrongParametersMatcher]
|
|
|
|
#
|
2014-04-20 02:48:43 +00:00
|
|
|
def permit(*params)
|
|
|
|
StrongParametersMatcher.new(params).in_context(self)
|
2014-01-24 21:14:49 +00:00
|
|
|
end
|
|
|
|
|
2014-01-23 18:07:36 +00:00
|
|
|
# @private
|
2014-01-24 21:14:49 +00:00
|
|
|
class StrongParametersMatcher
|
2014-04-16 08:58:05 +00:00
|
|
|
attr_writer :stubbed_params
|
2014-01-24 21:17:46 +00:00
|
|
|
|
2014-04-20 02:48:43 +00:00
|
|
|
def initialize(expected_permitted_params)
|
|
|
|
@action = nil
|
|
|
|
@verb = nil
|
|
|
|
@request_params = {}
|
|
|
|
@expected_permitted_params = expected_permitted_params
|
|
|
|
set_double_collection
|
2014-01-24 21:14:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def for(action, options = {})
|
|
|
|
@action = action
|
2014-04-20 02:48:43 +00:00
|
|
|
@verb = options.fetch(:verb, default_verb)
|
|
|
|
@request_params = options.fetch(:params, {})
|
2014-01-24 21:14:49 +00:00
|
|
|
self
|
|
|
|
end
|
|
|
|
|
|
|
|
def in_context(context)
|
|
|
|
@context = context
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2014-04-04 18:03:22 +00:00
|
|
|
def description
|
2014-04-20 02:48:43 +00:00
|
|
|
"permit #{verb.upcase} ##{action} to receive parameters #{param_names_as_sentence}"
|
2014-04-04 18:03:22 +00:00
|
|
|
end
|
|
|
|
|
2014-04-16 08:58:05 +00:00
|
|
|
def matches?(controller)
|
|
|
|
@controller = controller
|
2014-04-20 02:48:43 +00:00
|
|
|
ensure_action_and_verb_present!
|
2014-01-24 21:14:49 +00:00
|
|
|
|
2014-04-20 02:48:43 +00:00
|
|
|
Doublespeak.with_doubles_activated do
|
|
|
|
context.__send__(verb, action, request_params)
|
|
|
|
end
|
|
|
|
|
|
|
|
unpermitted_params.empty?
|
2014-01-24 21:14:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def failure_message
|
2014-04-20 02:48:43 +00:00
|
|
|
"Expected controller to permit #{unpermitted_params.to_sentence}, but it did not."
|
2014-01-24 21:14:49 +00:00
|
|
|
end
|
2014-01-24 21:17:46 +00:00
|
|
|
alias failure_message_for_should failure_message
|
2014-01-24 21:14:49 +00:00
|
|
|
|
2014-01-24 21:17:46 +00:00
|
|
|
def failure_message_when_negated
|
2014-04-20 02:48:43 +00:00
|
|
|
"Expected controller not to permit #{verified_permitted_params.to_sentence}, but it did."
|
2014-01-24 21:14:49 +00:00
|
|
|
end
|
2014-01-24 21:17:46 +00:00
|
|
|
alias failure_message_for_should_not failure_message_when_negated
|
2014-01-24 21:14:49 +00:00
|
|
|
|
2014-06-24 15:57:52 +00:00
|
|
|
protected
|
2014-01-24 21:17:46 +00:00
|
|
|
|
2014-04-20 02:48:43 +00:00
|
|
|
attr_reader :controller, :double_collection, :action, :verb,
|
|
|
|
:request_params, :expected_permitted_params, :context
|
2014-01-24 21:14:49 +00:00
|
|
|
|
2014-04-20 02:48:43 +00:00
|
|
|
def set_double_collection
|
|
|
|
@double_collection =
|
2014-06-12 22:12:54 +00:00
|
|
|
Doublespeak.double_collection_for(::ActionController::Parameters)
|
2014-01-24 21:14:49 +00:00
|
|
|
|
2014-04-20 02:48:43 +00:00
|
|
|
@double_collection.register_stub(:require).to_return { |params| params }
|
|
|
|
@double_collection.register_proxy(:permit)
|
2014-01-24 21:14:49 +00:00
|
|
|
end
|
|
|
|
|
2014-04-20 02:48:43 +00:00
|
|
|
def actual_permitted_params
|
|
|
|
double_collection.calls_to(:permit).inject([]) do |all_param_names, call|
|
|
|
|
all_param_names + call.args
|
|
|
|
end.flatten
|
2014-01-24 21:14:49 +00:00
|
|
|
end
|
|
|
|
|
2014-04-20 02:48:43 +00:00
|
|
|
def permit_called?
|
|
|
|
actual_permitted_params.any?
|
2014-01-24 21:14:49 +00:00
|
|
|
end
|
|
|
|
|
2014-04-20 02:48:43 +00:00
|
|
|
def unpermitted_params
|
|
|
|
expected_permitted_params - actual_permitted_params
|
2014-01-24 21:17:46 +00:00
|
|
|
end
|
2014-01-24 21:14:49 +00:00
|
|
|
|
2014-04-20 02:48:43 +00:00
|
|
|
def verified_permitted_params
|
|
|
|
expected_permitted_params & actual_permitted_params
|
2014-04-06 15:18:12 +00:00
|
|
|
end
|
|
|
|
|
2014-01-24 21:14:49 +00:00
|
|
|
def ensure_action_and_verb_present!
|
|
|
|
if action.blank?
|
|
|
|
raise ActionNotDefinedError
|
|
|
|
end
|
2014-04-20 02:48:43 +00:00
|
|
|
|
2014-01-24 21:14:49 +00:00
|
|
|
if verb.blank?
|
|
|
|
raise VerbNotDefinedError
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-04-20 02:48:43 +00:00
|
|
|
def default_verb
|
|
|
|
case action
|
|
|
|
when :create then :post
|
|
|
|
when :update then RailsShim.verb_for_update
|
2014-04-16 08:58:05 +00:00
|
|
|
end
|
2014-01-24 21:17:46 +00:00
|
|
|
end
|
|
|
|
|
2014-04-20 02:48:43 +00:00
|
|
|
def param_names_as_sentence
|
|
|
|
expected_permitted_params.map(&:inspect).to_sentence
|
2014-01-24 21:17:46 +00:00
|
|
|
end
|
|
|
|
|
2014-01-23 18:07:36 +00:00
|
|
|
# @private
|
2014-04-16 08:58:05 +00:00
|
|
|
class ActionNotDefinedError < StandardError
|
|
|
|
def message
|
|
|
|
'You must specify the controller action using the #for method.'
|
|
|
|
end
|
2014-01-24 21:14:49 +00:00
|
|
|
end
|
|
|
|
|
2014-01-23 18:07:36 +00:00
|
|
|
# @private
|
2014-04-16 08:58:05 +00:00
|
|
|
class VerbNotDefinedError < StandardError
|
|
|
|
def message
|
2014-04-20 02:48:43 +00:00
|
|
|
'You must specify an HTTP verb when using a non-RESTful action. For example: for(:authorize, verb: :post)'
|
2014-04-16 08:58:05 +00:00
|
|
|
end
|
2014-01-24 21:14:49 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|