prefab-cloud-ruby 0.20.0 → 0.21.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 (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 +26 -31
  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 +47 -46
  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