prefab-cloud-ruby 0.20.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc.sample +3 -0
  3. data/.github/workflows/ruby.yml +5 -1
  4. data/.gitmodules +3 -0
  5. data/Gemfile +14 -12
  6. data/Gemfile.lock +24 -14
  7. data/README.md +12 -10
  8. data/Rakefile +13 -14
  9. data/VERSION +1 -1
  10. data/lib/prefab/auth_interceptor.rb +2 -1
  11. data/lib/prefab/cancellable_interceptor.rb +8 -7
  12. data/lib/prefab/client.rb +52 -27
  13. data/lib/prefab/config_client.rb +59 -70
  14. data/lib/prefab/config_loader.rb +7 -114
  15. data/lib/prefab/config_resolver.rb +27 -57
  16. data/lib/prefab/config_value_unwrapper.rb +23 -0
  17. data/lib/prefab/criteria_evaluator.rb +96 -0
  18. data/lib/prefab/errors/invalid_api_key_error.rb +1 -1
  19. data/lib/prefab/feature_flag_client.rb +13 -145
  20. data/lib/prefab/internal_logger.rb +7 -6
  21. data/lib/prefab/local_config_parser.rb +110 -0
  22. data/lib/prefab/log_path_collector.rb +98 -0
  23. data/lib/prefab/logger_client.rb +46 -44
  24. data/lib/prefab/murmer3.rb +3 -4
  25. data/lib/prefab/noop_cache.rb +5 -7
  26. data/lib/prefab/noop_stats.rb +2 -3
  27. data/lib/prefab/options.rb +32 -11
  28. data/lib/prefab/ratelimit_client.rb +11 -13
  29. data/lib/prefab/sse_logger.rb +3 -2
  30. data/lib/prefab/weighted_value_resolver.rb +42 -0
  31. data/lib/prefab/yaml_config_parser.rb +32 -0
  32. data/lib/prefab-cloud-ruby.rb +7 -2
  33. data/lib/prefab_pb.rb +70 -43
  34. data/lib/prefab_services_pb.rb +14 -1
  35. data/prefab-cloud-ruby.gemspec +33 -19
  36. data/test/.prefab.unit_tests.config.yaml +3 -2
  37. data/test/integration_test.rb +98 -0
  38. data/test/integration_test_helpers.rb +37 -0
  39. data/test/test_client.rb +56 -31
  40. data/test/test_config_client.rb +21 -20
  41. data/test/test_config_loader.rb +48 -37
  42. data/test/test_config_resolver.rb +312 -135
  43. data/test/test_config_value_unwrapper.rb +83 -0
  44. data/test/test_criteria_evaluator.rb +533 -0
  45. data/test/test_feature_flag_client.rb +35 -347
  46. data/test/test_helper.rb +18 -14
  47. data/test/test_integration.rb +33 -0
  48. data/test/test_local_config_parser.rb +78 -0
  49. data/test/test_log_path_collector.rb +56 -0
  50. data/test/test_logger.rb +52 -51
  51. data/test/test_options.rb +32 -0
  52. data/test/test_weighted_value_resolver.rb +65 -0
  53. metadata +30 -16
  54. data/lib/prefab/config_helper.rb +0 -31
  55. data/run_test_harness_server.sh +0 -8
  56. data/test/harness_server.rb +0 -64
@@ -1,196 +1,370 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  class TestConfigResolver < Minitest::Test
5
-
6
6
  STAGING_ENV_ID = 1
7
7
  PRODUCTION_ENV_ID = 2
8
8
  TEST_ENV_ID = 3
9
+ SEGMENT_KEY = 'segment_key'
10
+ CONFIG_KEY = 'config_key'
11
+ DEFAULT_VALUE = 'default_value'
12
+ IN_SEGMENT_VALUE = 'in_segment_value'
13
+ WRONG_ENV_VALUE = 'wrong_env_value'
14
+ NOT_IN_SEGMENT_VALUE = 'not_in_segment_value'
9
15
 
10
16
  def test_resolution
11
17
  @loader = MockConfigLoader.new
12
18
 
13
19
  loaded_values = {
14
- "key" => { config: Prefab::Config.new(
15
- key: "key",
20
+ 'key' => { config: Prefab::Config.new(
21
+ key: 'key',
16
22
  rows: [
17
23
  Prefab::ConfigRow.new(
18
- value: Prefab::ConfigValue.new(string: "value_no_env_default"),
19
- ),
20
- Prefab::ConfigRow.new(
21
- project_env_id: TEST_ENV_ID,
22
- value: Prefab::ConfigValue.new(string: "value_none"),
23
- ),
24
- Prefab::ConfigRow.new(
25
- project_env_id: TEST_ENV_ID,
26
- namespace: "projectA",
27
- value: Prefab::ConfigValue.new(string: "valueA"),
28
- ),
29
- Prefab::ConfigRow.new(
30
- project_env_id: TEST_ENV_ID,
31
- namespace: "projectB",
32
- value: Prefab::ConfigValue.new(string: "valueB"),
33
- ),
34
- Prefab::ConfigRow.new(
35
- project_env_id: TEST_ENV_ID,
36
- namespace: "projectB.subprojectX",
37
- value: Prefab::ConfigValue.new(string: "projectB.subprojectX"),
24
+ values: [
25
+ Prefab::ConditionalValue.new(
26
+ value: Prefab::ConfigValue.new(string: 'value_no_env_default')
27
+ )
28
+ ]
38
29
  ),
39
30
  Prefab::ConfigRow.new(
40
31
  project_env_id: TEST_ENV_ID,
41
- namespace: "projectB.subprojectY",
42
- value: Prefab::ConfigValue.new(string: "projectB.subprojectY"),
43
- ),
32
+ values: [
33
+ Prefab::ConditionalValue.new(
34
+ criteria: [
35
+ Prefab::Criterion.new(
36
+ operator: Prefab::Criterion::CriterionOperator::HIERARCHICAL_MATCH,
37
+ value_to_match: Prefab::ConfigValue.new(string: 'projectB.subprojectX'),
38
+ property_name: Prefab::CriteriaEvaluator::NAMESPACE_KEY
39
+ )
40
+ ],
41
+ value: Prefab::ConfigValue.new(string: 'projectB.subprojectX')
42
+ ),
43
+ Prefab::ConditionalValue.new(
44
+ criteria: [
45
+ Prefab::Criterion.new(
46
+ operator: Prefab::Criterion::CriterionOperator::HIERARCHICAL_MATCH,
47
+ value_to_match: Prefab::ConfigValue.new(string: 'projectB.subprojectY'),
48
+ property_name: Prefab::CriteriaEvaluator::NAMESPACE_KEY
49
+ )
50
+ ],
51
+ value: Prefab::ConfigValue.new(string: 'projectB.subprojectY')
52
+ ),
53
+ Prefab::ConditionalValue.new(
54
+ criteria: [
55
+ Prefab::Criterion.new(
56
+ operator: Prefab::Criterion::CriterionOperator::HIERARCHICAL_MATCH,
57
+ value_to_match: Prefab::ConfigValue.new(string: 'projectA'),
58
+ property_name: Prefab::CriteriaEvaluator::NAMESPACE_KEY
59
+ )
60
+ ],
61
+ value: Prefab::ConfigValue.new(string: 'valueA')
62
+ ),
63
+ Prefab::ConditionalValue.new(
64
+ criteria: [
65
+ Prefab::Criterion.new(
66
+ operator: Prefab::Criterion::CriterionOperator::HIERARCHICAL_MATCH,
67
+ value_to_match: Prefab::ConfigValue.new(string: 'projectB'),
68
+ property_name: Prefab::CriteriaEvaluator::NAMESPACE_KEY
69
+ )
70
+ ],
71
+ value: Prefab::ConfigValue.new(string: 'valueB')
72
+ ),
73
+ Prefab::ConditionalValue.new(
74
+ criteria: [
75
+ Prefab::Criterion.new(
76
+ operator: Prefab::Criterion::CriterionOperator::HIERARCHICAL_MATCH,
77
+ value_to_match: Prefab::ConfigValue.new(string: 'projectB.subprojectX'),
78
+ property_name: Prefab::CriteriaEvaluator::NAMESPACE_KEY
79
+ )
80
+ ],
81
+ value: Prefab::ConfigValue.new(string: 'projectB.subprojectX')
82
+ ),
83
+ Prefab::ConditionalValue.new(
84
+ criteria: [
85
+ Prefab::Criterion.new(
86
+ operator: Prefab::Criterion::CriterionOperator::HIERARCHICAL_MATCH,
87
+ value_to_match: Prefab::ConfigValue.new(string: 'projectB.subprojectY'),
88
+ property_name: Prefab::CriteriaEvaluator::NAMESPACE_KEY
89
+ )
90
+ ],
91
+ value: Prefab::ConfigValue.new(string: 'projectB.subprojectY')
92
+ ),
93
+ Prefab::ConditionalValue.new(
94
+ value: Prefab::ConfigValue.new(string: 'value_none')
95
+ )
96
+ ]
97
+ )
44
98
 
45
99
  ]
46
100
  ) },
47
- "key2" => { config: Prefab::Config.new(
48
- key: "key2",
101
+ 'key2' => { config: Prefab::Config.new(
102
+ key: 'key2',
49
103
  rows: [
50
- value: Prefab::ConfigValue.new(string: "valueB2"),
104
+ Prefab::ConfigRow.new(
105
+ values: [
106
+ Prefab::ConditionalValue.new(
107
+ value: Prefab::ConfigValue.new(string: 'valueB2')
108
+ )
109
+ ]
110
+ )
51
111
  ]
52
112
  ) }
53
-
54
113
  }
55
114
 
56
115
  @loader.stub :calc_config, loaded_values do
57
-
58
- @resolverA = resolver_for_namespace("", @loader, project_env_id: PRODUCTION_ENV_ID)
59
- assert_equal "value_no_env_default", @resolverA.get("key")
116
+ @resolverA = resolver_for_namespace('', @loader, project_env_id: PRODUCTION_ENV_ID)
117
+ assert_equal 'value_no_env_default', @resolverA.get('key', nil).string
60
118
 
61
119
  ## below here in the test env
62
- @resolverA = resolver_for_namespace("", @loader)
63
- assert_equal "value_none", @resolverA.get("key")
120
+ @resolverA = resolver_for_namespace('', @loader)
121
+ assert_equal 'value_none', @resolverA.get('key', nil).string
64
122
 
65
- @resolverA = resolver_for_namespace("projectA", @loader)
66
- assert_equal "valueA", @resolverA.get("key")
123
+ @resolverA = resolver_for_namespace('projectA', @loader)
124
+ assert_equal 'valueA', @resolverA.get('key', nil).string
67
125
 
68
- @resolverB = resolver_for_namespace("projectB", @loader)
69
- assert_equal "valueB", @resolverB.get("key")
126
+ @resolverB = resolver_for_namespace('projectB', @loader)
127
+ assert_equal 'valueB', @resolverB.get('key', nil).string
70
128
 
71
- @resolverBX = resolver_for_namespace("projectB.subprojectX", @loader)
72
- assert_equal "projectB.subprojectX", @resolverBX.get("key")
129
+ @resolverBX = resolver_for_namespace('projectB.subprojectX', @loader)
130
+ assert_equal 'projectB.subprojectX', @resolverBX.get('key', nil).string
73
131
 
74
- @resolverBX = resolver_for_namespace("projectB.subprojectX", @loader)
75
- assert_equal "valueB2", @resolverBX.get("key2")
132
+ @resolverBX = resolver_for_namespace('projectB.subprojectX', @loader)
133
+ assert_equal 'valueB2', @resolverBX.get('key2', nil).string
76
134
 
77
- @resolverUndefinedSubProject = resolver_for_namespace("projectB.subprojectX.subsubQ", @loader)
78
- assert_equal "projectB.subprojectX", @resolverBX.get("key")
135
+ @resolverUndefinedSubProject = resolver_for_namespace('projectB.subprojectX.subsubQ', @loader)
136
+ assert_equal 'projectB.subprojectX', @resolverUndefinedSubProject.get('key', nil).string
79
137
 
80
- @resolverBX = resolver_for_namespace("projectC", @loader)
81
- assert_equal "value_none", @resolverBX.get("key")
82
- assert_nil @resolverBX.get("key_that_doesnt_exist")
83
- end
84
- end
138
+ @resolverBX = resolver_for_namespace('projectC', @loader)
139
+ assert_equal 'value_none', @resolverBX.get('key', nil).string
85
140
 
86
- def test_starts_with_ns
87
- @loader = MockConfigLoader.new
88
- @loader.stub :calc_config, {} do
89
- resolver = Prefab::ConfigResolver.new(MockBaseClient.new, @loader)
90
- assert_equal [true, 0], resolver.send(:starts_with_ns?, "", "a")
91
- assert_equal [true, 1], resolver.send(:starts_with_ns?, "a", "a")
92
- assert_equal [true, 1], resolver.send(:starts_with_ns?, "a", "a.b")
93
- assert_equal [false, 2], resolver.send(:starts_with_ns?, "a.b", "a")
94
-
95
- assert_equal [true, 1], resolver.send(:starts_with_ns?, "corp", "corp.proj.proja")
96
- assert_equal [true, 2], resolver.send(:starts_with_ns?, "corp.proj", "corp.proj.proja")
97
- assert_equal [true, 3], resolver.send(:starts_with_ns?, "corp.proj.proja", "corp.proj.proja")
98
- assert_equal [false, 3], resolver.send(:starts_with_ns?, "corp.proj.projb", "corp.proj.proja")
99
-
100
- # corp_equal [true, 1:,a:b is not a real delimited namespace[0
101
- assert_equal [false, 1], resolver.send(:starts_with_ns?, "corp", "corp:a:b")
102
- assert_equal [true, 1], resolver.send(:starts_with_ns?, "foo", "foo.baz")
103
- assert_equal [true, 2], resolver.send(:starts_with_ns?, "foo.baz", "foo.baz")
104
- assert_equal [false, 2], resolver.send(:starts_with_ns?, "foo.baz", "foo.bazz")
141
+ assert_nil @resolverBX.get('key_that_doesnt_exist', nil)
105
142
  end
106
143
  end
107
144
 
108
- def test_special_ff_variant_copying
145
+ def test_resolving_in_segment
146
+ segment_config = Prefab::Config.new(
147
+ config_type: Prefab::ConfigType::SEGMENT,
148
+ key: SEGMENT_KEY,
149
+ rows: [
150
+ Prefab::ConfigRow.new(
151
+ values: [
152
+ Prefab::ConditionalValue.new(
153
+ value: Prefab::ConfigValue.new(bool: true),
154
+ criteria: [
155
+ Prefab::Criterion.new(
156
+ operator: Prefab::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
157
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
158
+ property_name: 'email'
159
+ )
160
+ ]
161
+ ),
162
+ Prefab::ConditionalValue.new(value: Prefab::ConfigValue.new(bool: false))
163
+ ]
164
+ )
165
+ ]
166
+ )
109
167
 
110
- @loader = MockConfigLoader.new
111
- loaded_values = {
112
- "ff" => { source: 'test',
113
- config: Prefab::Config.new(
114
- key: "ff",
115
- variants: [
116
- Prefab::FeatureFlagVariant.new(string: "inactive"),
117
- Prefab::FeatureFlagVariant.new(string: "default"),
118
- Prefab::FeatureFlagVariant.new(string: "env"),
119
- ],
120
- rows: [
121
- { value: Prefab::ConfigValue.new(feature_flag: Prefab::FeatureFlag.new(
122
- inactive_variant_idx: 0,
123
- rules: default_ff_rule(1),
124
- )) },
125
- { project_env_id: TEST_ENV_ID,
126
- value: Prefab::ConfigValue.new(feature_flag: Prefab::FeatureFlag.new(
127
- inactive_variant_idx: 0,
128
- rules: default_ff_rule(2),
129
- )) }
130
- ]
168
+ config = Prefab::Config.new(
169
+ key: CONFIG_KEY,
170
+ rows: [
171
+ # wrong env
172
+ Prefab::ConfigRow.new(
173
+ project_env_id: TEST_ENV_ID,
174
+ values: [
175
+ Prefab::ConditionalValue.new(
176
+ criteria: [
177
+ Prefab::Criterion.new(
178
+ operator: Prefab::Criterion::CriterionOperator::IN_SEG,
179
+ value_to_match: Prefab::ConfigValue.new(string: SEGMENT_KEY)
131
180
  )
132
- }
181
+ ],
182
+ value: Prefab::ConfigValue.new(string: WRONG_ENV_VALUE)
183
+ ),
184
+ Prefab::ConditionalValue.new(
185
+ criteria: [],
186
+ value: Prefab::ConfigValue.new(string: DEFAULT_VALUE)
187
+ )
188
+ ]
189
+ ),
190
+
191
+ # correct env
192
+ Prefab::ConfigRow.new(
193
+ project_env_id: PRODUCTION_ENV_ID,
194
+ values: [
195
+ Prefab::ConditionalValue.new(
196
+ criteria: [
197
+ Prefab::Criterion.new(
198
+ operator: Prefab::Criterion::CriterionOperator::IN_SEG,
199
+ value_to_match: Prefab::ConfigValue.new(string: SEGMENT_KEY)
200
+ )
201
+ ],
202
+ value: Prefab::ConfigValue.new(string: IN_SEGMENT_VALUE)
203
+ ),
204
+ Prefab::ConditionalValue.new(
205
+ criteria: [],
206
+ value: Prefab::ConfigValue.new(string: DEFAULT_VALUE)
207
+ )
208
+ ]
209
+ )
210
+ ]
211
+ )
212
+
213
+ loaded_values = {
214
+ SEGMENT_KEY => { config: segment_config },
215
+ CONFIG_KEY => { config: config }
133
216
  }
134
- @loader.stub :calc_config, loaded_values do
135
- resolver = Prefab::ConfigResolver.new(MockBaseClient.new, @loader)
136
- config = resolver.get_config("ff")
137
- assert_equal 3, config.variants.size
138
- assert_equal %w(inactive default env), config.variants.map(&:string)
217
+
218
+ loader = MockConfigLoader.new
219
+
220
+ loader.stub :calc_config, loaded_values do
221
+ options = Prefab::Options.new
222
+ resolver = Prefab::ConfigResolver.new(MockBaseClient.new(options), loader)
223
+ resolver.project_env_id = PRODUCTION_ENV_ID
224
+
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
139
227
  end
140
228
  end
141
229
 
142
- # colons are not allowed in keys, but verify behavior anyway
143
- def test_key_and_namespaces_with_colons
144
- @loader = MockConfigLoader.new
230
+ def test_resolving_not_in_segment
231
+ segment_config = Prefab::Config.new(
232
+ config_type: Prefab::ConfigType::SEGMENT,
233
+ key: SEGMENT_KEY,
234
+ rows: [
235
+ Prefab::ConfigRow.new(
236
+ values: [
237
+ Prefab::ConditionalValue.new(
238
+ value: Prefab::ConfigValue.new(bool: true),
239
+ criteria: [
240
+ Prefab::Criterion.new(
241
+ operator: Prefab::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
242
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
243
+ property_name: 'email'
244
+ )
245
+ ]
246
+ ),
247
+ Prefab::ConditionalValue.new(value: Prefab::ConfigValue.new(bool: false))
248
+ ]
249
+ )
250
+ ]
251
+ )
252
+
253
+ config = Prefab::Config.new(
254
+ key: CONFIG_KEY,
255
+ rows: [
256
+ Prefab::ConfigRow.new(
257
+ values: [
258
+ Prefab::ConditionalValue.new(
259
+ criteria: [
260
+ Prefab::Criterion.new(
261
+ operator: Prefab::Criterion::CriterionOperator::IN_SEG,
262
+ value_to_match: Prefab::ConfigValue.new(string: SEGMENT_KEY)
263
+ )
264
+ ],
265
+ value: Prefab::ConfigValue.new(string: IN_SEGMENT_VALUE)
266
+ ),
267
+ Prefab::ConditionalValue.new(
268
+ criteria: [
269
+ Prefab::Criterion.new(
270
+ operator: Prefab::Criterion::CriterionOperator::NOT_IN_SEG,
271
+ value_to_match: Prefab::ConfigValue.new(string: SEGMENT_KEY)
272
+ )
273
+ ],
274
+ value: Prefab::ConfigValue.new(string: NOT_IN_SEGMENT_VALUE)
275
+ )
276
+ ]
277
+ )
278
+ ]
279
+ )
145
280
 
146
281
  loaded_values = {
147
- "Key:With:Colons" => { config: Prefab::Config.new(
148
- key: "Key:With:Colons",
149
- rows: [Prefab::ConfigRow.new(
150
- value: Prefab::ConfigValue.new(string: "value")
151
- )]
152
- ) },
153
- "proj:apikey" => { config: Prefab::Config.new(
154
- key: "proj:apikey",
155
- rows: [Prefab::ConfigRow.new(
156
- value: Prefab::ConfigValue.new(string: "v2")
157
- )]
158
- ) }
282
+ SEGMENT_KEY => { config: segment_config },
283
+ CONFIG_KEY => { config: config }
159
284
  }
160
285
 
161
- @loader.stub :calc_config, loaded_values do
286
+ loader = MockConfigLoader.new
162
287
 
163
- r = resolver_for_namespace("foo", @loader)
164
- assert_nil r.get("apikey")
288
+ loader.stub :calc_config, loaded_values do
289
+ options = Prefab::Options.new
290
+ resolver = Prefab::ConfigResolver.new(MockBaseClient.new(options), loader)
165
291
 
166
- r = resolver_for_namespace("proj", @loader)
167
- assert_nil r.get("apikey")
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
294
+ end
295
+ end
168
296
 
169
- r = resolver_for_namespace("", @loader)
170
- assert_nil r.get("apikey")
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,
301
+ rows: [
302
+ Prefab::ConfigRow.new(
303
+ values: [
304
+ Prefab::ConditionalValue.new(
305
+ value: Prefab::ConfigValue.new(bool: true),
306
+ criteria: [
307
+ 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'
311
+ )
312
+ ]
313
+ ),
314
+ Prefab::ConditionalValue.new(value: Prefab::ConfigValue.new(bool: false))
315
+ ]
316
+ )
317
+ ]
318
+ )
171
319
 
172
- @resolverKeyWith = resolver_for_namespace("Ket:With", @loader)
173
- assert_nil @resolverKeyWith.get("Colons")
174
- assert_nil @resolverKeyWith.get("With:Colons")
175
- assert_equal "value", @resolverKeyWith.get("Key:With:Colons")
320
+ config = Prefab::Config.new(
321
+ key: CONFIG_KEY,
322
+ rows: [
323
+ Prefab::ConfigRow.new(
324
+ values: [
325
+ Prefab::ConditionalValue.new(
326
+ criteria: [
327
+ 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: [
336
+ Prefab::Criterion.new(
337
+ operator: Prefab::Criterion::CriterionOperator::NOT_IN_SEG,
338
+ value_to_match: Prefab::ConfigValue.new(string: SEGMENT_KEY)
339
+ )
340
+ ],
341
+ value: Prefab::ConfigValue.new(string: NOT_IN_SEGMENT_VALUE)
342
+ )
343
+ ]
344
+ )
345
+ ]
346
+ )
347
+
348
+ loaded_values = {
349
+ SEGMENT_KEY => { config: segment_config },
350
+ CONFIG_KEY => { config: config }
351
+ }
176
352
 
177
- @resolverKeyWithExtra = resolver_for_namespace("Key:With:Extra", @loader)
178
- assert_nil @resolverKeyWithExtra.get("Colons")
179
- assert_nil @resolverKeyWithExtra.get("With:Colons")
180
- assert_equal "value", @resolverKeyWithExtra.get("Key:With:Colons")
353
+ loader = MockConfigLoader.new
181
354
 
182
- @resolverKey = resolver_for_namespace("Key", @loader)
183
- assert_nil @resolverKey.get("With:Colons")
184
- assert_nil @resolverKey.get("Colons")
185
- assert_equal "value", @resolverKey.get("Key:With:Colons")
355
+ loader.stub :calc_config, loaded_values do
356
+ options = Prefab::Options.new
357
+ resolver = Prefab::ConfigResolver.new(MockBaseClient.new(options), loader)
186
358
 
187
- @resolverWithProperlySegmentedNamespace = resolver_for_namespace("Key.With.Extra", @loader)
188
- assert_nil @resolverWithProperlySegmentedNamespace.get("Colons")
189
- assert_nil @resolverWithProperlySegmentedNamespace.get("With:Colons")
190
- assert_equal "value", @resolverWithProperlySegmentedNamespace.get("Key:With:Colons")
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
191
363
  end
192
364
  end
193
365
 
366
+ private
367
+
194
368
  def resolver_for_namespace(namespace, loader, project_env_id: TEST_ENV_ID)
195
369
  options = Prefab::Options.new(
196
370
  namespace: namespace
@@ -201,4 +375,7 @@ class TestConfigResolver < Minitest::Test
201
375
  resolver
202
376
  end
203
377
 
378
+ def string_list(values)
379
+ Prefab::ConfigValue.new(string_list: Prefab::StringList.new(values: values))
380
+ end
204
381
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestConfigValueUnwrapper < Minitest::Test
6
+ CONFIG_KEY = 'config_key'
7
+
8
+ def test_unwrapping_int
9
+ config_value = Prefab::ConfigValue.new(int: 123)
10
+ assert_equal 123, Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, {})
11
+ end
12
+
13
+ def test_unwrapping_string
14
+ config_value = Prefab::ConfigValue.new(string: 'abc')
15
+ assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, {})
16
+ end
17
+
18
+ def test_unwrapping_double
19
+ config_value = Prefab::ConfigValue.new(double: 1.23)
20
+ assert_equal 1.23, Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, {})
21
+ end
22
+
23
+ def test_unwrapping_bool
24
+ config_value = Prefab::ConfigValue.new(bool: true)
25
+ assert_equal true, Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, {})
26
+
27
+ config_value = Prefab::ConfigValue.new(bool: false)
28
+ assert_equal false, Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, {})
29
+ end
30
+
31
+ def test_unwrapping_log_level
32
+ config_value = Prefab::ConfigValue.new(log_level: :INFO)
33
+ assert_equal :INFO, Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, {})
34
+ end
35
+
36
+ def test_unwrapping_string_list
37
+ config_value = Prefab::ConfigValue.new(string_list: Prefab::StringList.new(values: %w[a b c]))
38
+ assert_equal %w[a b c], Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, {})
39
+ end
40
+
41
+ def test_unwrapping_weighted_values
42
+ # single value
43
+ config_value = Prefab::ConfigValue.new(weighted_values: weighted_values([['abc', 1]]))
44
+ assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, {})
45
+
46
+ # multiple values, evenly distributed
47
+ 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'))
52
+
53
+ # multiple values, unevenly distributed
54
+ 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'))
62
+ end
63
+
64
+ private
65
+
66
+ def weighted_values(values_and_weights)
67
+ values = values_and_weights.map do |value, weight|
68
+ weighted_value(value, weight)
69
+ end
70
+
71
+ Prefab::WeightedValues.new(weighted_values: values)
72
+ end
73
+
74
+ def weighted_value(string, weight)
75
+ Prefab::WeightedValue.new(
76
+ value: Prefab::ConfigValue.new(string: string), weight: weight
77
+ )
78
+ end
79
+
80
+ def lookup_properties(lookup_key)
81
+ { Prefab::CriteriaEvaluator::LOOKUP_KEY => lookup_key }
82
+ end
83
+ end