prefab-cloud-ruby 0.20.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 +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