durable_parameters 0.2.3

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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +853 -0
  4. data/Rakefile +29 -0
  5. data/app/params/account_params.rb.example +38 -0
  6. data/app/params/application_params.rb +16 -0
  7. data/lib/durable_parameters/adapters/hanami.rb +138 -0
  8. data/lib/durable_parameters/adapters/rage.rb +124 -0
  9. data/lib/durable_parameters/adapters/rails.rb +280 -0
  10. data/lib/durable_parameters/adapters/sinatra.rb +91 -0
  11. data/lib/durable_parameters/core/application_params.rb +334 -0
  12. data/lib/durable_parameters/core/configuration.rb +83 -0
  13. data/lib/durable_parameters/core/forbidden_attributes_protection.rb +48 -0
  14. data/lib/durable_parameters/core/parameters.rb +643 -0
  15. data/lib/durable_parameters/core/params_registry.rb +110 -0
  16. data/lib/durable_parameters/core.rb +15 -0
  17. data/lib/durable_parameters/log_subscriber.rb +34 -0
  18. data/lib/durable_parameters/railtie.rb +65 -0
  19. data/lib/durable_parameters/version.rb +7 -0
  20. data/lib/durable_parameters.rb +41 -0
  21. data/lib/generators/rails/USAGE +12 -0
  22. data/lib/generators/rails/durable_parameters_controller_generator.rb +17 -0
  23. data/lib/generators/rails/templates/controller.rb +94 -0
  24. data/lib/legacy/action_controller/application_params.rb +235 -0
  25. data/lib/legacy/action_controller/parameters.rb +524 -0
  26. data/lib/legacy/action_controller/params_registry.rb +108 -0
  27. data/lib/legacy/active_model/forbidden_attributes_protection.rb +40 -0
  28. data/test/action_controller_required_params_test.rb +36 -0
  29. data/test/action_controller_tainted_params_test.rb +29 -0
  30. data/test/active_model_mass_assignment_taint_protection_test.rb +25 -0
  31. data/test/application_params_array_test.rb +245 -0
  32. data/test/application_params_edge_cases_test.rb +361 -0
  33. data/test/application_params_test.rb +893 -0
  34. data/test/controller_generator_test.rb +31 -0
  35. data/test/core_parameters_test.rb +2376 -0
  36. data/test/durable_parameters_test.rb +115 -0
  37. data/test/enhanced_error_messages_test.rb +120 -0
  38. data/test/gemfiles/Gemfile.rails-3.0.x +14 -0
  39. data/test/gemfiles/Gemfile.rails-3.1.x +14 -0
  40. data/test/gemfiles/Gemfile.rails-3.2.x +14 -0
  41. data/test/log_on_unpermitted_params_test.rb +49 -0
  42. data/test/metadata_validation_test.rb +294 -0
  43. data/test/multi_parameter_attributes_test.rb +38 -0
  44. data/test/parameters_core_methods_test.rb +503 -0
  45. data/test/parameters_integration_test.rb +553 -0
  46. data/test/parameters_permit_test.rb +491 -0
  47. data/test/parameters_require_test.rb +9 -0
  48. data/test/parameters_taint_test.rb +98 -0
  49. data/test/params_registry_concurrency_test.rb +422 -0
  50. data/test/params_registry_test.rb +112 -0
  51. data/test/permit_by_model_test.rb +227 -0
  52. data/test/raise_on_unpermitted_params_test.rb +32 -0
  53. data/test/test_helper.rb +38 -0
  54. data/test/transform_params_edge_cases_test.rb +526 -0
  55. data/test/transformation_test.rb +360 -0
  56. metadata +223 -0
@@ -0,0 +1,526 @@
1
+ require 'test_helper'
2
+
3
+ class TransformParamsEdgeCasesTest < Minitest::Test
4
+ def setup
5
+ ActionController::ParamsRegistry.clear!
6
+
7
+ @user_params_class = Class.new(ActionController::ApplicationParams) do
8
+ def self.name
9
+ 'UserParams'
10
+ end
11
+
12
+ allow :name
13
+ allow :email
14
+ allow :status, only: [:create, :update]
15
+ allow :role, except: :destroy
16
+ metadata :ip_address
17
+ end
18
+
19
+ ActionController::ParamsRegistry.register('User', @user_params_class)
20
+ end
21
+
22
+ def teardown
23
+ ActionController::ParamsRegistry.clear!
24
+ end
25
+
26
+ # Test without require
27
+ def test_transform_params_without_require_returns_empty
28
+ params = ActionController::Parameters.new(
29
+ name: 'John',
30
+ email: 'john@example.com'
31
+ )
32
+
33
+ # Without require, no required_key is set
34
+ permitted = params.transform_params()
35
+
36
+ assert permitted.permitted?
37
+ assert_nil permitted[:name]
38
+ assert_nil permitted[:email]
39
+ end
40
+
41
+ # Test with require but no registration
42
+ def test_transform_params_with_unregistered_model_returns_empty
43
+ params = ActionController::Parameters.new(
44
+ unregistered: {
45
+ name: 'Test',
46
+ value: 123
47
+ }
48
+ )
49
+
50
+ permitted = params.require(:unregistered).transform_params()
51
+
52
+ assert permitted.permitted?
53
+ assert_nil permitted[:name]
54
+ assert_nil permitted[:value]
55
+ end
56
+
57
+ # Test explicit nil params class
58
+ def test_transform_params_with_explicit_nil_class_returns_empty
59
+ params = ActionController::Parameters.new(
60
+ user: {
61
+ name: 'Test',
62
+ email: 'test@example.com'
63
+ }
64
+ )
65
+
66
+ permitted = params.require(:user).transform_params(nil)
67
+
68
+ assert permitted.permitted?
69
+ assert_nil permitted[:name]
70
+ assert_nil permitted[:email]
71
+ end
72
+
73
+ # Test empty parameters
74
+ def test_transform_params_with_empty_hash
75
+ params = ActionController::Parameters.new(
76
+ user: {}
77
+ )
78
+
79
+ # Don't use require for empty hash - it would raise ParameterMissing
80
+ # Instead, access directly and set the required_key manually
81
+ user_params = params[:user]
82
+ user_params.required_key = :user
83
+
84
+ permitted = user_params.transform_params()
85
+
86
+ assert permitted.permitted?
87
+ assert_equal({}, permitted.to_h)
88
+ end
89
+
90
+ # Test with nil values
91
+ def test_transform_params_preserves_nil_values
92
+ params = ActionController::Parameters.new(
93
+ user: {
94
+ name: nil,
95
+ email: 'test@example.com'
96
+ }
97
+ )
98
+
99
+ permitted = params.require(:user).transform_params()
100
+
101
+ assert_nil permitted[:name]
102
+ assert_equal 'test@example.com', permitted[:email]
103
+ end
104
+
105
+ # Test with empty string values
106
+ def test_transform_params_preserves_empty_strings
107
+ params = ActionController::Parameters.new(
108
+ user: {
109
+ name: '',
110
+ email: 'test@example.com'
111
+ }
112
+ )
113
+
114
+ permitted = params.require(:user).transform_params()
115
+
116
+ assert_equal '', permitted[:name]
117
+ assert_equal 'test@example.com', permitted[:email]
118
+ end
119
+
120
+ # Test with numeric values
121
+ def test_transform_params_with_numeric_values
122
+ test_class = Class.new(ActionController::ApplicationParams) do
123
+ def self.name
124
+ 'ProductParams'
125
+ end
126
+ allow :price
127
+ allow :quantity
128
+ end
129
+
130
+ ActionController::ParamsRegistry.register('Product', test_class)
131
+
132
+ params = ActionController::Parameters.new(
133
+ product: {
134
+ price: 19.99,
135
+ quantity: 5
136
+ }
137
+ )
138
+
139
+ permitted = params.require(:product).transform_params()
140
+
141
+ assert_equal 19.99, permitted[:price]
142
+ assert_equal 5, permitted[:quantity]
143
+ end
144
+
145
+ # Test with boolean values
146
+ def test_transform_params_with_boolean_values
147
+ test_class = Class.new(ActionController::ApplicationParams) do
148
+ def self.name
149
+ 'SettingsParams'
150
+ end
151
+ allow :enabled
152
+ allow :public
153
+ end
154
+
155
+ ActionController::ParamsRegistry.register('Settings', test_class)
156
+
157
+ params = ActionController::Parameters.new(
158
+ settings: {
159
+ enabled: true,
160
+ public: false
161
+ }
162
+ )
163
+
164
+ permitted = params.require(:settings).transform_params()
165
+
166
+ assert_equal true, permitted[:enabled]
167
+ assert_equal false, permitted[:public]
168
+ end
169
+
170
+ # Test with special characters in values
171
+ def test_transform_params_with_special_characters
172
+ params = ActionController::Parameters.new(
173
+ user: {
174
+ name: "O'Brien",
175
+ email: 'test+spam@example.com'
176
+ }
177
+ )
178
+
179
+ permitted = params.require(:user).transform_params()
180
+
181
+ assert_equal "O'Brien", permitted[:name]
182
+ assert_equal 'test+spam@example.com', permitted[:email]
183
+ end
184
+
185
+ # Test with unicode characters
186
+ def test_transform_params_with_unicode_characters
187
+ params = ActionController::Parameters.new(
188
+ user: {
189
+ name: '日本語',
190
+ email: 'test@example.com'
191
+ }
192
+ )
193
+
194
+ permitted = params.require(:user).transform_params()
195
+
196
+ assert_equal '日本語', permitted[:name]
197
+ assert_equal 'test@example.com', permitted[:email]
198
+ end
199
+
200
+ # Test action filter with string action
201
+ def test_transform_params_with_string_action
202
+ params = ActionController::Parameters.new(
203
+ user: {
204
+ name: 'Test',
205
+ status: 'active'
206
+ }
207
+ )
208
+
209
+ permitted = params.require(:user).transform_params(action: 'create')
210
+
211
+ assert_equal 'Test', permitted[:name]
212
+ assert_equal 'active', permitted[:status]
213
+ end
214
+
215
+ # Test action filter with non-existent action
216
+ def test_transform_params_with_non_matching_action
217
+ params = ActionController::Parameters.new(
218
+ user: {
219
+ name: 'Test',
220
+ status: 'active',
221
+ role: 'admin'
222
+ }
223
+ )
224
+
225
+ permitted = params.require(:user).transform_params(action: :show)
226
+
227
+ assert_equal 'Test', permitted[:name]
228
+ assert_nil permitted[:status] # only allowed for create/update
229
+ assert_equal 'admin', permitted[:role] # allowed except for destroy
230
+ end
231
+
232
+ # Test additional_attrs with empty array
233
+ def test_transform_params_with_empty_additional_attrs
234
+ params = ActionController::Parameters.new(
235
+ user: {
236
+ name: 'Test',
237
+ email: 'test@example.com'
238
+ }
239
+ )
240
+
241
+ permitted = params.require(:user).transform_params(additional_attrs: [])
242
+
243
+ assert_equal 'Test', permitted[:name]
244
+ assert_equal 'test@example.com', permitted[:email]
245
+ end
246
+
247
+ # Test additional_attrs with non-existent attributes
248
+ def test_transform_params_additional_attrs_non_existent
249
+ params = ActionController::Parameters.new(
250
+ user: {
251
+ name: 'Test',
252
+ email: 'test@example.com'
253
+ }
254
+ )
255
+
256
+ permitted = params.require(:user).transform_params(additional_attrs: [:non_existent])
257
+
258
+ assert_equal 'Test', permitted[:name]
259
+ assert_equal 'test@example.com', permitted[:email]
260
+ assert_nil permitted[:non_existent]
261
+ end
262
+
263
+ # Test additional_attrs with string keys
264
+ def test_transform_params_additional_attrs_with_strings
265
+ params = ActionController::Parameters.new(
266
+ user: {
267
+ name: 'Test',
268
+ email: 'test@example.com',
269
+ age: 30
270
+ }
271
+ )
272
+
273
+ permitted = params.require(:user).transform_params(additional_attrs: ['age'])
274
+
275
+ assert_equal 'Test', permitted[:name]
276
+ assert_equal 'test@example.com', permitted[:email]
277
+ assert_equal 30, permitted[:age]
278
+ end
279
+
280
+ # Test combining action and additional_attrs
281
+ def test_transform_params_with_action_and_additional_attrs
282
+ params = ActionController::Parameters.new(
283
+ user: {
284
+ name: 'Test',
285
+ email: 'test@example.com',
286
+ status: 'active',
287
+ age: 30
288
+ }
289
+ )
290
+
291
+ permitted = params.require(:user).transform_params(
292
+ action: :create,
293
+ additional_attrs: [:age]
294
+ )
295
+
296
+ assert_equal 'Test', permitted[:name]
297
+ assert_equal 'test@example.com', permitted[:email]
298
+ assert_equal 'active', permitted[:status]
299
+ assert_equal 30, permitted[:age]
300
+ end
301
+
302
+ # Test combining all options
303
+ def test_transform_params_with_all_options
304
+ params = ActionController::Parameters.new(
305
+ user: {
306
+ name: 'Test',
307
+ email: 'test@example.com',
308
+ status: 'active',
309
+ age: 30
310
+ }
311
+ )
312
+
313
+ permitted = params.require(:user).transform_params(
314
+ action: :create,
315
+ additional_attrs: [:age],
316
+ current_user: Object.new,
317
+ ip_address: '127.0.0.1'
318
+ )
319
+
320
+ assert_equal 'Test', permitted[:name]
321
+ assert_equal 'test@example.com', permitted[:email]
322
+ assert_equal 'active', permitted[:status]
323
+ assert_equal 30, permitted[:age]
324
+ end
325
+
326
+ # Test required_key is preserved through chaining
327
+ def test_required_key_preserved_after_require
328
+ params = ActionController::Parameters.new(
329
+ user: {
330
+ name: 'Test'
331
+ }
332
+ )
333
+
334
+ user_params = params.require(:user)
335
+ assert_equal :user, user_params.required_key
336
+ end
337
+
338
+ # Test explicit params class overrides registry lookup
339
+ def test_explicit_params_class_overrides_registry
340
+ other_params_class = Class.new(ActionController::ApplicationParams) do
341
+ def self.name
342
+ 'OtherParams'
343
+ end
344
+ allow :different_field
345
+ end
346
+
347
+ params = ActionController::Parameters.new(
348
+ user: {
349
+ name: 'Test',
350
+ email: 'test@example.com',
351
+ different_field: 'value'
352
+ }
353
+ )
354
+
355
+ # Even though 'user' is registered, explicit class should be used
356
+ permitted = params.require(:user).transform_params(other_params_class)
357
+
358
+ assert_nil permitted[:name]
359
+ assert_nil permitted[:email]
360
+ assert_equal 'value', permitted[:different_field]
361
+ end
362
+
363
+ # Test with array values
364
+ def test_transform_params_does_not_permit_arrays_without_explicit_declaration
365
+ params = ActionController::Parameters.new(
366
+ user: {
367
+ name: 'Test',
368
+ tags: ['tag1', 'tag2']
369
+ }
370
+ )
371
+
372
+ permitted = params.require(:user).transform_params()
373
+
374
+ assert_equal 'Test', permitted[:name]
375
+ assert_nil permitted[:tags] # Arrays not permitted by default
376
+ end
377
+
378
+ # Test with hash values
379
+ def test_transform_params_does_not_permit_nested_hashes_without_explicit_declaration
380
+ params = ActionController::Parameters.new(
381
+ user: {
382
+ name: 'Test',
383
+ address: {
384
+ street: '123 Main St',
385
+ city: 'NYC'
386
+ }
387
+ }
388
+ )
389
+
390
+ permitted = params.require(:user).transform_params()
391
+
392
+ assert_equal 'Test', permitted[:name]
393
+ assert_nil permitted[:address] # Nested hashes not permitted by default
394
+ end
395
+
396
+ # Test metadata validation with explicit params class
397
+ def test_metadata_validation_with_explicit_params_class
398
+ other_params_class = Class.new(ActionController::ApplicationParams) do
399
+ def self.name
400
+ 'OtherParams'
401
+ end
402
+ allow :field
403
+ # No metadata declared
404
+ end
405
+
406
+ params = ActionController::Parameters.new(
407
+ user: { field: 'value' }
408
+ )
409
+
410
+ # Should validate against explicit class, not registry
411
+ error = assert_raises(ArgumentError) do
412
+ params.require(:user).transform_params(
413
+ other_params_class,
414
+ ip_address: '127.0.0.1'
415
+ )
416
+ end
417
+
418
+ assert_includes error.message, 'OtherParams'
419
+ assert_includes error.message, 'ip_address'
420
+ end
421
+
422
+ # Test case sensitivity
423
+ def test_transform_params_attribute_names_case_sensitive
424
+ test_class = Class.new(ActionController::ApplicationParams) do
425
+ def self.name
426
+ 'CaseParams'
427
+ end
428
+ allow :Name # capital N
429
+ end
430
+
431
+ ActionController::ParamsRegistry.register('Case', test_class)
432
+
433
+ params = ActionController::Parameters.new(
434
+ case: {
435
+ name: 'lowercase', # lowercase n
436
+ Name: 'capitalized'
437
+ }
438
+ )
439
+
440
+ permitted = params.require(:case).transform_params()
441
+
442
+ # Should only permit the exact case match
443
+ assert_equal 'capitalized', permitted[:Name]
444
+ assert_nil permitted[:name]
445
+ end
446
+
447
+ # Test with Date/Time objects
448
+ def test_transform_params_with_date_time_objects
449
+ test_class = Class.new(ActionController::ApplicationParams) do
450
+ def self.name
451
+ 'EventParams'
452
+ end
453
+ allow :scheduled_at
454
+ allow :date
455
+ end
456
+
457
+ ActionController::ParamsRegistry.register('Event', test_class)
458
+
459
+ now = Time.now
460
+ today = Date.today
461
+
462
+ params = ActionController::Parameters.new(
463
+ event: {
464
+ scheduled_at: now,
465
+ date: today
466
+ }
467
+ )
468
+
469
+ permitted = params.require(:event).transform_params()
470
+
471
+ assert_equal now, permitted[:scheduled_at]
472
+ assert_equal today, permitted[:date]
473
+ end
474
+
475
+ # Test duplicate additional_attrs
476
+ def test_transform_params_with_duplicate_additional_attrs
477
+ params = ActionController::Parameters.new(
478
+ user: {
479
+ name: 'Test',
480
+ age: 30
481
+ }
482
+ )
483
+
484
+ # name is already allowed, age is additional
485
+ permitted = params.require(:user).transform_params(
486
+ additional_attrs: [:name, :age, :name]
487
+ )
488
+
489
+ assert_equal 'Test', permitted[:name]
490
+ assert_equal 30, permitted[:age]
491
+ end
492
+
493
+ # Test that transform_params returns a new Parameters instance
494
+ def test_transform_params_returns_new_instance
495
+ params = ActionController::Parameters.new(
496
+ user: {
497
+ name: 'Test',
498
+ email: 'test@example.com'
499
+ }
500
+ )
501
+
502
+ user_params = params.require(:user)
503
+ permitted = user_params.transform_params()
504
+
505
+ refute_equal user_params.object_id, permitted.object_id
506
+ end
507
+
508
+ # Test that original params are not modified
509
+ def test_transform_params_does_not_modify_original
510
+ params = ActionController::Parameters.new(
511
+ user: {
512
+ name: 'Test',
513
+ email: 'test@example.com',
514
+ admin: true
515
+ }
516
+ )
517
+
518
+ user_params = params.require(:user)
519
+ permitted = user_params.transform_params()
520
+
521
+ # Original should still have all keys
522
+ assert user_params.key?(:admin)
523
+ # Permitted should not
524
+ assert !permitted.key?(:admin)
525
+ end
526
+ end