prefab-cloud-ruby 0.20.0 → 0.22.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.
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