shoulda-matchers 2.6.0 → 2.6.1.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile.lock +1 -1
- data/NEWS.md +34 -0
- data/README.md +14 -0
- data/features/activemodel_integration.feature +15 -0
- data/features/step_definitions/activemodel_steps.rb +21 -0
- data/gemfiles/3.0.gemfile.lock +1 -1
- data/gemfiles/3.1.gemfile.lock +1 -1
- data/gemfiles/3.2.gemfile.lock +1 -1
- data/gemfiles/4.0.0.gemfile.lock +1 -1
- data/gemfiles/4.0.1.gemfile.lock +1 -1
- data/gemfiles/4.1.gemfile.lock +1 -1
- data/lib/shoulda/matchers.rb +1 -0
- data/lib/shoulda/matchers/action_controller/callback_matcher.rb +11 -6
- data/lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb +59 -95
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +10 -18
- data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +10 -0
- data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +60 -18
- data/lib/shoulda/matchers/active_model/errors.rb +9 -7
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +4 -0
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +24 -5
- data/lib/shoulda/matchers/doublespeak.rb +27 -0
- data/lib/shoulda/matchers/doublespeak/double.rb +74 -0
- data/lib/shoulda/matchers/doublespeak/double_collection.rb +54 -0
- data/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +27 -0
- data/lib/shoulda/matchers/doublespeak/object_double.rb +32 -0
- data/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +30 -0
- data/lib/shoulda/matchers/doublespeak/structs.rb +8 -0
- data/lib/shoulda/matchers/doublespeak/stub_implementation.rb +34 -0
- data/lib/shoulda/matchers/doublespeak/world.rb +38 -0
- data/lib/shoulda/matchers/independent/delegate_matcher.rb +112 -61
- data/lib/shoulda/matchers/integrations/test_unit.rb +8 -6
- data/lib/shoulda/matchers/rails_shim.rb +16 -0
- data/lib/shoulda/matchers/version.rb +1 -1
- data/spec/shoulda/matchers/action_controller/callback_matcher_spec.rb +22 -19
- data/spec/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb +174 -65
- data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +14 -0
- data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +553 -211
- data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +6 -0
- data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +22 -0
- data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +23 -4
- data/spec/shoulda/matchers/doublespeak/double_collection_spec.rb +102 -0
- data/spec/shoulda/matchers/doublespeak/double_implementation_registry_spec.rb +21 -0
- data/spec/shoulda/matchers/doublespeak/double_spec.rb +144 -0
- data/spec/shoulda/matchers/doublespeak/object_double_spec.rb +77 -0
- data/spec/shoulda/matchers/doublespeak/proxy_implementation_spec.rb +40 -0
- data/spec/shoulda/matchers/doublespeak/stub_implementation_spec.rb +88 -0
- data/spec/shoulda/matchers/doublespeak/world_spec.rb +88 -0
- data/spec/shoulda/matchers/doublespeak_spec.rb +19 -0
- data/spec/shoulda/matchers/independent/delegate_matcher_spec.rb +105 -39
- data/spec/support/controller_builder.rb +18 -9
- data/spec/support/rails_versions.rb +4 -0
- metadata +34 -8
@@ -2,52 +2,55 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Shoulda::Matchers::ActionController::CallbackMatcher do
|
4
4
|
shared_examples 'CallbackMatcher' do |kind, callback_type|
|
5
|
-
let(:
|
6
|
-
let(:
|
5
|
+
let(:kind) { kind }
|
6
|
+
let(:callback_type) { callback_type }
|
7
|
+
let(:method_name) { :authenticate_user! }
|
8
|
+
let(:matcher) { described_class.new(method_name, kind, callback_type) }
|
9
|
+
let(:controller) { define_controller('HookController').new }
|
10
|
+
|
11
|
+
def match
|
12
|
+
__send__("use_#{kind}_#{callback_type}", method_name)
|
13
|
+
end
|
7
14
|
|
8
|
-
|
9
|
-
|
10
|
-
add_callback(kind, callback_type, :authenticate_user!)
|
15
|
+
it "matches when a #{kind} hook is in place" do
|
16
|
+
add_callback(kind, callback_type, method_name)
|
11
17
|
|
12
|
-
|
13
|
-
|
18
|
+
expect(controller).to match
|
19
|
+
end
|
14
20
|
|
15
|
-
|
16
|
-
|
17
|
-
end
|
21
|
+
it "does not match when a #{kind} hook is missing" do
|
22
|
+
expect(controller).not_to match
|
18
23
|
end
|
19
24
|
|
20
25
|
describe 'description' do
|
21
26
|
it 'includes the filter kind and name' do
|
22
|
-
expect(matcher.description).to eq "have
|
27
|
+
expect(matcher.description).to eq "have #{method_name.inspect} as a #{kind}_#{callback_type}"
|
23
28
|
end
|
24
29
|
end
|
25
30
|
|
26
31
|
describe 'failure message' do
|
27
32
|
it 'includes the filter kind and name that was expected' do
|
28
|
-
message = "Expected that HookController would have
|
33
|
+
message = "Expected that HookController would have #{method_name.inspect} as a #{kind}_#{callback_type}"
|
29
34
|
|
30
35
|
expect {
|
31
|
-
expect(controller).to send("use_#{kind}_#{callback_type}",
|
36
|
+
expect(controller).to send("use_#{kind}_#{callback_type}", method_name)
|
32
37
|
}.to fail_with_message(message)
|
33
38
|
end
|
34
39
|
end
|
35
40
|
|
36
41
|
describe 'failure message when negated' do
|
37
42
|
it 'includes the filter kind and name that was expected' do
|
38
|
-
add_callback(kind, callback_type,
|
39
|
-
message = "Expected that HookController would not have
|
43
|
+
add_callback(kind, callback_type, method_name)
|
44
|
+
message = "Expected that HookController would not have #{method_name.inspect} as a #{kind}_#{callback_type}"
|
40
45
|
|
41
|
-
expect {
|
42
|
-
expect(controller).not_to send("use_#{kind}_#{callback_type}", :authenticate_user!)
|
43
|
-
}.to fail_with_message(message)
|
46
|
+
expect { expect(controller).not_to match }.to fail_with_message(message)
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
47
50
|
private
|
48
51
|
|
49
52
|
def add_callback(kind, callback_type, callback)
|
50
|
-
controller.
|
53
|
+
controller.class.__send__("#{kind}_#{callback_type}", callback)
|
51
54
|
end
|
52
55
|
end
|
53
56
|
|
@@ -1,29 +1,29 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Shoulda::Matchers::ActionController do
|
4
|
-
describe
|
4
|
+
describe '#permit' do
|
5
5
|
it 'matches when the sent parameter is allowed' do
|
6
|
-
|
6
|
+
controller_for_resource_with_strong_parameters(action: :create) do
|
7
7
|
params.require(:user).permit(:name)
|
8
8
|
end
|
9
9
|
|
10
|
-
expect(
|
10
|
+
expect(@controller).to permit(:name).for(:create)
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'does not match when the sent parameter is not allowed' do
|
14
|
-
|
14
|
+
controller_for_resource_with_strong_parameters(action: :create) do
|
15
15
|
params.require(:user).permit(:name)
|
16
16
|
end
|
17
17
|
|
18
|
-
expect(
|
18
|
+
expect(@controller).not_to permit(:admin).for(:create)
|
19
19
|
end
|
20
20
|
|
21
21
|
it 'matches against multiple attributes' do
|
22
|
-
|
22
|
+
controller_for_resource_with_strong_parameters(action: :create) do
|
23
23
|
params.require(:user).permit(:name, :age)
|
24
24
|
end
|
25
25
|
|
26
|
-
expect(
|
26
|
+
expect(@controller).to permit(:name, :age).for(:create)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -56,145 +56,254 @@ describe Shoulda::Matchers::ActionController::StrongParametersMatcher do
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
describe
|
60
|
-
it
|
59
|
+
describe '#matches?' do
|
60
|
+
it 'is true for a subset of the allowable attributes' do
|
61
61
|
controller_for_resource_with_strong_parameters(action: :create) do
|
62
62
|
params.require(:user).permit(:name)
|
63
63
|
end
|
64
64
|
|
65
65
|
matcher = described_class.new([:name]).in_context(self).for(:create)
|
66
|
-
expect(matcher.matches?).to be_true
|
66
|
+
expect(matcher.matches?(@controller)).to be_true
|
67
67
|
end
|
68
68
|
|
69
|
-
it
|
69
|
+
it 'is true for all the allowable attributes' do
|
70
70
|
controller_for_resource_with_strong_parameters(action: :create) do
|
71
71
|
params.require(:user).permit(:name, :age)
|
72
72
|
end
|
73
73
|
|
74
74
|
matcher = described_class.new([:name, :age]).in_context(self).for(:create)
|
75
|
-
expect(matcher.matches?).to be_true
|
75
|
+
expect(matcher.matches?(@controller)).to be_true
|
76
76
|
end
|
77
77
|
|
78
|
-
it
|
78
|
+
it 'is false when any attributes are not allowed' do
|
79
79
|
controller_for_resource_with_strong_parameters(action: :create) do
|
80
80
|
params.require(:user).permit(:name)
|
81
81
|
end
|
82
82
|
|
83
83
|
matcher = described_class.new([:name, :admin]).in_context(self).for(:create)
|
84
|
-
expect(matcher.matches?).to be_false
|
84
|
+
expect(matcher.matches?(@controller)).to be_false
|
85
85
|
end
|
86
86
|
|
87
|
-
it
|
88
|
-
controller_for_resource_with_strong_parameters(action: :create)
|
87
|
+
it 'is false when permit is not called' do
|
88
|
+
controller_for_resource_with_strong_parameters(action: :create)
|
89
89
|
|
90
90
|
matcher = described_class.new([:name]).in_context(self).for(:create)
|
91
|
-
expect(matcher.matches?).to be_false
|
91
|
+
expect(matcher.matches?(@controller)).to be_false
|
92
92
|
end
|
93
93
|
|
94
|
-
it
|
94
|
+
it 'requires an action' do
|
95
|
+
controller_for_resource_with_strong_parameters
|
95
96
|
matcher = described_class.new([:name])
|
96
|
-
expect { matcher.matches? }
|
97
|
-
|
97
|
+
expect { matcher.matches?(@controller) }.
|
98
|
+
to raise_error(described_class::ActionNotDefinedError)
|
98
99
|
end
|
99
100
|
|
100
|
-
it
|
101
|
+
it 'requires a verb for non-restful action' do
|
102
|
+
controller_for_resource_with_strong_parameters
|
101
103
|
matcher = described_class.new([:name]).for(:authorize)
|
102
|
-
expect { matcher.matches? }
|
103
|
-
|
104
|
+
expect { matcher.matches?(@controller) }.
|
105
|
+
to raise_error(described_class::VerbNotDefinedError)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'works with routes that require extra params' do
|
109
|
+
options = {
|
110
|
+
controller_name: 'Posts',
|
111
|
+
action: :show,
|
112
|
+
routes: -> {
|
113
|
+
get '/posts/:slug', to: 'posts#show'
|
114
|
+
}
|
115
|
+
}
|
116
|
+
controller_for_resource_with_strong_parameters(options) do
|
117
|
+
params.require(:user).permit(:name)
|
118
|
+
end
|
119
|
+
|
120
|
+
matcher = described_class.new([:name]).
|
121
|
+
in_context(self).
|
122
|
+
for(:show, verb: :get, params: { slug: 'foo' })
|
123
|
+
expect(matcher.matches?(@controller)).to be_true
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'works with #update specifically' do
|
127
|
+
controller_for_resource_with_strong_parameters(action: :update) do
|
128
|
+
params.require(:user).permit(:name)
|
129
|
+
end
|
130
|
+
|
131
|
+
matcher = described_class.new([:name]).
|
132
|
+
in_context(self).
|
133
|
+
for(:update, params: { id: 1 })
|
134
|
+
expect(matcher.matches?(@controller)).to be_true
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'does not raise an error when #fetch was used instead of #require (issue #495)' do
|
138
|
+
controller_for_resource_with_strong_parameters(action: :create) do
|
139
|
+
params.fetch(:order, {}).permit(:eta, :diner_id)
|
140
|
+
end
|
141
|
+
|
142
|
+
matcher = described_class.new([:eta, :diner_id]).
|
143
|
+
in_context(self).
|
144
|
+
for(:create)
|
145
|
+
expect(matcher.matches?(@controller)).to be_true
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'tracks multiple calls to #permit' do
|
149
|
+
sets_of_attributes = [
|
150
|
+
[:eta, :diner_id],
|
151
|
+
[:phone_number, :address_1, :address_2, :city, :state, :zip]
|
152
|
+
]
|
153
|
+
controller_for_resource_with_strong_parameters(action: :create) do
|
154
|
+
params.require(:order).permit(sets_of_attributes[0])
|
155
|
+
params.require(:diner).permit(sets_of_attributes[1])
|
156
|
+
end
|
157
|
+
|
158
|
+
matcher = described_class.new(sets_of_attributes[0]).
|
159
|
+
in_context(self).
|
160
|
+
for(:create)
|
161
|
+
expect(matcher.matches?(@controller)).to be_true
|
162
|
+
|
163
|
+
matcher = described_class.new(sets_of_attributes[1]).
|
164
|
+
in_context(self).
|
165
|
+
for(:create)
|
166
|
+
expect(matcher.matches?(@controller)).to be_true
|
104
167
|
end
|
105
168
|
|
106
|
-
context '
|
107
|
-
it
|
169
|
+
context 'stubbing params on the controller' do
|
170
|
+
it 'still allows the original params to be set and accessed' do
|
171
|
+
actual_user_params = nil
|
172
|
+
actual_foo_param = nil
|
173
|
+
|
108
174
|
controller_for_resource_with_strong_parameters(action: :create) do
|
175
|
+
params[:foo] = 'bar'
|
176
|
+
actual_foo_param = params[:foo]
|
177
|
+
|
178
|
+
actual_user_params = params[:user]
|
179
|
+
|
109
180
|
params.require(:user).permit(:name)
|
110
181
|
end
|
111
182
|
|
112
|
-
described_class.new([:name]).
|
183
|
+
matcher = described_class.new([:name]).
|
184
|
+
in_context(self).
|
185
|
+
for(:create, params: { user: { some: 'params' } })
|
186
|
+
matcher.matches?(@controller)
|
187
|
+
|
188
|
+
expect(actual_user_params).to eq('some' => 'params')
|
189
|
+
expect(actual_foo_param).to eq 'bar'
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'stubs the params during the controller action' do
|
193
|
+
controller_for_resource_with_strong_parameters(action: :create) do
|
194
|
+
params.require(:user)
|
195
|
+
end
|
196
|
+
|
197
|
+
matcher = described_class.new([:name]).in_context(self).for(:create)
|
198
|
+
|
199
|
+
expect { matcher.matches?(@controller) }.not_to raise_error
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'does not permanently stub params' do
|
203
|
+
controller_for_resource_with_strong_parameters(action: :create)
|
204
|
+
|
205
|
+
matcher = described_class.new([:name]).in_context(self).for(:create)
|
206
|
+
matcher.matches?(@controller)
|
113
207
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
)
|
208
|
+
expect {
|
209
|
+
@controller.params.require(:user)
|
210
|
+
}.to raise_error(::ActionController::ParameterMissing)
|
118
211
|
end
|
119
212
|
|
120
|
-
it 'prevents permanently
|
213
|
+
it 'prevents permanently stubbing params on error' do
|
121
214
|
stub_controller_with_exception
|
122
215
|
|
123
216
|
begin
|
124
|
-
described_class.new([:name]).in_context(self).for(:create)
|
217
|
+
matcher = described_class.new([:name]).in_context(self).for(:create)
|
218
|
+
matcher.matches?(@controller)
|
125
219
|
rescue SimulatedError
|
126
220
|
end
|
127
221
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
)
|
222
|
+
expect {
|
223
|
+
@controller.params.require(:user)
|
224
|
+
}.to raise_error(::ActionController::ParameterMissing)
|
132
225
|
end
|
133
226
|
end
|
134
227
|
end
|
135
228
|
|
136
|
-
describe
|
137
|
-
it
|
138
|
-
|
229
|
+
describe 'failure message' do
|
230
|
+
it 'includes all missing attributes' do
|
231
|
+
controller_for_resource_with_strong_parameters(action: :create) do
|
139
232
|
params.require(:user).permit(:name, :age)
|
140
233
|
end
|
141
234
|
|
142
235
|
expect {
|
143
|
-
expect(
|
144
|
-
}.to fail_with_message(
|
236
|
+
expect(@controller).to permit(:name, :age, :city, :country).for(:create)
|
237
|
+
}.to fail_with_message('Expected controller to permit city and country, but it did not.')
|
145
238
|
end
|
146
239
|
|
147
|
-
it
|
148
|
-
|
240
|
+
it 'includes all attributes that should not have been allowed but were' do
|
241
|
+
controller_for_resource_with_strong_parameters(action: :create) do
|
149
242
|
params.require(:user).permit(:name, :age)
|
150
243
|
end
|
151
244
|
|
152
245
|
expect {
|
153
|
-
expect(
|
154
|
-
}.to fail_with_message(
|
246
|
+
expect(@controller).not_to permit(:name, :age).for(:create)
|
247
|
+
}.to fail_with_message('Expected controller not to permit name and age, but it did.')
|
155
248
|
end
|
156
249
|
end
|
157
250
|
|
158
|
-
describe
|
159
|
-
context
|
160
|
-
it
|
161
|
-
|
251
|
+
describe '#for' do
|
252
|
+
context 'when given :create' do
|
253
|
+
it 'POSTs to the controller' do
|
254
|
+
controller = ActionController::Base.new
|
255
|
+
context = mock()
|
256
|
+
context.expects(:post).with(:create, {})
|
162
257
|
matcher = described_class.new([:name]).in_context(context).for(:create)
|
163
258
|
|
164
|
-
matcher.matches?
|
165
|
-
expect(context).to have_received(:post).with(:create)
|
259
|
+
matcher.matches?(controller)
|
166
260
|
end
|
167
261
|
end
|
168
262
|
|
169
|
-
context
|
170
|
-
|
171
|
-
|
172
|
-
|
263
|
+
context 'when given :update' do
|
264
|
+
if rails_gte_41?
|
265
|
+
it 'PATCHes to the controller' do
|
266
|
+
controller = ActionController::Base.new
|
267
|
+
context = mock()
|
268
|
+
context.expects(:patch).with(:update, {})
|
269
|
+
matcher = described_class.new([:name]).in_context(context).for(:update)
|
173
270
|
|
174
|
-
|
175
|
-
|
271
|
+
matcher.matches?(controller)
|
272
|
+
end
|
273
|
+
else
|
274
|
+
it 'PUTs to the controller' do
|
275
|
+
controller = ActionController::Base.new
|
276
|
+
context = mock()
|
277
|
+
context.expects(:put).with(:update, {})
|
278
|
+
matcher = described_class.new([:name]).in_context(context).for(:update)
|
279
|
+
|
280
|
+
matcher.matches?(controller)
|
281
|
+
end
|
176
282
|
end
|
177
283
|
end
|
178
284
|
|
179
|
-
context
|
180
|
-
it
|
181
|
-
|
182
|
-
|
285
|
+
context 'when given a custom action and verb' do
|
286
|
+
it 'calls the action with the verb' do
|
287
|
+
controller = ActionController::Base.new
|
288
|
+
context = mock()
|
289
|
+
context.expects(:delete).with(:hide, {})
|
290
|
+
matcher = described_class.new([:name]).
|
291
|
+
in_context(context).
|
292
|
+
for(:hide, verb: :delete)
|
183
293
|
|
184
|
-
matcher.matches?
|
185
|
-
expect(context).to have_received(:delete).with(:hide)
|
294
|
+
matcher.matches?(controller)
|
186
295
|
end
|
187
296
|
end
|
188
297
|
end
|
189
298
|
|
190
299
|
def stub_controller_with_exception
|
191
|
-
|
300
|
+
controller_class = define_controller('Examples') do
|
192
301
|
def create
|
193
302
|
raise SimulatedError
|
194
303
|
end
|
195
304
|
end
|
196
305
|
|
197
|
-
setup_rails_controller_test(
|
306
|
+
setup_rails_controller_test(controller_class)
|
198
307
|
|
199
308
|
define_routes do
|
200
309
|
get 'examples', to: 'examples#create'
|
@@ -24,6 +24,20 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher do
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
describe '#_after_setting_value' do
|
28
|
+
it 'sets a block which is yielded after each value is set on the attribute' do
|
29
|
+
attribute = :attr
|
30
|
+
record = define_model(:example, attribute => :string).new
|
31
|
+
matcher = described_class.new('a', 'b', 'c').for(attribute)
|
32
|
+
call_count = 0
|
33
|
+
|
34
|
+
matcher._after_setting_value { call_count += 1 }
|
35
|
+
matcher.matches?(record)
|
36
|
+
|
37
|
+
expect(call_count).to eq 3
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
27
41
|
context 'an attribute with a validation' do
|
28
42
|
it 'allows a good value' do
|
29
43
|
expect(validating_format(with: /abc/)).to allow_value('abcde').for(:attr)
|