prefab-cloud-ruby 0.23.7 → 0.24.0

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.
@@ -9,10 +9,19 @@ class TestConfigResolver < Minitest::Test
9
9
  SEGMENT_KEY = 'segment_key'
10
10
  CONFIG_KEY = 'config_key'
11
11
  DEFAULT_VALUE = 'default_value'
12
+ DESIRED_VALUE = 'desired_value'
12
13
  IN_SEGMENT_VALUE = 'in_segment_value'
13
14
  WRONG_ENV_VALUE = 'wrong_env_value'
14
15
  NOT_IN_SEGMENT_VALUE = 'not_in_segment_value'
15
16
 
17
+ DEFAULT_ROW = Prefab::ConfigRow.new(
18
+ values: [
19
+ Prefab::ConditionalValue.new(
20
+ value: Prefab::ConfigValue.new(string: DEFAULT_VALUE)
21
+ )
22
+ ]
23
+ )
24
+
16
25
  def test_resolution
17
26
  @loader = MockConfigLoader.new
18
27
 
@@ -20,13 +29,7 @@ class TestConfigResolver < Minitest::Test
20
29
  'key' => { config: Prefab::Config.new(
21
30
  key: 'key',
22
31
  rows: [
23
- Prefab::ConfigRow.new(
24
- values: [
25
- Prefab::ConditionalValue.new(
26
- value: Prefab::ConfigValue.new(string: 'value_no_env_default')
27
- )
28
- ]
29
- ),
32
+ DEFAULT_ROW,
30
33
  Prefab::ConfigRow.new(
31
34
  project_env_id: TEST_ENV_ID,
32
35
  values: [
@@ -114,31 +117,49 @@ class TestConfigResolver < Minitest::Test
114
117
 
115
118
  @loader.stub :calc_config, loaded_values do
116
119
  @resolverA = resolver_for_namespace('', @loader, project_env_id: PRODUCTION_ENV_ID)
117
- assert_equal 'value_no_env_default', @resolverA.get('key', nil).string
120
+ assert_equal_context_and_jit DEFAULT_VALUE, @resolverA, 'key', {}, :string
118
121
 
119
122
  ## below here in the test env
120
123
  @resolverA = resolver_for_namespace('', @loader)
121
- assert_equal 'value_none', @resolverA.get('key', nil).string
124
+ assert_equal_context_and_jit 'value_none', @resolverA, 'key', {}, :string
122
125
 
123
126
  @resolverA = resolver_for_namespace('projectA', @loader)
124
- assert_equal 'valueA', @resolverA.get('key', nil).string
127
+ assert_equal_context_and_jit 'valueA', @resolverA, 'key', {}, :string
125
128
 
126
129
  @resolverB = resolver_for_namespace('projectB', @loader)
127
- assert_equal 'valueB', @resolverB.get('key', nil).string
130
+ assert_equal_context_and_jit 'valueB', @resolverB, 'key', {}, :string
128
131
 
129
132
  @resolverBX = resolver_for_namespace('projectB.subprojectX', @loader)
130
- assert_equal 'projectB.subprojectX', @resolverBX.get('key', nil).string
133
+ assert_equal_context_and_jit 'projectB.subprojectX', @resolverBX, 'key', {}, :string
131
134
 
132
135
  @resolverBX = resolver_for_namespace('projectB.subprojectX', @loader)
133
- assert_equal 'valueB2', @resolverBX.get('key2', nil).string
136
+ assert_equal_context_and_jit 'valueB2', @resolverBX, 'key2', {}, :string
134
137
 
135
- @resolverUndefinedSubProject = resolver_for_namespace('projectB.subprojectX.subsubQ', @loader)
136
- assert_equal 'projectB.subprojectX', @resolverUndefinedSubProject.get('key', nil).string
138
+ @resolverUndefinedSubProject = resolver_for_namespace('projectB.subprojectX.subsubQ',
139
+ @loader)
140
+ assert_equal_context_and_jit 'projectB.subprojectX', @resolverUndefinedSubProject, 'key',
141
+ {}, :string
137
142
 
138
143
  @resolverBX = resolver_for_namespace('projectC', @loader)
139
- assert_equal 'value_none', @resolverBX.get('key', nil).string
144
+ assert_equal_context_and_jit 'value_none', @resolverBX, 'key', {}, :string
140
145
 
141
146
  assert_nil @resolverBX.get('key_that_doesnt_exist', nil)
147
+
148
+ assert_equal @resolverBX.to_s.strip.split("\n").map(&:strip), [
149
+ 'key | value_none | String | Match: | Source:',
150
+ 'key2 | valueB2 | String | Match: | Source:'
151
+ ]
152
+
153
+ assert_equal @resolverBX.presenter.to_h, {
154
+ 'key' => Prefab::ResolvedConfigPresenter::ConfigRow.new('key', 'value_none', nil, nil),
155
+ 'key2' => Prefab::ResolvedConfigPresenter::ConfigRow.new('key2', 'valueB2', nil, nil)
156
+ }
157
+
158
+ resolved_lines = []
159
+ @resolverBX.presenter.each do |key, row|
160
+ resolved_lines << [key, row.value]
161
+ end
162
+ assert_equal resolved_lines, [%w[key value_none], %w[key2 valueB2]]
142
163
  end
143
164
  end
144
165
 
@@ -155,7 +176,7 @@ class TestConfigResolver < Minitest::Test
155
176
  Prefab::Criterion.new(
156
177
  operator: Prefab::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
157
178
  value_to_match: string_list(['hotmail.com', 'gmail.com']),
158
- property_name: 'email'
179
+ property_name: 'user.email'
159
180
  )
160
181
  ]
161
182
  ),
@@ -222,8 +243,10 @@ class TestConfigResolver < Minitest::Test
222
243
  resolver = Prefab::ConfigResolver.new(MockBaseClient.new(options), loader)
223
244
  resolver.project_env_id = PRODUCTION_ENV_ID
224
245
 
225
- assert_equal DEFAULT_VALUE, resolver.get(CONFIG_KEY, nil, { email: 'test@something-else.com' }).string
226
- assert_equal IN_SEGMENT_VALUE, resolver.get(CONFIG_KEY, nil, { email: 'test@hotmail.com' }).string
246
+ assert_equal_context_and_jit DEFAULT_VALUE, resolver, CONFIG_KEY,
247
+ { user: { email: 'test@something-else.com' } }, :string
248
+ assert_equal_context_and_jit IN_SEGMENT_VALUE, resolver, CONFIG_KEY,
249
+ { user: { email: 'test@hotmail.com' } }, :string
227
250
  end
228
251
  end
229
252
 
@@ -240,7 +263,7 @@ class TestConfigResolver < Minitest::Test
240
263
  Prefab::Criterion.new(
241
264
  operator: Prefab::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
242
265
  value_to_match: string_list(['hotmail.com', 'gmail.com']),
243
- property_name: 'email'
266
+ property_name: 'user.email'
244
267
  )
245
268
  ]
246
269
  ),
@@ -289,77 +312,98 @@ class TestConfigResolver < Minitest::Test
289
312
  options = Prefab::Options.new
290
313
  resolver = Prefab::ConfigResolver.new(MockBaseClient.new(options), loader)
291
314
 
292
- assert_equal IN_SEGMENT_VALUE, resolver.get(CONFIG_KEY, nil, { email: 'test@hotmail.com' }).string
293
- assert_equal NOT_IN_SEGMENT_VALUE, resolver.get(CONFIG_KEY, nil, { email: 'test@something-else.com' }).string
315
+ assert_equal_context_and_jit IN_SEGMENT_VALUE, resolver, CONFIG_KEY, { user: { email: 'test@hotmail.com' } },
316
+ :string
317
+ assert_equal_context_and_jit NOT_IN_SEGMENT_VALUE, resolver, CONFIG_KEY, { user: { email: 'test@something-else.com' } },
318
+ :string
294
319
  end
295
320
  end
296
321
 
297
- def test_resolving_in_segment_with_lookup_key
298
- segment_config = Prefab::Config.new(
299
- config_type: Prefab::ConfigType::SEGMENT,
300
- key: SEGMENT_KEY,
322
+ def test_jit_context_merges_with_existing_context
323
+ config = Prefab::Config.new(
324
+ key: CONFIG_KEY,
301
325
  rows: [
326
+ DEFAULT_ROW,
302
327
  Prefab::ConfigRow.new(
328
+ project_env_id: TEST_ENV_ID,
303
329
  values: [
304
330
  Prefab::ConditionalValue.new(
305
- value: Prefab::ConfigValue.new(bool: true),
306
331
  criteria: [
307
332
  Prefab::Criterion.new(
308
- operator: Prefab::Criterion::CriterionOperator::LOOKUP_KEY_IN,
309
- value_to_match: string_list(['user:1234', 'user:4567']),
310
- property_name: 'LOOKUP'
333
+ operator: Prefab::Criterion::CriterionOperator::PROP_IS_ONE_OF,
334
+ value_to_match: string_list(%w[pro advanced]),
335
+ property_name: 'team.plan'
336
+ ),
337
+
338
+ Prefab::Criterion.new(
339
+ operator: Prefab::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
340
+ value_to_match: string_list(%w[@example.com]),
341
+ property_name: 'user.email'
311
342
  )
312
- ]
313
- ),
314
- Prefab::ConditionalValue.new(value: Prefab::ConfigValue.new(bool: false))
343
+ ],
344
+ value: Prefab::ConfigValue.new(string: DESIRED_VALUE)
345
+ )
315
346
  ]
316
347
  )
317
348
  ]
318
349
  )
319
350
 
351
+ loader = MockConfigLoader.new
352
+
353
+ loader.stub :calc_config, { CONFIG_KEY => { config: config } } do
354
+ options = Prefab::Options.new
355
+ resolver = Prefab::ConfigResolver.new(MockBaseClient.new(options), loader)
356
+ resolver.project_env_id = TEST_ENV_ID
357
+
358
+ Prefab::Context.with_context({ user: { email: 'test@example.com' } }) do
359
+ assert_equal DEFAULT_VALUE, resolver.get(CONFIG_KEY).string
360
+ assert_equal DEFAULT_VALUE, resolver.get(CONFIG_KEY, { team: { plan: 'freebie' } }).string
361
+ assert_equal DESIRED_VALUE, resolver.get(CONFIG_KEY, { team: { plan: 'pro' } }).string
362
+ end
363
+ end
364
+ end
365
+
366
+ def test_jit_can_clobber_existing_context
320
367
  config = Prefab::Config.new(
321
368
  key: CONFIG_KEY,
322
369
  rows: [
370
+ DEFAULT_ROW,
323
371
  Prefab::ConfigRow.new(
372
+ project_env_id: TEST_ENV_ID,
324
373
  values: [
325
374
  Prefab::ConditionalValue.new(
326
375
  criteria: [
327
376
  Prefab::Criterion.new(
328
- operator: Prefab::Criterion::CriterionOperator::IN_SEG,
329
- value_to_match: Prefab::ConfigValue.new(string: SEGMENT_KEY)
330
- )
331
- ],
332
- value: Prefab::ConfigValue.new(string: IN_SEGMENT_VALUE)
333
- ),
334
- Prefab::ConditionalValue.new(
335
- criteria: [
377
+ operator: Prefab::Criterion::CriterionOperator::PROP_IS_ONE_OF,
378
+ value_to_match: string_list(%w[pro advanced]),
379
+ property_name: 'team.plan'
380
+ ),
381
+
336
382
  Prefab::Criterion.new(
337
- operator: Prefab::Criterion::CriterionOperator::NOT_IN_SEG,
338
- value_to_match: Prefab::ConfigValue.new(string: SEGMENT_KEY)
383
+ operator: Prefab::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
384
+ value_to_match: string_list(%w[@example.com]),
385
+ property_name: 'user.email'
339
386
  )
340
387
  ],
341
- value: Prefab::ConfigValue.new(string: NOT_IN_SEGMENT_VALUE)
388
+ value: Prefab::ConfigValue.new(string: DESIRED_VALUE)
342
389
  )
343
390
  ]
344
391
  )
345
392
  ]
346
393
  )
347
394
 
348
- loaded_values = {
349
- SEGMENT_KEY => { config: segment_config },
350
- CONFIG_KEY => { config: config }
351
- }
352
-
353
395
  loader = MockConfigLoader.new
354
396
 
355
- loader.stub :calc_config, loaded_values do
397
+ loader.stub :calc_config, { CONFIG_KEY => { config: config } } do
356
398
  options = Prefab::Options.new
357
399
  resolver = Prefab::ConfigResolver.new(MockBaseClient.new(options), loader)
400
+ resolver.project_env_id = TEST_ENV_ID
358
401
 
359
- assert_equal IN_SEGMENT_VALUE, resolver.get(CONFIG_KEY, 'user:1234', {}).string
360
- assert_equal IN_SEGMENT_VALUE, resolver.get(CONFIG_KEY, 'user:4567', {}).string
361
- assert_equal NOT_IN_SEGMENT_VALUE, resolver.get(CONFIG_KEY, nil, {}).string
362
- assert_equal NOT_IN_SEGMENT_VALUE, resolver.get(CONFIG_KEY, 'user:9999', {}).string
402
+ Prefab::Context.with_context({ user: { email: 'test@hotmail.com' }, team: { plan: 'pro' } }) do
403
+ assert_equal DEFAULT_VALUE, resolver.get(CONFIG_KEY).string
404
+ assert_equal DESIRED_VALUE, resolver.get(CONFIG_KEY, { user: { email: 'test@example.com' } }).string
405
+ assert_equal DEFAULT_VALUE, resolver.get(CONFIG_KEY, { team: { plan: 'freebie' } }).string
406
+ end
363
407
  end
364
408
  end
365
409
 
@@ -374,4 +418,12 @@ class TestConfigResolver < Minitest::Test
374
418
  resolver.update
375
419
  resolver
376
420
  end
421
+
422
+ def assert_equal_context_and_jit(expected_value, resolver, key, properties, type)
423
+ assert_equal expected_value, resolver.get(key, properties).send(type)
424
+
425
+ Prefab::Context.with_context(properties) do
426
+ assert_equal expected_value, resolver.get(key).send(type)
427
+ end
428
+ end
377
429
  end
@@ -41,34 +41,34 @@ class TestConfigValueUnwrapper < Minitest::Test
41
41
  def test_unwrapping_weighted_values
42
42
  # single value
43
43
  config_value = Prefab::ConfigValue.new(weighted_values: weighted_values([['abc', 1]]))
44
+
44
45
  assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, {})
45
46
 
46
47
  # multiple values, evenly distributed
47
48
  config_value = Prefab::ConfigValue.new(weighted_values: weighted_values([['abc', 1], ['def', 1], ['ghi', 1]]))
48
- assert_equal 'ghi', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, lookup_properties('user:123'))
49
- assert_equal 'ghi', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, lookup_properties('user:456'))
50
- assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, lookup_properties('user:789'))
51
- assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, lookup_properties('user:012'))
49
+ assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:000'))
50
+ assert_equal 'ghi', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:456'))
51
+ assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:789'))
52
+ assert_equal 'ghi', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:888'))
52
53
 
53
54
  # multiple values, unevenly distributed
54
55
  config_value = Prefab::ConfigValue.new(weighted_values: weighted_values([['abc', 1], ['def', 99], ['ghi', 1]]))
55
- assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, lookup_properties('user:123'))
56
- assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, lookup_properties('user:456'))
57
- assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, lookup_properties('user:789'))
58
- assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, lookup_properties('user:012'))
59
-
60
- assert_equal 'ghi', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, lookup_properties('user:103'))
61
- assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, lookup_properties('user:119'))
56
+ assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:123'))
57
+ assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:456'))
58
+ assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:789'))
59
+ assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:012'))
60
+ assert_equal 'ghi', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:428'))
61
+ assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:548'))
62
62
  end
63
63
 
64
64
  private
65
65
 
66
- def weighted_values(values_and_weights)
66
+ def weighted_values(values_and_weights, hash_by_property_name: 'user.key')
67
67
  values = values_and_weights.map do |value, weight|
68
68
  weighted_value(value, weight)
69
69
  end
70
70
 
71
- Prefab::WeightedValues.new(weighted_values: values)
71
+ Prefab::WeightedValues.new(weighted_values: values, hash_by_property_name: hash_by_property_name)
72
72
  end
73
73
 
74
74
  def weighted_value(string, weight)
@@ -77,7 +77,7 @@ class TestConfigValueUnwrapper < Minitest::Test
77
77
  )
78
78
  end
79
79
 
80
- def lookup_properties(lookup_key)
81
- { Prefab::CriteriaEvaluator::LOOKUP_KEY => lookup_key }
80
+ def context_with_key(key)
81
+ Prefab::Context.new(user: { key: key })
82
82
  end
83
83
  end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestContext < Minitest::Test
6
+ EXAMPLE_PROPERTIES = { user: { key: 'some-user-key', name: 'Ted' }, team: { key: 'abc', plan: 'pro' } }.freeze
7
+
8
+ def setup
9
+ Prefab::Context.current = nil
10
+ end
11
+
12
+ def test_initialize_with_empty_context
13
+ context = Prefab::Context.new({})
14
+ assert_empty context.contexts
15
+ end
16
+
17
+ def test_initialize_with_named_context
18
+ named_context = Prefab::Context::NamedContext.new('test', foo: 'bar')
19
+ context = Prefab::Context.new(named_context)
20
+ assert_equal 1, context.contexts.size
21
+ assert_equal named_context, context.contexts['test']
22
+ end
23
+
24
+ def test_initialize_with_hash
25
+ context = Prefab::Context.new(test: { foo: 'bar' })
26
+ assert_equal 1, context.contexts.size
27
+ assert_equal 'bar', context.contexts['test'].get('foo')
28
+ end
29
+
30
+ def test_initialize_with_multiple_hashes
31
+ context = Prefab::Context.new(test: { foo: 'bar' }, other: { foo: 'baz' })
32
+ assert_equal 2, context.contexts.size
33
+ assert_equal 'bar', context.contexts['test'].get('foo')
34
+ assert_equal 'baz', context.contexts['other'].get('foo')
35
+ end
36
+
37
+ def test_initialize_with_invalid_hash
38
+ _, err = capture_io do
39
+ Prefab::Context.new({ foo: 'bar', baz: 'qux' })
40
+ end
41
+
42
+ assert_match '[DEPRECATION] Prefab contexts should be a hash with a key of the context name and a value of a hash',
43
+ err
44
+ end
45
+
46
+ def test_initialize_with_invalid_argument
47
+ assert_raises(ArgumentError) { Prefab::Context.new([]) }
48
+ end
49
+
50
+ def test_current
51
+ context = Prefab::Context.current
52
+ assert_instance_of Prefab::Context, context
53
+ assert_empty context.to_h
54
+ end
55
+
56
+ def test_current_set
57
+ context = Prefab::Context.new(EXAMPLE_PROPERTIES)
58
+ Prefab::Context.current = context
59
+ assert_instance_of Prefab::Context, context
60
+ assert_equal stringify(EXAMPLE_PROPERTIES), context.to_h
61
+ end
62
+
63
+ def test_merge_with_current
64
+ context = Prefab::Context.new(EXAMPLE_PROPERTIES)
65
+ Prefab::Context.current = context
66
+ assert_equal stringify(EXAMPLE_PROPERTIES), context.to_h
67
+
68
+ new_context = Prefab::Context.merge_with_current({ user: { key: 'brand-new', other: 'different' },
69
+ address: { city: 'New York' } })
70
+ assert_equal stringify({
71
+ # Note that the user's `name` from the original
72
+ # context is not included. This is because we don't _merge_ the new
73
+ # properties if they collide with an existing context name. We _replace_
74
+ # them.
75
+ user: { key: 'brand-new', other: 'different' },
76
+ team: EXAMPLE_PROPERTIES[:team],
77
+ address: { city: 'New York' }
78
+ }),
79
+ new_context.to_h
80
+
81
+ # the original/current context is unchanged
82
+ assert_equal stringify(EXAMPLE_PROPERTIES), Prefab::Context.current.to_h
83
+ end
84
+
85
+ def test_with_context
86
+ Prefab::Context.with_context(EXAMPLE_PROPERTIES) do
87
+ context = Prefab::Context.current
88
+ assert_equal(stringify(EXAMPLE_PROPERTIES), context.to_h)
89
+ assert_equal('some-user-key', context['user.key'])
90
+ end
91
+ end
92
+
93
+ def test_with_context_nesting
94
+ Prefab::Context.with_context(EXAMPLE_PROPERTIES) do
95
+ Prefab::Context.with_context({ user: { key: 'abc', other: 'different' } }) do
96
+ context = Prefab::Context.current
97
+ assert_equal({ 'user' => { 'key' => 'abc', 'other' => 'different' } }, context.to_h)
98
+ end
99
+
100
+ context = Prefab::Context.current
101
+ assert_equal(stringify(EXAMPLE_PROPERTIES), context.to_h)
102
+ end
103
+ end
104
+
105
+ def test_setting
106
+ context = Prefab::Context.new({})
107
+ context.set('user', { key: 'value' })
108
+ context[:other] = { key: 'different', something: 'other' }
109
+ assert_equal(stringify({ user: { key: 'value' }, other: { key: 'different', something: 'other' } }), context.to_h)
110
+ end
111
+
112
+ def test_getting
113
+ context = Prefab::Context.new(EXAMPLE_PROPERTIES)
114
+ assert_equal('some-user-key', context.get('user.key'))
115
+ assert_equal('some-user-key', context['user.key'])
116
+ assert_equal('pro', context.get('team.plan'))
117
+ assert_equal('pro', context['team.plan'])
118
+ end
119
+
120
+ def test_dot_notation_getting
121
+ context = Prefab::Context.new({ 'user' => { 'key' => 'value' } })
122
+ assert_equal('value', context.get('user.key'))
123
+ assert_equal('value', context['user.key'])
124
+ end
125
+
126
+ def test_dot_notation_getting_with_symbols
127
+ context = Prefab::Context.new({ user: { key: 'value' } })
128
+ assert_equal('value', context.get('user.key'))
129
+ assert_equal('value', context['user.key'])
130
+ end
131
+
132
+ def test_merge
133
+ context = Prefab::Context.new(EXAMPLE_PROPERTIES)
134
+ context.merge!(:other, { key: 'different' })
135
+ assert_equal(stringify(EXAMPLE_PROPERTIES.merge(other: { key: 'different' })), context.to_h)
136
+ end
137
+
138
+ def test_clear
139
+ context = Prefab::Context.new(EXAMPLE_PROPERTIES)
140
+ context.clear
141
+
142
+ assert_empty context.to_h
143
+ end
144
+
145
+ private
146
+
147
+ def stringify(hash)
148
+ hash.map { |k, v| [k.to_s, stringify_keys(v)] }.to_h
149
+ end
150
+
151
+ def stringify_keys(value)
152
+ if value.is_a?(Hash)
153
+ value.transform_keys(&:to_s)
154
+ else
155
+ value
156
+ end
157
+ end
158
+ end