prefab-cloud-ruby 0.19.0 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc.sample +3 -0
  3. data/.github/workflows/ruby.yml +4 -0
  4. data/.gitmodules +3 -0
  5. data/Gemfile +12 -12
  6. data/Gemfile.lock +16 -14
  7. data/README.md +1 -1
  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 +33 -24
  13. data/lib/prefab/config_client.rb +55 -66
  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 +6 -5
  21. data/lib/prefab/local_config_parser.rb +110 -0
  22. data/lib/prefab/logger_client.rb +30 -36
  23. data/lib/prefab/murmer3.rb +3 -4
  24. data/lib/prefab/noop_cache.rb +5 -7
  25. data/lib/prefab/noop_stats.rb +2 -3
  26. data/lib/prefab/options.rb +11 -9
  27. data/lib/prefab/ratelimit_client.rb +11 -13
  28. data/lib/prefab/sse_logger.rb +3 -2
  29. data/lib/prefab/weighted_value_resolver.rb +42 -0
  30. data/lib/prefab/yaml_config_parser.rb +32 -0
  31. data/lib/prefab-cloud-ruby.rb +7 -2
  32. data/lib/prefab_pb.rb +49 -43
  33. data/lib/prefab_services_pb.rb +0 -1
  34. data/prefab-cloud-ruby.gemspec +28 -19
  35. data/test/.prefab.unit_tests.config.yaml +3 -2
  36. data/test/integration_test.rb +98 -0
  37. data/test/integration_test_helpers.rb +37 -0
  38. data/test/test_client.rb +32 -31
  39. data/test/test_config_client.rb +21 -20
  40. data/test/test_config_loader.rb +48 -37
  41. data/test/test_config_resolver.rb +312 -135
  42. data/test/test_config_value_unwrapper.rb +83 -0
  43. data/test/test_criteria_evaluator.rb +533 -0
  44. data/test/test_feature_flag_client.rb +35 -347
  45. data/test/test_helper.rb +18 -14
  46. data/test/test_integration.rb +33 -0
  47. data/test/test_local_config_parser.rb +78 -0
  48. data/test/test_logger.rb +90 -42
  49. data/test/test_weighted_value_resolver.rb +65 -0
  50. metadata +24 -27
  51. data/lib/prefab/config_helper.rb +0 -31
  52. data/run_test_harness_server.sh +0 -8
  53. data/test/harness_server.rb +0 -64
@@ -1,366 +1,54 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  class TestFeatureFlagClient < Minitest::Test
6
+ DEFAULT = 'default'
5
7
 
6
- def setup
7
- super
8
- @mock_base_client = MockBaseClient.new
9
- @client = Prefab::FeatureFlagClient.new(@mock_base_client)
10
- Prefab::FeatureFlagClient.send(:public, :is_on?) #publicize for testing
11
- Prefab::FeatureFlagClient.send(:public, :segment_match?) #publicize for testing
12
- Prefab::FeatureFlagClient.send(:public, :get_variant) #publicize for testing
13
- end
14
-
15
- def test_pct
16
- feature = "FlagName"
17
-
18
- variants = [
19
- Prefab::FeatureFlagVariant.new(bool: false),
20
- Prefab::FeatureFlagVariant.new(bool: true)
21
- ]
22
- flag = Prefab::FeatureFlag.new(
23
- active: true,
24
- inactive_variant_idx: 1,
25
- rules: [
26
- Prefab::Rule.new(
27
- criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
28
- variant_weights: [
29
- Prefab::VariantWeight.new(weight: 86,
30
- variant_idx: 2), #true
31
- Prefab::VariantWeight.new(weight: 14,
32
- variant_idx: 1), #false
33
- ]
34
- )
35
- ]
36
- )
37
- # weights above chosen to be 86% in variant_idx 2. and 14% in variant_idx 1.
38
- # since hashes high is 86.32 > 86 it just falls outside the 86% range and gets false
39
-
40
- # "FlagNamevery high hash" hashes to 88.8602812% through dist
41
- assert_equal false,
42
- evaluate(feature, "very high hash", [], flag, variants)
43
- # "FlagNamehashes low" hashes to 42.7934% through dist
44
- assert_equal true,
45
- evaluate(feature, "hashes low", [], flag, variants)
46
-
47
- end
48
-
49
- def test_basic_active_inactive
50
- feature = "FlagName"
51
- variants = [
52
- Prefab::FeatureFlagVariant.new(bool: false),
53
- Prefab::FeatureFlagVariant.new(bool: true)
54
- ]
55
- flag = Prefab::FeatureFlag.new(
56
- active: true,
57
- inactive_variant_idx: 1,
58
- rules: default_ff_rule(2)
59
- )
60
- assert_equal true,
61
- evaluate(feature, "hashes high", [], flag, variants)
62
- assert_equal true,
63
- evaluate(feature, "hashes low", [], flag, variants)
8
+ def test_feature_is_on
9
+ ff_client = new_client
64
10
 
65
- variants = [
66
- Prefab::FeatureFlagVariant.new(bool: false),
67
- Prefab::FeatureFlagVariant.new(bool: true)
68
- ]
69
- flag = Prefab::FeatureFlag.new(
70
- active: false,
71
- inactive_variant_idx: 1,
72
- rules: default_ff_rule(2)
73
- )
74
- assert_equal false,
75
- evaluate(feature, "hashes high", [], flag, variants)
76
- assert_equal false,
77
- evaluate(feature, "hashes low", [], flag, variants)
11
+ assert_equal false, ff_client.feature_is_on?('something-that-does-not-exist')
12
+ assert_equal false, ff_client.feature_is_on?('disabled_flag')
13
+ assert_equal true, ff_client.feature_is_on?('enabled_flag')
14
+ assert_equal false, ff_client.feature_is_on?('flag_with_a_value')
78
15
  end
79
16
 
80
- def test_inclusion_rule
81
- feature = "FlagName"
82
- variants = [
83
- Prefab::FeatureFlagVariant.new(string: "inactive"),
84
- Prefab::FeatureFlagVariant.new(string: "rule target"),
85
- Prefab::FeatureFlagVariant.new(string: "default"),
86
- ]
87
- flag = Prefab::FeatureFlag.new(
88
- active: true,
89
- inactive_variant_idx: 1,
90
- rules: [
91
- Prefab::Rule.new(
92
- variant_weights: [
93
- Prefab::VariantWeight.new(weight: 1000,
94
- variant_idx: 2)
95
- ],
96
- criteria: Prefab::Criteria.new(
97
- operator: "LOOKUP_KEY_IN",
98
- values: ["user:1"]
99
- )
100
- ),
101
- Prefab::Rule.new(
102
- criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
103
- variant_weights: [
104
- Prefab::VariantWeight.new(weight: 1000,
105
- variant_idx: 3)
106
- ]
107
- )
108
-
109
- ],
110
- )
111
-
112
- assert_equal "rule target",
113
- evaluate(feature, "user:1", [], flag, variants)
114
- assert_equal "default",
115
- evaluate(feature, "user:2", [], flag, variants)
17
+ def test_feature_is_on_for
18
+ ff_client = new_client
116
19
 
20
+ assert_equal false, ff_client.feature_is_on_for?('something-that-does-not-exist', 'irrelevant')
21
+ assert_equal false, ff_client.feature_is_on_for?('in_lookup_key', 'not-included')
22
+ assert_equal true, ff_client.feature_is_on_for?('in_lookup_key', 'abc123')
23
+ assert_equal true, ff_client.feature_is_on_for?('in_lookup_key', 'xyz987')
117
24
  end
118
25
 
119
- def test_property_is_one_of
120
- feature = "FlagName"
121
- variants = [
122
- Prefab::FeatureFlagVariant.new(string: "inactive"),
123
- Prefab::FeatureFlagVariant.new(string: "rule target"),
124
- Prefab::FeatureFlagVariant.new(string: "default"),
125
- ]
126
- flag = Prefab::FeatureFlag.new(
127
- active: true,
128
- inactive_variant_idx: 1,
129
- rules: [
130
- Prefab::Rule.new(
131
- variant_weights: [
132
- Prefab::VariantWeight.new(weight: 1000,
133
- variant_idx: 2)
134
- ],
135
- criteria: Prefab::Criteria.new(
136
- operator: "PROP_IS_ONE_OF",
137
- values: ["a@example.com", "b@example.com"],
138
- property: "email"
139
- )
140
- ),
141
- Prefab::Rule.new(
142
- criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
143
- variant_weights: [
144
- Prefab::VariantWeight.new(weight: 1000,
145
- variant_idx: 3)
146
- ]
147
- )
148
-
149
- ],
150
- )
151
-
152
- assert_equal "default",
153
- evaluate(feature, "user:1", { email: "not@example.com" }, flag, variants)
154
- assert_equal "default",
155
- evaluate(feature, "user:2", {}, flag, variants)
156
- assert_equal "rule target",
157
- evaluate(feature, "user:2", { email: "b@example.com" }, flag, variants)
158
- assert_equal "rule target",
159
- evaluate(feature, "user:2", { "email" => "b@example.com" }, flag, variants)
26
+ def test_get
27
+ ff_client = new_client
160
28
 
161
- end
29
+ # No default
30
+ assert_equal false, ff_client.get('something-that-does-not-exist')
31
+ assert_equal false, ff_client.get('disabled_flag')
32
+ assert_equal true, ff_client.get('enabled_flag')
33
+ assert_equal 'all-features', ff_client.get('flag_with_a_value')
162
34
 
163
- def test_segment_match?
164
- segment = Prefab::Segment.new(
165
- criterion: [
166
- Prefab::Criteria.new(
167
- operator: "PROP_IS_ONE_OF",
168
- values: ["a@example.com", "b@example.com"],
169
- property: "email"
170
- ),
171
- Prefab::Criteria.new(
172
- operator: "LOOKUP_KEY_IN",
173
- values: ["user:2"]
174
- )
175
- ]
176
- )
177
- assert_equal false, @client.segment_match?(segment, "user:0", {})
178
- assert_equal true, @client.segment_match?(segment, "user:2", {})
179
- assert_equal false, @client.segment_match?(segment, "user:1", { email: "no@example.com" })
180
- assert_equal true, @client.segment_match?(segment, "user:1", { email: "a@example.com" })
35
+ # with defaults
36
+ assert_equal DEFAULT, ff_client.get('something-that-does-not-exist', default: DEFAULT)
37
+ assert_equal false, ff_client.get('disabled_flag', default: DEFAULT)
38
+ assert_equal true, ff_client.get('enabled_flag', default: DEFAULT)
39
+ assert_equal 'all-features', ff_client.get('flag_with_a_value', default: DEFAULT)
181
40
  end
182
41
 
183
- def test_segments
184
- segment_key = "prefab-segment-beta-group"
185
- @mock_base_client.config_client.mock_this_config(segment_key,
186
- Prefab::Segment.new(
187
- criterion: [
188
- Prefab::Criteria.new(
189
- operator: Prefab::Criteria::CriteriaOperator::LOOKUP_KEY_IN,
190
- values: ["user:1"]
191
- )
192
- ]
193
- )
194
- )
195
-
196
- feature = "FlagName"
197
- variants = [
198
- Prefab::FeatureFlagVariant.new(string: "inactive"),
199
- Prefab::FeatureFlagVariant.new(string: "rule target"),
200
- Prefab::FeatureFlagVariant.new(string: "default"),
201
- ]
202
- flag = Prefab::FeatureFlag.new(
203
- active: true,
204
- inactive_variant_idx: 1,
205
- rules: [
206
- Prefab::Rule.new(
207
- variant_weights: [
208
- Prefab::VariantWeight.new(weight: 1000,
209
- variant_idx: 2)
210
- ],
211
- criteria: Prefab::Criteria.new(
212
- operator: "IN_SEG",
213
- values: [segment_key]
214
- )
215
- ),
216
- Prefab::Rule.new(
217
- criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
218
- variant_weights: [
219
- Prefab::VariantWeight.new(weight: 1000,
220
- variant_idx: 3)
221
- ]
222
- )
223
-
224
- ],
225
- )
226
-
227
- assert_equal "rule target",
228
- evaluate(feature, "user:1", [], flag, variants)
229
- assert_equal "default",
230
- evaluate(feature, "user:2", [], flag, variants)
42
+ private
231
43
 
232
- end
233
-
234
- def test_in_multiple_segments_has_or_behavior
235
- segment_key_one = "prefab-segment-segment-1"
236
- @mock_base_client.config_client.mock_this_config(segment_key_one,
237
- Prefab::Segment.new(
238
- criterion: [
239
- Prefab::Criteria.new(
240
- operator: Prefab::Criteria::CriteriaOperator::LOOKUP_KEY_IN,
241
- values: ["user:1", "user:2"]
242
- )
243
- ]
244
- )
245
- )
246
- segment_key_two = "prefab-segment-segment-2"
247
- @mock_base_client.config_client.mock_this_config(segment_key_two,
248
- Prefab::Segment.new(
249
- criterion: [
250
- Prefab::Criteria.new(
251
- operator: Prefab::Criteria::CriteriaOperator::LOOKUP_KEY_IN,
252
- values: ["user:3", "user:4"]
253
- )
254
- ]
255
- )
256
- )
257
-
258
- feature = "FlagName"
259
- variants = [
260
- Prefab::FeatureFlagVariant.new(string: "inactive"),
261
- Prefab::FeatureFlagVariant.new(string: "rule target"),
262
- Prefab::FeatureFlagVariant.new(string: "default"),
263
- ]
264
- flag = Prefab::FeatureFlag.new(
265
- active: true,
266
- inactive_variant_idx: 1,
267
- rules: [
268
- Prefab::Rule.new(
269
- variant_weights: [
270
- Prefab::VariantWeight.new(weight: 1000,
271
- variant_idx: 2)
272
- ],
273
- criteria: Prefab::Criteria.new(
274
- operator: "IN_SEG",
275
- values: [segment_key_one, segment_key_two]
276
- )
277
- ),
278
- Prefab::Rule.new(
279
- criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
280
- variant_weights: [
281
- Prefab::VariantWeight.new(weight: 1000,
282
- variant_idx: 3)
283
- ]
284
- )
285
-
286
- ],
287
- )
288
-
289
- assert_equal "rule target",
290
- evaluate(feature, "user:1", [], flag, variants)
291
- assert_equal "rule target",
292
- evaluate(feature, "user:2", [], flag, variants), "matches segment 1"
293
- assert_equal "rule target",
294
- evaluate(feature, "user:3", [], flag, variants)
295
- assert_equal "rule target",
296
- evaluate(feature, "user:4", [], flag, variants)
297
- assert_equal "default",
298
- evaluate(feature, "user:5", [], flag, variants)
299
-
300
- end
301
-
302
- def test_prop_ends_with_one_of
303
- feature = "FlagName"
304
-
305
- variants = [
306
- Prefab::FeatureFlagVariant.new(bool: false),
307
- Prefab::FeatureFlagVariant.new(bool: true)
308
- ]
309
- flag = Prefab::FeatureFlag.new(
310
- active: true,
311
- inactive_variant_idx: 1,
312
- rules: [
313
- Prefab::Rule.new(
314
- criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::PROP_ENDS_WITH_ONE_OF,
315
- property: "email",
316
- values: ["@example.com"]),
317
- variant_weights: [
318
- Prefab::VariantWeight.new(weight: 100, variant_idx: 2)
319
- ]
320
- )
321
- ]
322
- )
323
-
324
- assert_equal false, evaluate(feature, "user:0", {}, flag, variants)
325
- assert_equal true, evaluate(feature, "user:0", {email: "test@example.com"}, flag, variants)
326
- assert_equal true, evaluate(feature, "user:0", {"email" => "test@example.com"}, flag, variants)
327
- end
328
-
329
- def test_prop_does_not_end_with_one_of
330
- feature = "FlagName"
331
-
332
- variants = [
333
- Prefab::FeatureFlagVariant.new(bool: false),
334
- Prefab::FeatureFlagVariant.new(bool: true)
335
- ]
336
- flag = Prefab::FeatureFlag.new(
337
- active: true,
338
- inactive_variant_idx: 1,
339
- rules: [
340
- Prefab::Rule.new(
341
- criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::PROP_DOES_NOT_END_WITH_ONE_OF,
342
- property: "email",
343
- values: ["@example.com"]),
344
- variant_weights: [
345
- Prefab::VariantWeight.new(weight: 100, variant_idx: 2)
346
- ]
347
- ),
348
- Prefab::Rule.new(
349
- criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
350
- variant_weights: [
351
- Prefab::VariantWeight.new(weight: 100, variant_idx: 1)
352
- ]
353
- )
354
- ],
355
- )
356
-
357
- assert_equal true, evaluate(feature, "user:0", {}, flag, variants)
358
- assert_equal false, evaluate(feature, "user:0", {email: "test@example.com"}, flag, variants)
359
- assert_equal false, evaluate(feature, "user:0", {"email" => "test@example.com"}, flag, variants)
360
- end
44
+ def new_client(overrides = {})
45
+ options = Prefab::Options.new(**{
46
+ prefab_config_override_dir: 'none',
47
+ prefab_config_classpath_dir: 'test',
48
+ prefab_envs: ['unit_tests'],
49
+ prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
50
+ }.merge(overrides))
361
51
 
362
- def evaluate(feature_name, lookup_key, attributes, flag, variants)
363
- variant = @client.get_variant(feature_name, lookup_key, attributes, flag, variants)
364
- @client.value_of_variant(variant)
52
+ Prefab::Client.new(options).feature_flag_client
365
53
  end
366
54
  end
data/test/test_helper.rb CHANGED
@@ -1,13 +1,20 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'minitest/autorun'
3
- require "minitest/focus"
4
+ require 'minitest/focus'
5
+ require 'minitest/reporters'
6
+ Minitest::Reporters.use!
7
+
4
8
  require 'prefab-cloud-ruby'
5
9
 
6
10
  class MockBaseClient
7
11
  STAGING_ENV_ID = 1
8
12
  PRODUCTION_ENV_ID = 2
9
13
  TEST_ENV_ID = 3
10
- attr_reader :namespace, :logger, :config_client, :options
14
+ attr_reader :namespace
15
+ attr_reader :logger
16
+ attr_reader :config_client
17
+ attr_reader :options
11
18
 
12
19
  def initialize(options = Prefab::Options.new)
13
20
  @options = options
@@ -24,20 +31,19 @@ class MockBaseClient
24
31
  @logger
25
32
  end
26
33
 
27
- def log_internal level, message
28
- end
34
+ def log_internal(level, message); end
29
35
 
30
- def config_value key
36
+ def config_value(key)
31
37
  @config_values[key]
32
38
  end
33
-
34
39
  end
35
40
 
36
41
  class MockConfigClient
37
42
  def initialize(config_values = {})
38
43
  @config_values = config_values
39
44
  end
40
- def get(key, default=nil)
45
+
46
+ def get(key, default = nil)
41
47
  @config_values.fetch(key, default)
42
48
  end
43
49
 
@@ -45,17 +51,15 @@ class MockConfigClient
45
51
  Prefab::Config.new(value: @config_values[key], key: key)
46
52
  end
47
53
 
48
- def mock_this_config key, config_value
54
+ def mock_this_config(key, config_value)
49
55
  @config_values[key] = config_value
50
56
  end
51
57
  end
52
58
 
53
59
  class MockConfigLoader
54
- def calc_config
55
- end
60
+ def calc_config; end
56
61
  end
57
62
 
58
-
59
63
  private
60
64
 
61
65
  def default_ff_rule(variant_idx)
@@ -81,9 +85,9 @@ end
81
85
 
82
86
  def new_client(overrides = {})
83
87
  options = Prefab::Options.new(**{
84
- prefab_config_override_dir: "none",
85
- prefab_config_classpath_dir: "test",
86
- prefab_envs: ["unit_tests"],
88
+ prefab_config_override_dir: 'none',
89
+ prefab_config_classpath_dir: 'test',
90
+ prefab_envs: ['unit_tests'],
87
91
  prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
88
92
  }.merge(overrides))
89
93
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+ require 'integration_test_helpers'
5
+ require 'integration_test'
6
+ require 'yaml'
7
+
8
+ class TestIntegration < Minitest::Test
9
+ IntegrationTestHelpers.find_integration_tests.map do |test_file|
10
+ tests = YAML.load(File.read(test_file))['tests']
11
+
12
+ tests.each do |test|
13
+ define_method(:"test_#{test['name']}") do
14
+ it = IntegrationTest.new(test)
15
+
16
+ case it.test_type
17
+ when :raise
18
+ err = assert_raises(it.expected[:error]) do
19
+ it.test_client.send(it.func, *it.input)
20
+ end
21
+ assert_match(/#{it.expected[:message]}/, err.message)
22
+ when :nil
23
+ assert_nil it.test_client.send(it.func, *it.input)
24
+ when :feature_flag
25
+ flag, lookup_key, attributes = *it.input
26
+ assert_equal it.expected[:value], it.test_client.send(it.func, flag, lookup_key, attributes: attributes)
27
+ when :simple_equality
28
+ assert_equal it.expected[:value], it.test_client.send(it.func, *it.input)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestLocalConfigParser < Minitest::Test
6
+ FILE_NAME = 'example-config.yaml'
7
+ DEFAULT_MATCH = 'default'
8
+
9
+ def test_parse_int_config
10
+ key = :sample_int
11
+ parsed = Prefab::LocalConfigParser.parse(key, 123, {}, FILE_NAME)[key]
12
+ config = parsed[:config]
13
+
14
+ assert_equal FILE_NAME, parsed[:source]
15
+ assert_equal DEFAULT_MATCH, parsed[:match]
16
+ assert_equal :CONFIG, config.config_type
17
+ assert_equal key.to_s, config.key
18
+ assert_equal 1, config.rows.size
19
+ assert_equal 1, config.rows[0].values.size
20
+ assert_equal 123, config.rows[0].values[0].value.int
21
+ end
22
+
23
+ def test_flag_with_a_value
24
+ key = :flag_with_a_value
25
+ value = stringify_keys({ feature_flag: true, value: 'all-features' })
26
+ parsed = Prefab::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
27
+ config = parsed[:config]
28
+
29
+ assert_equal FILE_NAME, parsed[:source]
30
+ assert_equal key, parsed[:match]
31
+ assert_equal :FEATURE_FLAG, config.config_type
32
+ assert_equal key.to_s, config.key
33
+ assert_equal 1, config.rows.size
34
+ assert_equal 1, config.rows[0].values.size
35
+
36
+ value_row = config.rows[0].values[0]
37
+ assert_equal Prefab::WeightedValues, value_row.value.weighted_values.class
38
+ assert_equal 'all-features', Prefab::ConfigValueUnwrapper.unwrap(value_row.value, key, {})
39
+ end
40
+
41
+ def test_flag_in_lookup_key
42
+ key = :flag_in_lookup_key
43
+ value = stringify_keys({ "feature_flag": 'true', value: true,
44
+ criterion: { operator: 'LOOKUP_KEY_IN', values: %w[abc123 xyz987] } })
45
+ parsed = Prefab::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
46
+ config = parsed[:config]
47
+
48
+ assert_equal FILE_NAME, parsed[:source]
49
+ assert_equal key, parsed[:match]
50
+ assert_equal :FEATURE_FLAG, config.config_type
51
+ assert_equal key.to_s, config.key
52
+ assert_equal 1, config.rows.size
53
+ assert_equal 1, config.rows[0].values.size
54
+ assert_equal 1, config.rows[0].values[0].criteria.size
55
+
56
+ value_row = config.rows[0].values[0]
57
+ assert_equal Prefab::WeightedValues, value_row.value.weighted_values.class
58
+ assert_equal true, Prefab::ConfigValueUnwrapper.unwrap(value_row.value, key, {})
59
+
60
+ assert_equal Prefab::CriteriaEvaluator::LOOKUP_KEY, value_row.criteria[0].property_name
61
+ assert_equal :LOOKUP_KEY_IN, value_row.criteria[0].operator
62
+ assert_equal %w[abc123 xyz987], value_row.criteria[0].value_to_match.string_list.values
63
+ end
64
+
65
+ private
66
+
67
+ def stringify_keys(hash)
68
+ deep_transform_keys(hash, &:to_s)
69
+ end
70
+
71
+ def deep_transform_keys(hash, &block)
72
+ result = {}
73
+ hash.each do |key, value|
74
+ result[yield(key)] = value.is_a?(Hash) ? deep_transform_keys(value, &block) : value
75
+ end
76
+ result
77
+ end
78
+ end