prefab-cloud-ruby 0.23.7 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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