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,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class DurableParametersTest < Minitest::Test
6
+ def test_top_level_aliases_are_defined
7
+ assert defined?(StrongParameters::Parameters)
8
+ assert defined?(StrongParameters::ApplicationParams)
9
+ assert defined?(StrongParameters::ParamsRegistry)
10
+ assert defined?(StrongParameters::ForbiddenAttributesProtection)
11
+ end
12
+
13
+ def test_aliases_point_to_core_classes
14
+ assert_equal StrongParameters::Core::Parameters, StrongParameters::Parameters
15
+ assert_equal StrongParameters::Core::ApplicationParams, StrongParameters::ApplicationParams
16
+ assert_equal StrongParameters::Core::ParamsRegistry, StrongParameters::ParamsRegistry
17
+ assert_equal StrongParameters::Core::ForbiddenAttributesProtection, StrongParameters::ForbiddenAttributesProtection
18
+ end
19
+
20
+
21
+
22
+ def test_module_has_version
23
+ assert defined?(StrongParameters::VERSION)
24
+ assert StrongParameters::VERSION.is_a?(String)
25
+ assert_match(/\A\d+\.\d+\.\d+(\.\w+)?\z/, StrongParameters::VERSION)
26
+ end
27
+
28
+ def test_core_module_is_defined
29
+ assert defined?(StrongParameters::Core)
30
+ assert StrongParameters::Core.is_a?(Module)
31
+ end
32
+
33
+ def test_strong_parameters_is_module
34
+ assert StrongParameters.is_a?(Module)
35
+ end
36
+
37
+ def test_adapter_loading_rails
38
+ # Rails is loaded in test_helper, so railtie and log_subscriber should be required
39
+ # We can't easily test the require calls, but we can check if the adapters are available
40
+ # Since Rails is defined, the Rails adapter should be loaded
41
+ assert defined?(StrongParameters::Adapters::Rails)
42
+ end
43
+
44
+ def test_adapter_loading_sinatra
45
+ # Test that Sinatra adapter would be loaded if Sinatra was defined
46
+ # Since Sinatra is not defined in this test suite, we can't test the require
47
+ # But we can check that the adapter file exists
48
+ adapter_path = File.join(__dir__, '..', 'lib', 'durable_parameters', 'adapters', 'sinatra.rb')
49
+ assert File.exist?(adapter_path), "Sinatra adapter file should exist"
50
+ end
51
+
52
+ def test_adapter_loading_hanami
53
+ adapter_path = File.join(__dir__, '..', 'lib', 'durable_parameters', 'adapters', 'hanami.rb')
54
+ assert File.exist?(adapter_path), "Hanami adapter file should exist"
55
+ end
56
+
57
+ def test_adapter_loading_rage
58
+ adapter_path = File.join(__dir__, '..', 'lib', 'durable_parameters', 'adapters', 'rage.rb')
59
+ assert File.exist?(adapter_path), "Rage adapter file should exist"
60
+ end
61
+
62
+ def test_aliases_are_not_redefined_if_already_defined
63
+ # Test that if StrongParameters::Parameters is already defined, it won't be redefined
64
+ original_value = StrongParameters::Parameters
65
+ # Simulate re-requiring (though in practice it's already required)
66
+ # Since the code uses unless defined?(Parameters), and Parameters is StrongParameters::Parameters
67
+ # It should not redefine if already defined
68
+ assert_equal original_value, StrongParameters::Parameters
69
+ end
70
+
71
+ def test_adapters_module_is_defined
72
+ assert defined?(StrongParameters::Adapters)
73
+ assert StrongParameters::Adapters.is_a?(Module)
74
+ end
75
+
76
+ def test_rails_adapter_is_module
77
+ assert StrongParameters::Adapters::Rails.is_a?(Module)
78
+ end
79
+
80
+ def test_sinatra_adapter_file_exists_and_loadable
81
+ adapter_path = File.join(__dir__, '..', 'lib', 'durable_parameters', 'adapters', 'sinatra.rb')
82
+ assert File.exist?(adapter_path)
83
+ # Test that it can be required without error
84
+ assert require 'durable_parameters/adapters/sinatra'
85
+ assert defined?(StrongParameters::Adapters::Sinatra)
86
+ end
87
+
88
+ def test_hanami_adapter_file_exists_and_loadable
89
+ adapter_path = File.join(__dir__, '..', 'lib', 'durable_parameters', 'adapters', 'hanami.rb')
90
+ assert File.exist?(adapter_path)
91
+ # Test that it can be required without error
92
+ assert require 'durable_parameters/adapters/hanami'
93
+ assert defined?(StrongParameters::Adapters::Hanami)
94
+ end
95
+
96
+ def test_rage_adapter_file_exists_and_loadable
97
+ adapter_path = File.join(__dir__, '..', 'lib', 'durable_parameters', 'adapters', 'rage.rb')
98
+ assert File.exist?(adapter_path)
99
+ # Test that it can be required without error
100
+ assert require 'durable_parameters/adapters/rage'
101
+ assert defined?(StrongParameters::Adapters::Rage)
102
+ end
103
+
104
+ def test_version_is_not_empty
105
+ refute StrongParameters::VERSION.empty?
106
+ end
107
+
108
+ def test_core_classes_are_accessible_via_aliases
109
+ # Test that the aliases work for instantiation or basic methods
110
+ assert StrongParameters::Parameters.new({}).is_a?(StrongParameters::Core::Parameters)
111
+ assert StrongParameters::ApplicationParams.is_a?(Class)
112
+ assert StrongParameters::ParamsRegistry.is_a?(Class)
113
+ assert StrongParameters::ForbiddenAttributesProtection.is_a?(Module)
114
+ end
115
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class EnhancedErrorMessagesTest < Minitest::Test
6
+ def setup
7
+ @params = StrongParameters::Core::Parameters.new(
8
+ usr: { name: 'John', email: 'john@example.com' },
9
+ account: { balance: 100 },
10
+ data: { value: 'test' }
11
+ )
12
+ end
13
+
14
+ def test_parameter_missing_includes_available_keys_in_error_message
15
+ error = assert_raises(StrongParameters::Core::ParameterMissing) do
16
+ @params.require(:missing_key)
17
+ end
18
+
19
+ assert_match(/Available keys:/, error.message)
20
+ assert_match(/usr/, error.message)
21
+ assert_match(/account/, error.message)
22
+ assert_match(/data/, error.message)
23
+ end
24
+
25
+ def test_parameter_missing_suggests_similar_keys
26
+ error = assert_raises(StrongParameters::Core::ParameterMissing) do
27
+ @params.require(:user) # Similar to 'usr'
28
+ end
29
+
30
+ assert_match(/Did you mean\?/, error.message)
31
+ assert_match(/usr/, error.message)
32
+ end
33
+
34
+ def test_parameter_missing_suggests_keys_starting_with_same_letter
35
+ error = assert_raises(StrongParameters::Core::ParameterMissing) do
36
+ @params.require(:accounts) # Similar to 'account'
37
+ end
38
+
39
+ assert_match(/Did you mean\?/, error.message)
40
+ assert_match(/account/, error.message)
41
+ end
42
+
43
+ def test_parameter_missing_does_not_suggest_when_no_similar_keys_exist
44
+ error = assert_raises(StrongParameters::Core::ParameterMissing) do
45
+ @params.require(:xyz)
46
+ end
47
+
48
+ # Should show available keys but not suggestions
49
+ assert_match(/Available keys:/, error.message)
50
+ refute_match(/Did you mean\?/, error.message)
51
+ end
52
+
53
+ def test_parameter_missing_with_empty_params_shows_helpful_message
54
+ empty_params = StrongParameters::Core::Parameters.new({})
55
+
56
+ error = assert_raises(StrongParameters::Core::ParameterMissing) do
57
+ empty_params.require(:user)
58
+ end
59
+
60
+ assert_match(/param is missing or the value is empty: user/, error.message)
61
+ end
62
+
63
+ def test_parameter_missing_with_nested_params
64
+ params = StrongParameters::Core::Parameters.new(
65
+ user: { profile: { name: 'John' } }
66
+ )
67
+
68
+ user_params = params.require(:user)
69
+
70
+ error = assert_raises(StrongParameters::Core::ParameterMissing) do
71
+ user_params.require(:preferences)
72
+ end
73
+
74
+ assert_match(/Available keys:/, error.message)
75
+ assert_match(/profile/, error.message)
76
+ end
77
+
78
+ def test_parameter_missing_stores_param_attribute
79
+ error = assert_raises(StrongParameters::Core::ParameterMissing) do
80
+ @params.require(:missing)
81
+ end
82
+
83
+ assert_equal 'missing', error.param
84
+ end
85
+
86
+ def test_parameter_missing_when_value_is_empty_hash
87
+ params = StrongParameters::Core::Parameters.new(user: {})
88
+
89
+ error = assert_raises(StrongParameters::Core::ParameterMissing) do
90
+ params.require(:user)
91
+ end
92
+
93
+ assert_match(/param is missing or the value is empty: user/, error.message)
94
+ end
95
+
96
+ def test_parameter_missing_when_value_is_empty_array
97
+ params = StrongParameters::Core::Parameters.new(tags: [])
98
+
99
+ error = assert_raises(StrongParameters::Core::ParameterMissing) do
100
+ params.require(:tags)
101
+ end
102
+
103
+ assert_match(/param is missing or the value is empty: tags/, error.message)
104
+ end
105
+
106
+ def test_parameter_missing_suggestion_with_partial_match
107
+ params = StrongParameters::Core::Parameters.new(
108
+ user_profile: { name: 'John' },
109
+ user_settings: { theme: 'dark' }
110
+ )
111
+
112
+ error = assert_raises(StrongParameters::Core::ParameterMissing) do
113
+ params.require(:user)
114
+ end
115
+
116
+ assert_match(/Did you mean\?/, error.message)
117
+ # Should suggest keys containing 'user'
118
+ assert_match(/user_profile|user_settings/, error.message)
119
+ end
120
+ end
@@ -0,0 +1,14 @@
1
+ source 'http://rubygems.org'
2
+ gemspec :path => "./../.."
3
+
4
+ gem "actionpack", "~> 3.0.0"
5
+ gem "railties", "~> 3.0.0"
6
+ gem "activemodel", "~> 3.0.0"
7
+
8
+ if RUBY_VERSION < '1.9.3'
9
+ gem 'rake', '~> 10.0'
10
+ gem 'i18n', '~> 0.6.11'
11
+ gem 'rdoc', '~> 4.2.2'
12
+ else
13
+ gem 'rake'
14
+ end
@@ -0,0 +1,14 @@
1
+ source 'http://rubygems.org'
2
+ gemspec :path => "./../.."
3
+
4
+ gem "actionpack", "~> 3.1.0"
5
+ gem "railties", "~> 3.1.0"
6
+ gem "activemodel", "~> 3.1.0"
7
+
8
+ if RUBY_VERSION < '1.9.3'
9
+ gem 'rake', '~> 10.0'
10
+ gem 'i18n', '~> 0.6.11'
11
+ gem 'rack-cache', '< 1.3'
12
+ else
13
+ gem 'rake'
14
+ end
@@ -0,0 +1,14 @@
1
+ source 'http://rubygems.org'
2
+ gemspec :path => "./../.."
3
+
4
+ gem "actionpack", "~> 3.2.0"
5
+ gem "railties", "~> 3.2.0"
6
+ gem "activemodel", "~> 3.2.0"
7
+
8
+ if RUBY_VERSION < '1.9.3'
9
+ gem 'rake', '~> 10.0'
10
+ gem 'i18n', '~> 0.6.11'
11
+ gem 'rack-cache', '< 1.3'
12
+ else
13
+ gem 'rake'
14
+ end
@@ -0,0 +1,49 @@
1
+ require 'test_helper'
2
+
3
+ class LogOnUnpermittedParamsTest < Minitest::Test
4
+ def setup
5
+ ActionController::Parameters.action_on_unpermitted_parameters = :log
6
+ end
7
+
8
+ def teardown
9
+ ActionController::Parameters.action_on_unpermitted_parameters = false
10
+ end
11
+
12
+ def test_logs_on_unexpected_params
13
+ params = ActionController::Parameters.new({
14
+ :book => { :pages => 65 },
15
+ :fishing => "Turnips"
16
+ })
17
+
18
+ assert_logged("Unpermitted parameters: fishing") do
19
+ params.permit(:book => [:pages])
20
+ end
21
+ end
22
+
23
+ def test_logs_on_unexpected_nested_params
24
+ params = ActionController::Parameters.new({
25
+ :book => { :pages => 65, :title => "Green Cats and where to find then." }
26
+ })
27
+
28
+ assert_logged("Unpermitted parameters: title") do
29
+ params.permit(:book => [:pages])
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def assert_logged(message)
36
+ old_logger = ActionController::Base.logger
37
+ log = StringIO.new
38
+ ActionController::Base.logger = Logger.new(log)
39
+
40
+ begin
41
+ yield
42
+
43
+ log.rewind
44
+ assert_match message, log.read
45
+ ensure
46
+ ActionController::Base.logger = old_logger
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,294 @@
1
+ require 'test_helper'
2
+
3
+ class MetadataValidationTest < Minitest::Test
4
+ def setup
5
+ ActionController::ParamsRegistry.clear!
6
+
7
+ @basic_params_class = Class.new(ActionController::ApplicationParams) do
8
+ def self.name
9
+ 'BasicParams'
10
+ end
11
+
12
+ allow :name
13
+ allow :email
14
+ end
15
+
16
+ @params_with_metadata = Class.new(ActionController::ApplicationParams) do
17
+ def self.name
18
+ 'ParamsWithMetadata'
19
+ end
20
+
21
+ allow :name
22
+ allow :email
23
+ metadata :ip_address, :user_agent, :session_id
24
+ end
25
+
26
+ ActionController::ParamsRegistry.register('Basic', @basic_params_class)
27
+ ActionController::ParamsRegistry.register('WithMetadata', @params_with_metadata)
28
+ end
29
+
30
+ def teardown
31
+ ActionController::ParamsRegistry.clear!
32
+ end
33
+
34
+ # Test current_user is always allowed without declaration
35
+ def test_current_user_always_allowed_without_declaration
36
+ params = ActionController::Parameters.new(
37
+ basic: {
38
+ name: 'Test User',
39
+ email: 'test@example.com'
40
+ }
41
+ )
42
+
43
+ # Should not raise even though current_user not declared
44
+ permitted = params.require(:basic).transform_params(current_user: Object.new)
45
+
46
+ assert_equal 'Test User', permitted[:name]
47
+ assert_equal 'test@example.com', permitted[:email]
48
+ end
49
+
50
+ # Test metadata_allowed? method
51
+ def test_metadata_allowed_returns_true_for_current_user
52
+ assert @basic_params_class.metadata_allowed?(:current_user)
53
+ assert @basic_params_class.metadata_allowed?('current_user')
54
+ end
55
+
56
+ def test_metadata_allowed_returns_false_for_undeclared_keys
57
+ assert !@basic_params_class.metadata_allowed?(:ip_address)
58
+ assert !@basic_params_class.metadata_allowed?(:random_key)
59
+ end
60
+
61
+ def test_metadata_allowed_returns_true_for_declared_keys
62
+ assert @params_with_metadata.metadata_allowed?(:ip_address)
63
+ assert @params_with_metadata.metadata_allowed?('user_agent')
64
+ assert @params_with_metadata.metadata_allowed?(:session_id)
65
+ end
66
+
67
+ # Test multiple metadata keys
68
+ def test_multiple_metadata_keys_can_be_passed
69
+ params = ActionController::Parameters.new(
70
+ with_metadata: {
71
+ name: 'Test User',
72
+ email: 'test@example.com'
73
+ }
74
+ )
75
+
76
+ # Should not raise with all declared metadata
77
+ permitted = params.require(:with_metadata).transform_params(
78
+ current_user: Object.new,
79
+ ip_address: '192.168.1.1',
80
+ user_agent: 'Mozilla/5.0',
81
+ session_id: 'abc123'
82
+ )
83
+
84
+ assert_equal 'Test User', permitted[:name]
85
+ assert_equal 'test@example.com', permitted[:email]
86
+ end
87
+
88
+ # Test error messages
89
+ def test_error_message_includes_undeclared_key
90
+ params = ActionController::Parameters.new(
91
+ basic: { name: 'Test' }
92
+ )
93
+
94
+ error = assert_raises(ArgumentError) do
95
+ params.require(:basic).transform_params(ip_address: '127.0.0.1')
96
+ end
97
+
98
+ assert_includes error.message, 'ip_address'
99
+ end
100
+
101
+ def test_error_message_includes_params_class_name
102
+ params = ActionController::Parameters.new(
103
+ basic: { name: 'Test' }
104
+ )
105
+
106
+ error = assert_raises(ArgumentError) do
107
+ params.require(:basic).transform_params(ip_address: '127.0.0.1')
108
+ end
109
+
110
+ assert_includes error.message, 'BasicParams'
111
+ end
112
+
113
+ def test_error_message_includes_declaration_hint
114
+ params = ActionController::Parameters.new(
115
+ basic: { name: 'Test' }
116
+ )
117
+
118
+ error = assert_raises(ArgumentError) do
119
+ params.require(:basic).transform_params(ip_address: '127.0.0.1')
120
+ end
121
+
122
+ assert_includes error.message, 'metadata :ip_address'
123
+ end
124
+
125
+ def test_error_message_mentions_current_user_is_always_allowed
126
+ params = ActionController::Parameters.new(
127
+ basic: { name: 'Test' }
128
+ )
129
+
130
+ error = assert_raises(ArgumentError) do
131
+ params.require(:basic).transform_params(ip_address: '127.0.0.1')
132
+ end
133
+
134
+ assert_includes error.message, 'current_user is always allowed'
135
+ end
136
+
137
+ # Test multiple undeclared keys
138
+ def test_multiple_undeclared_keys_all_mentioned_in_error
139
+ params = ActionController::Parameters.new(
140
+ basic: { name: 'Test' }
141
+ )
142
+
143
+ error = assert_raises(ArgumentError) do
144
+ params.require(:basic).transform_params(
145
+ ip_address: '127.0.0.1',
146
+ user_agent: 'Mozilla',
147
+ device_id: 'xyz'
148
+ )
149
+ end
150
+
151
+ assert_includes error.message, 'ip_address'
152
+ assert_includes error.message, 'user_agent'
153
+ assert_includes error.message, 'device_id'
154
+ end
155
+
156
+ # Test metadata doesn't affect the actual parameter filtering
157
+ def test_metadata_keys_dont_affect_parameter_filtering
158
+ params = ActionController::Parameters.new(
159
+ with_metadata: {
160
+ name: 'Test User',
161
+ email: 'test@example.com',
162
+ ip_address: 'should not be in result',
163
+ user_agent: 'should not be in result'
164
+ }
165
+ )
166
+
167
+ permitted = params.require(:with_metadata).transform_params(
168
+ current_user: Object.new,
169
+ ip_address: '192.168.1.1',
170
+ user_agent: 'Mozilla/5.0'
171
+ )
172
+
173
+ assert_equal 'Test User', permitted[:name]
174
+ assert_equal 'test@example.com', permitted[:email]
175
+ # These should not be in the permitted params because they're metadata, not allowed attributes
176
+ assert_nil permitted[:ip_address]
177
+ assert_nil permitted[:user_agent]
178
+ end
179
+
180
+ # Test inheritance of metadata declarations
181
+ def test_metadata_inherited_from_parent
182
+ parent_class = Class.new(ActionController::ApplicationParams) do
183
+ def self.name
184
+ 'ParentParams'
185
+ end
186
+
187
+ allow :name
188
+ metadata :ip_address
189
+ end
190
+
191
+ child_class = Class.new(parent_class) do
192
+ def self.name
193
+ 'ChildParams'
194
+ end
195
+
196
+ allow :email
197
+ metadata :session_id
198
+ end
199
+
200
+ # Child should have parent's metadata
201
+ assert child_class.metadata_allowed?(:ip_address)
202
+ # Child should have its own metadata
203
+ assert child_class.metadata_allowed?(:session_id)
204
+ # Both should allow current_user
205
+ assert child_class.metadata_allowed?(:current_user)
206
+ end
207
+
208
+ # Test empty metadata
209
+ def test_empty_metadata_set_by_default
210
+ assert_equal Set.new, @basic_params_class.allowed_metadata
211
+ end
212
+
213
+ # Test metadata method accepts multiple keys at once
214
+ def test_metadata_method_accepts_multiple_keys
215
+ test_class = Class.new(ActionController::ApplicationParams) do
216
+ metadata :key1, :key2, :key3
217
+ end
218
+
219
+ assert test_class.metadata_allowed?(:key1)
220
+ assert test_class.metadata_allowed?(:key2)
221
+ assert test_class.metadata_allowed?(:key3)
222
+ end
223
+
224
+ # Test metadata with string keys
225
+ def test_metadata_works_with_string_keys
226
+ test_class = Class.new(ActionController::ApplicationParams) do
227
+ metadata 'string_key'
228
+ end
229
+
230
+ assert test_class.metadata_allowed?(:string_key)
231
+ assert test_class.metadata_allowed?('string_key')
232
+ end
233
+
234
+ # Test that action and additional_attrs are not treated as metadata
235
+ def test_action_option_not_treated_as_metadata
236
+ params = ActionController::Parameters.new(
237
+ basic: { name: 'Test' }
238
+ )
239
+
240
+ # Should not raise even though action not declared as metadata
241
+ permitted = params.require(:basic).transform_params(action: :create)
242
+ assert_equal 'Test', permitted[:name]
243
+ end
244
+
245
+ def test_additional_attrs_option_not_treated_as_metadata
246
+ params = ActionController::Parameters.new(
247
+ basic: { name: 'Test' }
248
+ )
249
+
250
+ # Should not raise even though additional_attrs not declared as metadata
251
+ permitted = params.require(:basic).transform_params(additional_attrs: [:other])
252
+ assert_equal 'Test', permitted[:name]
253
+ end
254
+
255
+ def test_action_and_additional_attrs_with_current_user_all_work
256
+ params = ActionController::Parameters.new(
257
+ basic: { name: 'Test' }
258
+ )
259
+
260
+ # Should not raise with all non-metadata options
261
+ permitted = params.require(:basic).transform_params(
262
+ action: :create,
263
+ additional_attrs: [:other],
264
+ current_user: Object.new
265
+ )
266
+ assert_equal 'Test', permitted[:name]
267
+ end
268
+
269
+ # Test nil and empty values
270
+ def test_nil_metadata_values_allowed
271
+ params = ActionController::Parameters.new(
272
+ with_metadata: { name: 'Test' }
273
+ )
274
+
275
+ # Should not raise with nil metadata value
276
+ permitted = params.require(:with_metadata).transform_params(
277
+ current_user: nil,
278
+ ip_address: nil
279
+ )
280
+ assert_equal 'Test', permitted[:name]
281
+ end
282
+
283
+ def test_empty_string_metadata_values_allowed
284
+ params = ActionController::Parameters.new(
285
+ with_metadata: { name: 'Test' }
286
+ )
287
+
288
+ # Should not raise with empty string metadata value
289
+ permitted = params.require(:with_metadata).transform_params(
290
+ ip_address: ''
291
+ )
292
+ assert_equal 'Test', permitted[:name]
293
+ end
294
+ end
@@ -0,0 +1,38 @@
1
+ require 'test_helper'
2
+
3
+ class MultiParameterAttributesTest < Minitest::Test
4
+ def test_permitted_multi_parameter_attribute_keys
5
+ params = ActionController::Parameters.new({
6
+ :book => {
7
+ "shipped_at(1i)" => "2012",
8
+ "shipped_at(2i)" => "3",
9
+ "shipped_at(3i)" => "25",
10
+ "shipped_at(4i)" => "10",
11
+ "shipped_at(5i)" => "15",
12
+ "published_at(1i)" => "1999",
13
+ "published_at(2i)" => "2",
14
+ "published_at(3i)" => "5",
15
+ "price(1)" => "R$",
16
+ "price(2f)" => "2.02"
17
+ }
18
+ })
19
+
20
+ permitted = params.permit :book => [ :shipped_at, :price ]
21
+
22
+ assert permitted.permitted?
23
+
24
+ assert_equal "2012", permitted[:book]["shipped_at(1i)"]
25
+ assert_equal "3", permitted[:book]["shipped_at(2i)"]
26
+ assert_equal "25", permitted[:book]["shipped_at(3i)"]
27
+ assert_equal "10", permitted[:book]["shipped_at(4i)"]
28
+ assert_equal "15", permitted[:book]["shipped_at(5i)"]
29
+
30
+ assert_equal "R$", permitted[:book]["price(1)"]
31
+ assert_equal "2.02", permitted[:book]["price(2f)"]
32
+
33
+ assert_nil permitted[:book]["published_at(1i)"]
34
+ assert_nil permitted[:book]["published_at(2i)"]
35
+ assert_nil permitted[:book]["published_at(3i)"]
36
+ end
37
+ end
38
+