prefab-cloud-ruby 0.5.1 → 0.8.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.
@@ -6,17 +6,46 @@ class TestConfigResolver < Minitest::Test
6
6
  @loader = MockConfigLoader.new
7
7
 
8
8
  loaded_values = {
9
- "projectA:key" => Prefab::ConfigValue.new(string: "valueA"),
10
- "key" => Prefab::ConfigValue.new(string: "value_none"),
11
- "projectB:key" => Prefab::ConfigValue.new(string: "valueB"),
12
- "projectB.subprojectX:key" => Prefab::ConfigValue.new(string: "projectB.subprojectX"),
13
- "projectB.subprojectY:key" => Prefab::ConfigValue.new(string: "projectB.subprojectY"),
14
- "projectB:key2" => Prefab::ConfigValue.new(string: "valueB2")
9
+ "key" => Prefab::ConfigDelta.new(
10
+ key: "key",
11
+ default: Prefab::ConfigValue.new(string: "value_no_env_default"),
12
+ envs: [{
13
+ environment: "test",
14
+ default: Prefab::ConfigValue.new(string: "value_none"),
15
+ namespace_values: [
16
+ {
17
+ namespace: "projectA",
18
+ config_value: Prefab::ConfigValue.new(string: "valueA")
19
+ },
20
+ {
21
+ namespace: "projectB",
22
+ config_value: Prefab::ConfigValue.new(string: "valueB")
23
+ },
24
+ {
25
+ namespace: "projectB.subprojectX",
26
+ config_value: Prefab::ConfigValue.new(string: "projectB.subprojectX")
27
+ },
28
+ {
29
+ namespace: "projectB.subprojectY",
30
+ config_value: Prefab::ConfigValue.new(string: "projectB.subprojectY")
31
+ },
32
+ ]
33
+ }]
34
+ ),
35
+ "key2" => Prefab::ConfigDelta.new(
36
+ key: "key2",
37
+ default: Prefab::ConfigValue.new(string: "valueB2"),
38
+ )
15
39
  }
16
40
 
17
41
  @loader.stub :calc_config, loaded_values do
18
- @resolver = Prefab::ConfigResolver.new(MockBaseClient.new, @loader)
19
- assert_equal "value_none", @resolver.get("key")
42
+
43
+ @resolverA = resolver_for_namespace("", @loader, environment: "some_other_env")
44
+ assert_equal "value_no_env_default", @resolverA.get("key")
45
+
46
+ ## below here in the test env
47
+ @resolverA = resolver_for_namespace("", @loader)
48
+ assert_equal "value_none", @resolverA.get("key")
20
49
 
21
50
  @resolverA = resolver_for_namespace("projectA", @loader)
22
51
  assert_equal "valueA", @resolverA.get("key")
@@ -27,7 +56,10 @@ class TestConfigResolver < Minitest::Test
27
56
  @resolverBX = resolver_for_namespace("projectB.subprojectX", @loader)
28
57
  assert_equal "projectB.subprojectX", @resolverBX.get("key")
29
58
 
30
- @resolverUndefinedSubProject = resolver_for_namespace("projectB.subprojectX:subsubQ", @loader)
59
+ @resolverBX = resolver_for_namespace("projectB.subprojectX", @loader)
60
+ assert_equal "valueB2", @resolverBX.get("key2")
61
+
62
+ @resolverUndefinedSubProject = resolver_for_namespace("projectB.subprojectX.subsubQ", @loader)
31
63
  assert_equal "projectB.subprojectX", @resolverBX.get("key")
32
64
 
33
65
  @resolverBX = resolver_for_namespace("projectC", @loader)
@@ -40,30 +72,69 @@ class TestConfigResolver < Minitest::Test
40
72
  @loader = MockConfigLoader.new
41
73
  @loader.stub :calc_config, {} do
42
74
  resolver = Prefab::ConfigResolver.new(MockBaseClient.new, @loader)
43
- assert resolver.send(:starts_with_ns?, "", "a")
44
- assert resolver.send(:starts_with_ns?, "a", "a")
45
- assert resolver.send(:starts_with_ns?, "a", "a.b")
46
- assert !resolver.send(:starts_with_ns?, "a.b", "a")
47
-
48
- assert resolver.send(:starts_with_ns?, "corp", "corp.proj.proja")
49
- assert resolver.send(:starts_with_ns?, "corp.proj", "corp.proj.proja")
50
- assert resolver.send(:starts_with_ns?, "corp.proj.proja", "corp.proj.proja")
51
- assert !resolver.send(:starts_with_ns?, "corp.proj.projb", "corp.proj.proja")
52
-
53
- # corp:a:b is not a real delimited namespace
54
- assert !resolver.send(:starts_with_ns?, "corp", "corp:a:b")
55
- assert resolver.send(:starts_with_ns?, "foo", "foo.baz")
56
- assert resolver.send(:starts_with_ns?, "foo.baz", "foo.baz")
57
- assert !resolver.send(:starts_with_ns?, "foo.baz", "foo.bazz")
75
+ assert_equal [true, 0], resolver.send(:starts_with_ns?, "", "a")
76
+ assert_equal [true, 1], resolver.send(:starts_with_ns?, "a", "a")
77
+ assert_equal [true, 1], resolver.send(:starts_with_ns?, "a", "a.b")
78
+ assert_equal [false, 2], resolver.send(:starts_with_ns?, "a.b", "a")
79
+
80
+ assert_equal [true, 1], resolver.send(:starts_with_ns?, "corp", "corp.proj.proja")
81
+ assert_equal [true, 2], resolver.send(:starts_with_ns?, "corp.proj", "corp.proj.proja")
82
+ assert_equal [true, 3], resolver.send(:starts_with_ns?, "corp.proj.proja", "corp.proj.proja")
83
+ assert_equal [false, 3], resolver.send(:starts_with_ns?, "corp.proj.projb", "corp.proj.proja")
84
+
85
+ # corp_equal [true, 1:,a:b is not a real delimited namespace[0
86
+ assert_equal [false, 1], resolver.send(:starts_with_ns?, "corp", "corp:a:b")
87
+ assert_equal [true, 1], resolver.send(:starts_with_ns?, "foo", "foo.baz")
88
+ assert_equal [true, 2], resolver.send(:starts_with_ns?, "foo.baz", "foo.baz")
89
+ assert_equal [false, 2], resolver.send(:starts_with_ns?, "foo.baz", "foo.bazz")
90
+ end
91
+ end
92
+
93
+ def test_special_ff_variant_copying
94
+ @loader = MockConfigLoader.new
95
+ loaded_values = {
96
+ "ff" => Prefab::ConfigDelta.new(
97
+ key: "ff",
98
+ default: Prefab::ConfigValue.new(feature_flag: Prefab::FeatureFlag.new(
99
+ variants: [
100
+ Prefab::FeatureFlagVariant.new(string: "inactive"),
101
+ Prefab::FeatureFlagVariant.new(string: "default"),
102
+ Prefab::FeatureFlagVariant.new(string: "env"),
103
+ ],
104
+ inactive_variant_idx: 0,
105
+ default: Prefab::VariantDistribution.new(variant_idx: 1)
106
+ )),
107
+ envs: [
108
+ Prefab::EnvironmentValues.new(
109
+ environment: "test",
110
+ default: Prefab::ConfigValue.new(feature_flag: Prefab::FeatureFlag.new(
111
+ inactive_variant_idx: 0,
112
+ default: Prefab::VariantDistribution.new(variant_idx: 2)))
113
+ )
114
+ ]
115
+ )
116
+ }
117
+ @loader.stub :calc_config, loaded_values do
118
+ resolver = Prefab::ConfigResolver.new(MockBaseClient.new, @loader)
119
+ ff = resolver.get("ff")
120
+ assert_equal 3, ff.variants.size
121
+ assert_equal %w(inactive default env), ff.variants.map(&:string)
58
122
  end
59
123
  end
60
124
 
61
125
  # colons are not allowed in keys, but verify behavior anyway
62
- def test_keys_with_colons
126
+ def test_key_and_namespaces_with_colons
63
127
  @loader = MockConfigLoader.new
128
+
64
129
  loaded_values = {
65
- "Key:With:Colons" => Prefab::ConfigValue.new(string: "value"),
66
- "proj:apikey" => Prefab::ConfigValue.new(string: "v2")
130
+ "Key:With:Colons" => Prefab::ConfigDelta.new(
131
+ key: "Key:With:Colons",
132
+ default: Prefab::ConfigValue.new(string: "value"),
133
+ ),
134
+ "proj:apikey" => Prefab::ConfigDelta.new(
135
+ key: "proj:apikey",
136
+ default: Prefab::ConfigValue.new(string: "v2"),
137
+ )
67
138
  }
68
139
 
69
140
  @loader.stub :calc_config, loaded_values do
@@ -72,37 +143,36 @@ class TestConfigResolver < Minitest::Test
72
143
  assert_nil r.get("apikey")
73
144
 
74
145
  r = resolver_for_namespace("proj", @loader)
75
- assert_equal "v2", r.get("apikey")
146
+ assert_nil r.get("apikey")
76
147
 
77
148
  r = resolver_for_namespace("", @loader)
78
149
  assert_nil r.get("apikey")
79
150
 
80
-
81
151
  @resolverKeyWith = resolver_for_namespace("Ket:With", @loader)
82
152
  assert_nil @resolverKeyWith.get("Colons")
83
153
  assert_nil @resolverKeyWith.get("With:Colons")
84
- assert_nil @resolverKeyWith.get("Key:With:Colons")
154
+ assert_equal "value", @resolverKeyWith.get("Key:With:Colons")
85
155
 
86
156
  @resolverKeyWithExtra = resolver_for_namespace("Key:With:Extra", @loader)
87
157
  puts @resolverKeyWithExtra.to_s
88
158
  assert_nil @resolverKeyWithExtra.get("Colons")
89
159
  assert_nil @resolverKeyWithExtra.get("With:Colons")
90
- assert_nil @resolverKeyWithExtra.get("Key:With:Colons")
160
+ assert_equal "value", @resolverKeyWithExtra.get("Key:With:Colons")
91
161
 
92
162
  @resolverKey = resolver_for_namespace("Key", @loader)
93
- assert_equal "value", @resolverKey.get("With:Colons")
163
+ assert_nil @resolverKey.get("With:Colons")
94
164
  assert_nil @resolverKey.get("Colons")
95
- assert_nil @resolverKey.get("Key:With:Colons")
165
+ assert_equal "value", @resolverKey.get("Key:With:Colons")
96
166
 
97
167
  @resolverWithProperlySegmentedNamespace = resolver_for_namespace("Key.With.Extra", @loader)
98
168
  assert_nil @resolverWithProperlySegmentedNamespace.get("Colons")
99
- assert_equal "value", @resolverWithProperlySegmentedNamespace.get("With:Colons")
100
- assert_nil @resolverWithProperlySegmentedNamespace.get("Key:With:Colons")
169
+ assert_nil @resolverWithProperlySegmentedNamespace.get("With:Colons")
170
+ assert_equal "value", @resolverWithProperlySegmentedNamespace.get("Key:With:Colons")
101
171
  end
102
172
  end
103
173
 
104
- def resolver_for_namespace(namespace, loader)
105
- Prefab::ConfigResolver.new(MockBaseClient.new(namespace: namespace), loader)
174
+ def resolver_for_namespace(namespace, loader, environment: "test")
175
+ Prefab::ConfigResolver.new(MockBaseClient.new(namespace: namespace, environment: environment), loader)
106
176
  end
107
177
 
108
178
  end
@@ -2,63 +2,219 @@ require 'test_helper'
2
2
 
3
3
  class TestFeatureFlagClient < Minitest::Test
4
4
 
5
+ def setup
6
+ super
7
+ @mock_base_client = MockBaseClient.new
8
+ @client = Prefab::FeatureFlagClient.new(@mock_base_client)
9
+ Prefab::FeatureFlagClient.send(:public, :is_on?) #publicize for testing
10
+ Prefab::FeatureFlagClient.send(:public, :segment_match?) #publicize for testing
11
+ end
12
+
5
13
  def test_pct
6
- client = Prefab::FeatureFlagClient.new(MockBaseClient.new)
7
- Prefab::FeatureFlagClient.send(:public, :is_on?)
8
14
  feature = "FlagName"
9
- flag = Prefab::FeatureFlag.new( pct: 0.5)
15
+
16
+ flag = Prefab::FeatureFlag.new(
17
+ active: true,
18
+ variants: [
19
+ Prefab::FeatureFlagVariant.new(bool: false),
20
+ Prefab::FeatureFlagVariant.new(bool: true)
21
+ ],
22
+ inactive_variant_idx: 0,
23
+ default: Prefab::VariantDistribution.new(variant_weights:
24
+ Prefab::VariantWeights.new(weights: [
25
+ Prefab::VariantWeight.new(weight: 500,
26
+ variant_idx: 1),
27
+ Prefab::VariantWeight.new(weight: 500,
28
+ variant_idx: 0),
29
+ ]
30
+ )
31
+ )
32
+ )
10
33
 
11
34
  assert_equal false,
12
- client.is_on?(feature, "hashes high", [], flag)
35
+ @client.evaluate(feature, "hashes high", [], flag)
36
+ assert_equal true,
37
+ @client.evaluate(feature, "hashes low", [], flag)
38
+ end
13
39
 
40
+ def test_basic_active_inactive
41
+ feature = "FlagName"
42
+ flag = Prefab::FeatureFlag.new(
43
+ active: true,
44
+ variants: [
45
+ Prefab::FeatureFlagVariant.new(bool: false),
46
+ Prefab::FeatureFlagVariant.new(bool: true)
47
+ ],
48
+ inactive_variant_idx: 0,
49
+ default: Prefab::VariantDistribution.new(variant_idx: 1)
50
+ )
51
+ assert_equal true,
52
+ @client.evaluate(feature, "hashes high", [], flag)
14
53
  assert_equal true,
15
- client.is_on?(feature, "hashes low", [], flag)
54
+ @client.evaluate(feature, "hashes low", [], flag)
55
+
56
+ flag = Prefab::FeatureFlag.new(
57
+ active: false,
58
+ variants: [
59
+ Prefab::FeatureFlagVariant.new(bool: false),
60
+ Prefab::FeatureFlagVariant.new(bool: true)
61
+ ],
62
+ inactive_variant_idx: 0,
63
+ default: Prefab::VariantDistribution.new(variant_idx: 1)
64
+ )
65
+ assert_equal false,
66
+ @client.evaluate(feature, "hashes high", [], flag)
67
+ assert_equal false,
68
+ @client.evaluate(feature, "hashes low", [], flag)
16
69
  end
17
70
 
71
+ def test_user_targets
18
72
 
19
- def test_off
20
- client = Prefab::FeatureFlagClient.new(MockBaseClient.new)
21
- Prefab::FeatureFlagClient.send(:public, :is_on?)
22
73
  feature = "FlagName"
23
- flag = Prefab::FeatureFlag.new(pct: 0)
74
+ flag = Prefab::FeatureFlag.new(
75
+ active: true,
76
+ variants: [
77
+ Prefab::FeatureFlagVariant.new(string: "inactive"),
78
+ Prefab::FeatureFlagVariant.new(string: "user target"),
79
+ Prefab::FeatureFlagVariant.new(string: "default"),
80
+ ],
81
+ inactive_variant_idx: 0,
82
+ user_targets: [
83
+ variant_idx: 1,
84
+ identifiers: ["user:1", "user:3"]
85
+ ],
86
+ default: Prefab::VariantDistribution.new(variant_idx: 2)
87
+ )
24
88
 
25
- assert_equal false,
26
- client.is_on?(feature, "hashes high", [], flag)
89
+ assert_equal "user target",
90
+ @client.evaluate(feature, "user:1", [], flag)
91
+ assert_equal "default",
92
+ @client.evaluate(feature, "user:2", [], flag)
93
+ assert_equal "user target",
94
+ @client.evaluate(feature, "user:3", [], flag)
95
+ end
96
+
97
+ def test_inclusion_rule
98
+ feature = "FlagName"
99
+ flag = Prefab::FeatureFlag.new(
100
+ active: true,
101
+ variants: [
102
+ Prefab::FeatureFlagVariant.new(string: "inactive"),
103
+ Prefab::FeatureFlagVariant.new(string: "rule target"),
104
+ Prefab::FeatureFlagVariant.new(string: "default"),
105
+ ],
106
+ inactive_variant_idx: 0,
107
+ rules: [Prefab::Rule.new(
108
+ distribution: Prefab::VariantDistribution.new(variant_idx: 1),
109
+ criteria: Prefab::Criteria.new(
110
+ operator: "IN",
111
+ values: ["user:1"]
112
+ )
113
+ )],
114
+ default: Prefab::VariantDistribution.new(variant_idx: 2)
115
+ )
116
+
117
+ assert_equal "rule target",
118
+ @client.evaluate(feature, "user:1", [], flag)
119
+ assert_equal "default",
120
+ @client.evaluate(feature, "user:2", [], flag)
27
121
 
28
- assert_equal false,
29
- client.is_on?(feature, "hashes low", [], flag)
30
122
  end
31
123
 
124
+ def test_segment_match?
125
+ segment = Prefab::Segment.new(
126
+ name: "Beta Group",
127
+ includes: ["user:1", "user:5"],
128
+ excludes: ["user:1", "user:2"]
129
+ )
130
+ assert_equal false, @client.segment_match?(segment, "user:0", {})
131
+ assert_equal false, @client.segment_match?(segment, "user:1", {})
132
+ assert_equal false, @client.segment_match?(segment, "user:2", {})
133
+ assert_equal true, @client.segment_match?(segment, "user:5", {})
134
+ end
135
+
136
+ def test_segments
137
+ segment_key = "prefab-segment-beta-group"
138
+ @mock_base_client.mock_this_config(segment_key,
139
+ Prefab::Segment.new(
140
+ name: "Beta Group",
141
+ includes: ["user:1"]
142
+ )
143
+ )
32
144
 
33
- def test_on
34
- client = Prefab::FeatureFlagClient.new(MockBaseClient.new)
35
- Prefab::FeatureFlagClient.send(:public, :is_on?)
36
145
  feature = "FlagName"
37
- flag = Prefab::FeatureFlag.new(pct: 1)
146
+ flag = Prefab::FeatureFlag.new(
147
+ active: true,
148
+ variants: [
149
+ Prefab::FeatureFlagVariant.new(string: "inactive"),
150
+ Prefab::FeatureFlagVariant.new(string: "rule target"),
151
+ Prefab::FeatureFlagVariant.new(string: "default"),
152
+ ],
153
+ inactive_variant_idx: 0,
154
+ rules: [Prefab::Rule.new(
155
+ distribution: Prefab::VariantDistribution.new(variant_idx: 1),
156
+ criteria: Prefab::Criteria.new(
157
+ operator: "IN_SEG",
158
+ values: [segment_key]
159
+ )
160
+ )],
161
+ default: Prefab::VariantDistribution.new(variant_idx: 2)
162
+ )
38
163
 
39
- assert_equal true,
40
- client.is_on?(feature, "hashes high", [], flag)
164
+ assert_equal "rule target",
165
+ @client.evaluate(feature, "user:1", [], flag)
166
+ assert_equal "default",
167
+ @client.evaluate(feature, "user:2", [], flag)
41
168
 
42
- assert_equal true,
43
- client.is_on?(feature, "hashes low", [], flag)
44
169
  end
45
170
 
46
- def test_whitelist
47
- client = Prefab::FeatureFlagClient.new(MockBaseClient.new)
48
- Prefab::FeatureFlagClient.send(:public, :is_on?)
171
+ def test_in_multiple_segments_has_or_behavior
172
+ segment_key_one = "prefab-segment-segment-1"
173
+ @mock_base_client.mock_this_config(segment_key_one,
174
+ Prefab::Segment.new(
175
+ name: "Segment-1",
176
+ includes: ["user:1", "user:2"],
177
+ excludes: ["user:3"]
178
+ )
179
+ )
180
+ segment_key_two = "prefab-segment-segment-2"
181
+ @mock_base_client.mock_this_config(segment_key_two,
182
+ Prefab::Segment.new(
183
+ name: "Segment-2",
184
+ includes: ["user:3", "user:4"],
185
+ excludes: ["user:2"]
186
+ )
187
+ )
188
+
49
189
  feature = "FlagName"
50
- flag = Prefab::FeatureFlag.new(pct: 0, whitelisted: ["beta", "user:1", "user:3"])
190
+ flag = Prefab::FeatureFlag.new(
191
+ active: true,
192
+ variants: [
193
+ Prefab::FeatureFlagVariant.new(string: "inactive"),
194
+ Prefab::FeatureFlagVariant.new(string: "rule target"),
195
+ Prefab::FeatureFlagVariant.new(string: "default"),
196
+ ],
197
+ inactive_variant_idx: 0,
198
+ rules: [Prefab::Rule.new(
199
+ distribution: Prefab::VariantDistribution.new(variant_idx: 1),
200
+ criteria: Prefab::Criteria.new(
201
+ operator: "IN_SEG",
202
+ values: [segment_key_one, segment_key_two]
203
+ )
204
+ )],
205
+ default: Prefab::VariantDistribution.new(variant_idx: 2)
206
+ )
51
207
 
52
- assert_equal false,
53
- client.is_on?(feature, "anything", [], flag)
54
- assert_equal true,
55
- client.is_on?(feature, "anything", ["beta"], flag)
56
- assert_equal true,
57
- client.is_on?(feature, "anything", ["alpha", "beta"], flag)
58
- assert_equal true,
59
- client.is_on?(feature, "anything", ["alpha", "user:1"], flag)
60
- assert_equal false,
61
- client.is_on?(feature, "anything", ["alpha", "user:2"], flag)
208
+ assert_equal "rule target",
209
+ @client.evaluate(feature, "user:1", [], flag)
210
+ assert_equal "rule target",
211
+ @client.evaluate(feature, "user:2", [], flag), "matches segment 1"
212
+ assert_equal "rule target",
213
+ @client.evaluate(feature, "user:3", [], flag)
214
+ assert_equal "rule target",
215
+ @client.evaluate(feature, "user:4", [], flag)
216
+ assert_equal "default",
217
+ @client.evaluate(feature, "user:5", [], flag)
62
218
 
63
219
  end
64
220
  end
data/test/test_helper.rb CHANGED
@@ -2,19 +2,29 @@ require 'minitest/autorun'
2
2
  require 'prefab-cloud-ruby'
3
3
 
4
4
  class MockBaseClient
5
- attr_reader :namespace, :logger
5
+ attr_reader :namespace, :logger, :environment
6
6
 
7
- def initialize(namespace: "")
7
+ def initialize(environment: "test", namespace: "")
8
+ @environment = environment
8
9
  @namespace = namespace
9
10
  @logger = Logger.new($stdout)
11
+ @config_values = {}
10
12
  end
11
13
 
12
- def account_id
14
+ def project_id
13
15
  1
14
16
  end
15
17
 
16
18
  def log_internal level, message
17
19
  end
20
+
21
+ def mock_this_config key, config_value
22
+ @config_values[key] = config_value
23
+ end
24
+
25
+ def get(key)
26
+ @config_values[key]
27
+ end
18
28
  end
19
29
 
20
30
  class MockConfigLoader