613 lines
17 KiB
Ruby
613 lines
17 KiB
Ruby
require 'unit_spec_helper'
|
|
|
|
describe Shoulda::Matchers::ActionController::PermitMatcher, type: :controller do
|
|
shared_examples 'basic tests' do
|
|
it 'accepts a subset of the permitted attributes' do
|
|
define_controller_with_strong_parameters(action: :create) do |ctrl|
|
|
params_with_conditional_require(ctrl.params).permit(:name, :age)
|
|
end
|
|
|
|
expect(controller).to permit_with_conditional_slice_of_params(
|
|
permit(:name).for(:create),
|
|
)
|
|
end
|
|
|
|
it 'accepts all of the permitted attributes' do
|
|
define_controller_with_strong_parameters(action: :create) do |ctrl|
|
|
params_with_conditional_require(ctrl.params).permit(:name, :age)
|
|
end
|
|
|
|
expect(controller).to permit_with_conditional_slice_of_params(
|
|
permit(:name, :age).for(:create),
|
|
)
|
|
end
|
|
|
|
it 'rejects attributes that have not been permitted' do
|
|
define_controller_with_strong_parameters(action: :create) do |ctrl|
|
|
params_with_conditional_require(ctrl.params).permit(:name)
|
|
end
|
|
|
|
expect(controller).not_to permit_with_conditional_slice_of_params(
|
|
permit(:name, :admin).for(:create),
|
|
)
|
|
end
|
|
|
|
it 'rejects when #permit has not been called' do
|
|
define_controller_with_strong_parameters(action: :create)
|
|
|
|
expect(controller).not_to permit_with_conditional_slice_of_params(
|
|
permit(:name).for(:create),
|
|
)
|
|
end
|
|
|
|
it 'tracks multiple calls to #permit for different subparameters' do
|
|
sets_of_attributes = [
|
|
[:eta, :diner_id],
|
|
[:phone_number, :address_1, :address_2, :city, :state, :zip],
|
|
]
|
|
|
|
define_controller_with_strong_parameters(action: :create) do |ctrl|
|
|
params_with_conditional_require(ctrl.params, :order).
|
|
permit(sets_of_attributes[0])
|
|
|
|
params_with_conditional_require(ctrl.params, :diner).
|
|
permit(sets_of_attributes[1])
|
|
end
|
|
|
|
expect(controller).to permit_with_conditional_slice_of_params(
|
|
permit(*sets_of_attributes[0]).for(:create),
|
|
all_params: [:order, :diner],
|
|
selected_param: :order,
|
|
)
|
|
|
|
expect(controller).to permit_with_conditional_slice_of_params(
|
|
permit(*sets_of_attributes[1]).for(:create),
|
|
all_params: [:order, :diner],
|
|
selected_param: :diner,
|
|
)
|
|
end
|
|
end
|
|
|
|
it 'requires an action' do
|
|
assertion = -> { expect(controller).to permit(:name) }
|
|
|
|
define_controller_with_strong_parameters
|
|
|
|
expect(&assertion).to raise_error(described_class::ActionNotDefinedError)
|
|
end
|
|
|
|
it 'requires a verb for a non-restful action' do
|
|
define_controller_with_strong_parameters
|
|
|
|
assertion = lambda do
|
|
expect(controller).to permit(:name).for(:authorize)
|
|
end
|
|
|
|
expect(&assertion).to raise_error(described_class::VerbNotDefinedError)
|
|
end
|
|
|
|
context 'when operating on the entire params hash' do
|
|
include_context 'basic tests' do
|
|
def permit_with_conditional_slice_of_params(permit, _options = {})
|
|
permit
|
|
end
|
|
|
|
def params_with_conditional_require(params, *_filters)
|
|
params
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when operating on a slice of the params hash' do
|
|
include_context 'basic tests' do
|
|
def permit_with_conditional_slice_of_params(
|
|
permit,
|
|
all_params: [:user],
|
|
selected_param: :user
|
|
)
|
|
params = all_params.inject({}) do |hash, param|
|
|
hash.merge(param => { any: 'value' })
|
|
end
|
|
|
|
permit.add_params(params).on(selected_param)
|
|
end
|
|
|
|
def params_with_conditional_require(params, *filters)
|
|
if filters.none?
|
|
filters = [:user]
|
|
end
|
|
|
|
params.require(*filters)
|
|
end
|
|
end
|
|
|
|
it 'rejects if asserting that parameters were not permitted, but on the wrong slice' do
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params.require(:order).permit(:eta, :diner_id)
|
|
end
|
|
|
|
expect(controller).
|
|
not_to permit(:eta, :diner_id).
|
|
for(:create, params: { order: { some: 'value' } }).
|
|
on(:something_else)
|
|
end
|
|
|
|
it 'tracks multiple calls to #permit for the same subparameter' do
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params.require(:foo).permit(:bar)
|
|
params.require(:foo).permit(:baz)
|
|
end
|
|
|
|
params = {
|
|
foo: {
|
|
bar: 'some value',
|
|
baz: 'some value',
|
|
},
|
|
}
|
|
expect(controller).
|
|
to permit(:bar).
|
|
on(:foo).
|
|
for(:create, params: params)
|
|
expect(controller).
|
|
to permit(:baz).
|
|
on(:foo).
|
|
for(:create, params: params)
|
|
end
|
|
end
|
|
|
|
it 'can be used more than once in the same test' do
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params.permit(:name)
|
|
end
|
|
|
|
expect(controller).to permit(:name).for(:create)
|
|
expect(controller).not_to permit(:admin).for(:create)
|
|
end
|
|
|
|
it 'allows extra parameters to be provided if the route requires them' do
|
|
options = {
|
|
controller_name: 'Posts',
|
|
action: :show,
|
|
routes: -> { get '/posts/:slug', to: 'posts#show' },
|
|
}
|
|
|
|
define_controller_with_strong_parameters(options) do
|
|
params.permit(:name)
|
|
end
|
|
|
|
expect(controller).
|
|
to permit(:name).
|
|
for(:show, verb: :get, params: { slug: 'foo' })
|
|
end
|
|
|
|
it 'works with #update specifically' do
|
|
define_controller_with_strong_parameters(action: :update) do
|
|
params.permit(:name)
|
|
end
|
|
|
|
expect(controller).
|
|
to permit(:name).
|
|
for(:update, params: { id: 1 })
|
|
end
|
|
|
|
it 'works when multiple ActionController::Parameters were instantiated' do
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params.permit(:name)
|
|
params.dup
|
|
end
|
|
|
|
expect(controller).to permit(:name).for(:create)
|
|
end
|
|
|
|
describe '#matches?' do
|
|
it 'does not raise an error when #fetch was used instead of #require (issue #495)' do
|
|
matcher = permit(:eta, :diner_id).for(:create)
|
|
matching = -> { matcher.matches?(controller) }
|
|
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params.fetch(:order, {}).permit(:eta, :diner_id)
|
|
end
|
|
|
|
expect(&matching).not_to raise_error
|
|
end
|
|
|
|
context 'stubbing params on the controller' do
|
|
it 'still allows the original params hash to be modified and accessed prior to the call to #require' do
|
|
actual_user_params = nil
|
|
actual_foo_param = nil
|
|
matcher = permit(:name).for(
|
|
:create,
|
|
params: { user: { some: 'params' } },
|
|
)
|
|
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params[:foo] = 'bar'
|
|
actual_foo_param = params[:foo]
|
|
actual_user_params = params[:user]
|
|
|
|
params.permit(:name)
|
|
end
|
|
|
|
matcher.matches?(controller)
|
|
|
|
expect(actual_user_params).to eq('some' => 'params')
|
|
expect(actual_foo_param).to eq 'bar'
|
|
end
|
|
|
|
it 'still allows #require to return a slice of the params' do
|
|
expected_user_params = { 'foo' => 'bar' }
|
|
actual_user_params = nil
|
|
matcher = permit(:name).for(
|
|
:update,
|
|
params: { id: 1, user: expected_user_params },
|
|
)
|
|
|
|
define_controller_with_strong_parameters(action: :update) do
|
|
actual_user_params = params.require(:user)
|
|
begin
|
|
actual_user_params.permit(:name)
|
|
rescue StandardError
|
|
end
|
|
end
|
|
|
|
matcher.matches?(controller)
|
|
|
|
expect(actual_user_params).to eq expected_user_params
|
|
end
|
|
|
|
it 'does not permanently stub the params hash' do
|
|
matcher = permit(:name).for(:create)
|
|
params_access = -> { controller.params.require(:user) }
|
|
|
|
define_controller_with_strong_parameters(action: :create)
|
|
|
|
matcher.matches?(controller)
|
|
|
|
expect(¶ms_access).
|
|
to raise_error(::ActionController::ParameterMissing)
|
|
end
|
|
|
|
it 'prevents permanently stubbing params on error' do
|
|
matcher = permit(:name).for(:create)
|
|
params_access = -> { controller.params.require(:user) }
|
|
|
|
define_controller_raising_exception
|
|
|
|
begin
|
|
matcher.matches?(controller)
|
|
rescue simulated_error_class
|
|
end
|
|
|
|
expect(¶ms_access).
|
|
to raise_error(::ActionController::ParameterMissing)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#description' do
|
|
it 'returns the correct string' do
|
|
options = { action: :create, method: :post }
|
|
|
|
define_controller_with_strong_parameters(options) do
|
|
params.permit(:name, :age)
|
|
end
|
|
|
|
matcher = described_class.new([:name, :age, :height]).for(:create)
|
|
expect(matcher.description).to eq(
|
|
'(for POST #create) restrict parameters to :name, :age, and :height',
|
|
)
|
|
end
|
|
|
|
context 'when a verb is specified' do
|
|
it 'returns the correct string' do
|
|
options = { action: :some_action }
|
|
|
|
define_controller_with_strong_parameters(options) do
|
|
params.permit(:name, :age)
|
|
end
|
|
|
|
matcher = described_class.
|
|
new([:name]).
|
|
for(:some_action, verb: :put)
|
|
expect(matcher.description).to eq(
|
|
'(for PUT #some_action) restrict parameters to :name',
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'positive failure message' do
|
|
context 'when no parameters were permitted' do
|
|
it 'returns the correct message' do
|
|
define_controller_with_strong_parameters(action: :create)
|
|
|
|
assertion = lambda do
|
|
expect(@controller).
|
|
to permit(:name, :age, :city, :country).
|
|
for(:create)
|
|
end
|
|
|
|
message =
|
|
'Expected POST #create to restrict parameters to '\
|
|
":name, :age, :city, and :country,\n"\
|
|
'but it did not restrict any parameters.'
|
|
|
|
expect(&assertion).to fail_with_message(message)
|
|
end
|
|
end
|
|
|
|
context 'when some, but not all, parameters were permitted' do
|
|
it 'returns the correct message, including missing attributes' do
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params.permit(:name, :age)
|
|
end
|
|
|
|
assertion = lambda do
|
|
expect(@controller).
|
|
to permit(:name, :age, :city, :country).
|
|
for(:create)
|
|
end
|
|
|
|
message =
|
|
'Expected POST #create to restrict parameters to '\
|
|
":name, :age, :city, and :country,\n"\
|
|
'but the restricted parameters were :name and :age instead.'
|
|
|
|
expect(&assertion).to fail_with_message(message)
|
|
end
|
|
end
|
|
|
|
context 'qualified with #on' do
|
|
context 'when the subparameter was never required' do
|
|
it 'returns the correct message' do
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params.permit(:name, :age)
|
|
end
|
|
|
|
assertion = lambda do
|
|
expect(@controller).
|
|
to permit(:name, :age, :city, :country).
|
|
for(:create).
|
|
on(:person)
|
|
end
|
|
|
|
message =
|
|
'Expected POST #create to restrict parameters on :person to '\
|
|
":name, :age, :city, and :country,\n"\
|
|
'but it did not restrict any parameters.'
|
|
|
|
expect(&assertion).to fail_with_message(message)
|
|
end
|
|
end
|
|
|
|
context 'when the subparameter was required' do
|
|
context 'but no parameters were permitted' do
|
|
it 'returns the correct message' do
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params.require(:person)
|
|
end
|
|
|
|
assertion = lambda do
|
|
params = {
|
|
person: {
|
|
name: 'some name',
|
|
age: 'some age',
|
|
},
|
|
}
|
|
expect(@controller).
|
|
to permit(:name, :age, :city, :country).
|
|
for(:create, params: params).
|
|
on(:person)
|
|
end
|
|
|
|
message =
|
|
'Expected POST #create to restrict parameters on :person to '\
|
|
":name, :age, :city, and :country,\n"\
|
|
'but it did not restrict any parameters.'
|
|
|
|
expect(&assertion).to fail_with_message(message)
|
|
end
|
|
end
|
|
|
|
context 'but some, but not all, parameters were permitted' do
|
|
it 'returns the correct message' do
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params.require(:person).permit(:name, :age)
|
|
end
|
|
|
|
assertion = lambda do
|
|
params = {
|
|
person: {
|
|
name: 'some name',
|
|
age: 'some age',
|
|
},
|
|
}
|
|
expect(@controller).
|
|
to permit(:name, :age, :city, :country).
|
|
for(:create, params: params).
|
|
on(:person)
|
|
end
|
|
|
|
message =
|
|
'Expected POST #create to restrict parameters on :person to '\
|
|
":name, :age, :city, and :country,\n"\
|
|
'but the restricted parameters were :name and :age instead.'
|
|
|
|
expect(&assertion).to fail_with_message(message)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'negative failure message' do
|
|
it 'returns the correct message' do
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params.permit(:name, :age, :city, :country)
|
|
end
|
|
|
|
assertion = lambda do
|
|
expect(@controller).
|
|
not_to permit(:name, :age, :city, :country).
|
|
for(:create)
|
|
end
|
|
|
|
message =
|
|
'Expected POST #create not to restrict parameters to '\
|
|
":name, :age, :city, and :country,\n"\
|
|
'but it did.'
|
|
|
|
expect(&assertion).to fail_with_message(message)
|
|
end
|
|
|
|
context 'qualified with #on' do
|
|
it 'returns the correct message' do
|
|
define_controller_with_strong_parameters(action: :create) do
|
|
params.require(:person).permit(:name, :age)
|
|
end
|
|
|
|
assertion = lambda do
|
|
params = {
|
|
person: {
|
|
name: 'some name',
|
|
age: 'some age',
|
|
},
|
|
}
|
|
expect(@controller).
|
|
not_to permit(:name, :age).
|
|
for(:create, params: params).
|
|
on(:person)
|
|
end
|
|
|
|
message =
|
|
'Expected POST #create not to restrict parameters on :person to '\
|
|
":name and :age,\n"\
|
|
'but it did.'
|
|
|
|
expect(&assertion).to fail_with_message(message)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#for' do
|
|
context 'when given :create' do
|
|
it 'POSTs to the controller' do
|
|
controller = ActionController::Base.new
|
|
context = build_context
|
|
matcher = permit(:name).for(:create).in_context(context)
|
|
|
|
matcher.matches?(controller)
|
|
|
|
expect_to_have_made_controller_request(
|
|
verb: :post,
|
|
action: :create,
|
|
params: {},
|
|
context: context,
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when given :update' do
|
|
it 'PATCHes to the controller' do
|
|
controller = ActionController::Base.new
|
|
context = build_context
|
|
matcher = permit(:name).for(:update).in_context(context)
|
|
|
|
matcher.matches?(controller)
|
|
|
|
expect_to_have_made_controller_request(
|
|
verb: :patch,
|
|
action: :update,
|
|
params: {},
|
|
context: context,
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when given a custom action and verb' do
|
|
it 'calls the action with the verb' do
|
|
controller = ActionController::Base.new
|
|
context = build_context
|
|
matcher = permit(:name).
|
|
for(:hide, verb: :delete).
|
|
in_context(context)
|
|
|
|
matcher.matches?(controller)
|
|
|
|
expect_to_have_made_controller_request(
|
|
verb: :delete,
|
|
action: :hide,
|
|
params: {},
|
|
context: context,
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
let(:simulated_error_class) do
|
|
Class.new(StandardError)
|
|
end
|
|
|
|
def define_controller_with_strong_parameters(options = {}, &action_body)
|
|
model_name = options.fetch(:model_name, 'User')
|
|
controller_name = options.fetch(:controller_name, 'UsersController')
|
|
collection_name = controller_name.
|
|
to_s.sub(/Controller$/, '').underscore.
|
|
to_sym
|
|
action_name = options.fetch(:action, :some_action)
|
|
routes = options.fetch(:routes, -> { resources collection_name })
|
|
|
|
define_model(model_name)
|
|
|
|
controller_class = define_controller(controller_name) do
|
|
define_method action_name do
|
|
if action_body
|
|
if action_body.arity == 0
|
|
instance_eval(&action_body)
|
|
else
|
|
action_body.call(self)
|
|
end
|
|
end
|
|
|
|
head :ok
|
|
end
|
|
end
|
|
|
|
setup_rails_controller_test(controller_class)
|
|
|
|
define_routes(&routes)
|
|
|
|
controller_class
|
|
end
|
|
|
|
def define_controller_raising_exception
|
|
_simulated_error_class = simulated_error_class
|
|
|
|
controller_class = define_controller('Examples') do
|
|
define_method :create do
|
|
raise _simulated_error_class
|
|
end
|
|
end
|
|
|
|
setup_rails_controller_test(controller_class)
|
|
|
|
define_routes do
|
|
get 'examples', to: 'examples#create'
|
|
end
|
|
|
|
controller_class
|
|
end
|
|
|
|
def build_context
|
|
double('context', post: nil, put: nil, patch: nil, delete: nil)
|
|
end
|
|
|
|
def expect_to_have_made_controller_request(context:, verb:, action:, params:)
|
|
if action_pack_gte_5?
|
|
expect(context).to have_received(verb).with(action, params: params)
|
|
else
|
|
expect(context).to have_received(verb).with(action, params)
|
|
end
|
|
end
|
|
end
|