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,83 +1,90 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  class TestConfigLoader < Minitest::Test
5
6
  def setup
6
7
  options = Prefab::Options.new(
7
- prefab_config_override_dir: "none",
8
- prefab_config_classpath_dir: "test",
9
- prefab_envs: "unit_tests"
8
+ prefab_config_override_dir: 'none',
9
+ prefab_config_classpath_dir: 'test',
10
+ prefab_envs: 'unit_tests'
10
11
  )
11
12
  @loader = Prefab::ConfigLoader.new(MockBaseClient.new(options))
12
13
  end
13
14
 
14
15
  def test_load
15
- should_be :int, 123, "sample_int"
16
- should_be :string, "test sample value", "sample"
17
- should_be :bool, true, "sample_bool"
18
- should_be :double, 12.12, "sample_double"
16
+ should_be :int, 123, 'sample_int'
17
+ should_be :string, 'test sample value', 'sample'
18
+ should_be :bool, true, 'sample_bool'
19
+ should_be :double, 12.12, 'sample_double'
19
20
  end
20
21
 
21
22
  def test_nested
22
- should_be :string, "nested value", "nested.values.string"
23
- should_be :string, "top level", "nested.values"
24
- should_be :log_level, :ERROR, "log-level.app"
25
- should_be :log_level, :WARN, "log-level.app.controller.hello"
26
- should_be :log_level, :INFO, "log-level.app.controller.hello.index"
23
+ should_be :string, 'nested value', 'nested.values.string'
24
+ should_be :string, 'top level', 'nested.values'
25
+ should_be :log_level, :ERROR, 'log-level.app'
26
+ should_be :log_level, :WARN, 'log-level.app.controller.hello'
27
+ should_be :log_level, :INFO, 'log-level.app.controller.hello.index'
27
28
  end
28
29
 
29
30
  def test_invalid_log_level
30
- should_be :log_level, :NOT_SET_LOG_LEVEL, "log-level.invalid"
31
+ should_be :log_level, :NOT_SET_LOG_LEVEL, 'log-level.invalid'
31
32
  end
32
33
 
33
34
  def test_load_without_unit_test_env
34
35
  options = Prefab::Options.new(
35
- prefab_config_override_dir: "none",
36
- prefab_config_classpath_dir: "test",
36
+ prefab_config_override_dir: 'none',
37
+ prefab_config_classpath_dir: 'test'
37
38
  # no prefab_envs
38
39
  )
39
40
  @loader = Prefab::ConfigLoader.new(MockBaseClient.new(options))
40
- should_be :string, "default sample value", "sample"
41
- should_be :bool, true, "sample_bool"
41
+ should_be :string, 'default sample value', 'sample'
42
+ should_be :bool, true, 'sample_bool'
42
43
  end
43
44
 
44
45
  def test_highwater
45
46
  assert_equal 0, @loader.highwater_mark
46
- @loader.set(Prefab::Config.new(id: 1, key: "sample_int", rows: [Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(int: 456))]),"test")
47
+ @loader.set(Prefab::Config.new(id: 1, key: 'sample_int', rows: [config_row(Prefab::ConfigValue.new(int: 456))]),
48
+ 'test')
47
49
  assert_equal 1, @loader.highwater_mark
48
50
 
49
- @loader.set(Prefab::Config.new(id: 5, key: "sample_int", rows: [Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(int: 456))]),"test")
51
+ @loader.set(Prefab::Config.new(id: 5, key: 'sample_int', rows: [config_row(Prefab::ConfigValue.new(int: 456))]),
52
+ 'test')
50
53
  assert_equal 5, @loader.highwater_mark
51
- @loader.set(Prefab::Config.new(id: 2, key: "sample_int", rows: [Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(int: 456))]),"test")
54
+ @loader.set(Prefab::Config.new(id: 2, key: 'sample_int', rows: [config_row(Prefab::ConfigValue.new(int: 456))]),
55
+ 'test')
52
56
  assert_equal 5, @loader.highwater_mark
53
57
  end
54
58
 
55
59
  def test_keeps_most_recent
56
60
  assert_equal 0, @loader.highwater_mark
57
- @loader.set(Prefab::Config.new(id: 1, key: "sample_int", rows: [Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(int: 1))]),"test")
61
+ @loader.set(Prefab::Config.new(id: 1, key: 'sample_int', rows: [config_row(Prefab::ConfigValue.new(int: 1))]),
62
+ 'test')
58
63
  assert_equal 1, @loader.highwater_mark
59
- should_be :int, 1, "sample_int"
64
+ should_be :int, 1, 'sample_int'
60
65
 
61
- @loader.set(Prefab::Config.new(id: 4, key: "sample_int", rows: [Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(int: 4))]),"test")
66
+ @loader.set(Prefab::Config.new(id: 4, key: 'sample_int', rows: [config_row(Prefab::ConfigValue.new(int: 4))]),
67
+ 'test')
62
68
  assert_equal 4, @loader.highwater_mark
63
- should_be :int, 4, "sample_int"
69
+ should_be :int, 4, 'sample_int'
64
70
 
65
- @loader.set(Prefab::Config.new(id: 2, key: "sample_int", rows: [Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(int: 2))]),"test")
71
+ @loader.set(Prefab::Config.new(id: 2, key: 'sample_int', rows: [config_row(Prefab::ConfigValue.new(int: 2))]),
72
+ 'test')
66
73
  assert_equal 4, @loader.highwater_mark
67
- should_be :int, 4, "sample_int"
74
+ should_be :int, 4, 'sample_int'
68
75
  end
69
76
 
70
77
  def test_api_precedence
71
- should_be :int, 123, "sample_int"
78
+ should_be :int, 123, 'sample_int'
72
79
 
73
- @loader.set(Prefab::Config.new(key: "sample_int", rows: [Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(int: 456))]), "test")
74
- should_be :int, 456, "sample_int"
80
+ @loader.set(Prefab::Config.new(key: 'sample_int', rows: [config_row(Prefab::ConfigValue.new(int: 456))]), 'test')
81
+ should_be :int, 456, 'sample_int'
75
82
  end
76
83
 
77
84
  def test_api_deltas
78
85
  val = Prefab::ConfigValue.new(int: 456)
79
- config = Prefab::Config.new(key: "sample_int", rows: [Prefab::ConfigRow.new(value: val)])
80
- @loader.set(config, "test")
86
+ config = Prefab::Config.new(key: 'sample_int', rows: [config_row(val)])
87
+ @loader.set(config, 'test')
81
88
 
82
89
  configs = Prefab::Configs.new
83
90
  configs.configs << config
@@ -86,11 +93,11 @@ class TestConfigLoader < Minitest::Test
86
93
 
87
94
  def test_loading_tombstones_removes_entries
88
95
  val = Prefab::ConfigValue.new(int: 456)
89
- config = Prefab::Config.new(key: "sample_int", rows: [Prefab::ConfigRow.new(value: val)], id: 2)
90
- @loader.set(config, "test")
96
+ config = Prefab::Config.new(key: 'sample_int', rows: [config_row(val)], id: 2)
97
+ @loader.set(config, 'test')
91
98
 
92
- config = Prefab::Config.new(key: "sample_int", rows: [], id: 3)
93
- @loader.set(config, "test")
99
+ config = Prefab::Config.new(key: 'sample_int', rows: [], id: 3)
100
+ @loader.set(config, 'test')
94
101
 
95
102
  configs = Prefab::Configs.new
96
103
  assert_equal configs, @loader.get_api_deltas
@@ -99,7 +106,11 @@ class TestConfigLoader < Minitest::Test
99
106
  private
100
107
 
101
108
  def should_be(type, value, key)
102
- assert_equal type, @loader.calc_config[key][:config].rows[0].value.type
103
- assert_equal value, @loader.calc_config[key][:config].rows[0].value.send(type)
109
+ assert_equal type, @loader.calc_config[key][:config].rows[0].values[0].value.type
110
+ assert_equal value, @loader.calc_config[key][:config].rows[0].values[0].value.send(type)
111
+ end
112
+
113
+ def config_row(value)
114
+ Prefab::ConfigRow.new(values: [Prefab::ConditionalValue.new(value: value)])
104
115
  end
105
116
  end
@@ -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