prefab-cloud-ruby 0.8.0 → 0.11.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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +24 -0
- data/README.md +3 -0
- data/VERSION +1 -1
- data/lib/prefab/client.rb +12 -8
- data/lib/prefab/config_client.rb +51 -19
- data/lib/prefab/config_loader.rb +12 -10
- data/lib/prefab/config_resolver.rb +36 -38
- data/lib/prefab/feature_flag_client.rb +40 -27
- data/lib/prefab-cloud-ruby.rb +2 -0
- data/lib/prefab_pb.rb +47 -44
- data/lib/prefab_services_pb.rb +17 -3
- data/prefab-cloud-ruby.gemspec +5 -3
- data/run_test_harness_server.sh +1 -1
- data/test/harness_server.rb +12 -4
- data/test/test_config_loader.rb +20 -20
- data/test/test_config_resolver.rb +72 -52
- data/test/test_feature_flag_client.rb +195 -108
- data/test/test_helper.rb +42 -6
- data/test/test_logger.rb +0 -10
- metadata +16 -2
@@ -13,111 +13,174 @@ class TestFeatureFlagClient < Minitest::Test
|
|
13
13
|
def test_pct
|
14
14
|
feature = "FlagName"
|
15
15
|
|
16
|
+
variants = [
|
17
|
+
Prefab::FeatureFlagVariant.new(bool: false),
|
18
|
+
Prefab::FeatureFlagVariant.new(bool: true)
|
19
|
+
]
|
16
20
|
flag = Prefab::FeatureFlag.new(
|
17
21
|
active: true,
|
18
|
-
|
19
|
-
|
20
|
-
Prefab::
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
)
|
31
|
-
)
|
22
|
+
inactive_variant_idx: 1,
|
23
|
+
rules: [
|
24
|
+
Prefab::Rule.new(
|
25
|
+
criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
|
26
|
+
variant_weights: [
|
27
|
+
Prefab::VariantWeight.new(weight: 86,
|
28
|
+
variant_idx: 2), #true
|
29
|
+
Prefab::VariantWeight.new(weight: 14,
|
30
|
+
variant_idx: 1), #false
|
31
|
+
]
|
32
|
+
)
|
33
|
+
]
|
32
34
|
)
|
35
|
+
# weights above chosen to be 86% in variant_idx 2. and 14% in variant_idx 1.
|
36
|
+
# since hashes high is 86.32 > 86 it just falls outside the 86% range and gets false
|
33
37
|
|
38
|
+
# "1FlagNamehashes high" hashes to 86.322% through dist
|
34
39
|
assert_equal false,
|
35
|
-
@client.evaluate(feature, "hashes high", [], flag)
|
40
|
+
@client.evaluate(feature, "hashes high", [], flag, variants)
|
41
|
+
# "1FlagNamehashes low" hashes to 44.547% through dist
|
36
42
|
assert_equal true,
|
37
|
-
@client.evaluate(feature, "hashes low", [], flag)
|
43
|
+
@client.evaluate(feature, "hashes low", [], flag, variants)
|
44
|
+
|
38
45
|
end
|
39
46
|
|
40
47
|
def test_basic_active_inactive
|
41
48
|
feature = "FlagName"
|
49
|
+
variants = [
|
50
|
+
Prefab::FeatureFlagVariant.new(bool: false),
|
51
|
+
Prefab::FeatureFlagVariant.new(bool: true)
|
52
|
+
]
|
42
53
|
flag = Prefab::FeatureFlag.new(
|
43
54
|
active: true,
|
44
|
-
|
45
|
-
|
46
|
-
Prefab::FeatureFlagVariant.new(bool: true)
|
47
|
-
],
|
48
|
-
inactive_variant_idx: 0,
|
49
|
-
default: Prefab::VariantDistribution.new(variant_idx: 1)
|
55
|
+
inactive_variant_idx: 1,
|
56
|
+
rules: default_ff_rule(2)
|
50
57
|
)
|
51
58
|
assert_equal true,
|
52
|
-
@client.evaluate(feature, "hashes high", [], flag)
|
59
|
+
@client.evaluate(feature, "hashes high", [], flag, variants)
|
53
60
|
assert_equal true,
|
54
|
-
@client.evaluate(feature, "hashes low", [], flag)
|
61
|
+
@client.evaluate(feature, "hashes low", [], flag, variants)
|
55
62
|
|
63
|
+
variants = [
|
64
|
+
Prefab::FeatureFlagVariant.new(bool: false),
|
65
|
+
Prefab::FeatureFlagVariant.new(bool: true)
|
66
|
+
]
|
56
67
|
flag = Prefab::FeatureFlag.new(
|
57
68
|
active: false,
|
58
|
-
|
59
|
-
|
60
|
-
Prefab::FeatureFlagVariant.new(bool: true)
|
61
|
-
],
|
62
|
-
inactive_variant_idx: 0,
|
63
|
-
default: Prefab::VariantDistribution.new(variant_idx: 1)
|
69
|
+
inactive_variant_idx: 1,
|
70
|
+
rules: default_ff_rule(2)
|
64
71
|
)
|
65
72
|
assert_equal false,
|
66
|
-
@client.evaluate(feature, "hashes high", [], flag)
|
73
|
+
@client.evaluate(feature, "hashes high", [], flag, variants)
|
67
74
|
assert_equal false,
|
68
|
-
@client.evaluate(feature, "hashes low", [], flag)
|
75
|
+
@client.evaluate(feature, "hashes low", [], flag, variants)
|
69
76
|
end
|
70
77
|
|
71
78
|
def test_user_targets
|
72
79
|
|
73
80
|
feature = "FlagName"
|
81
|
+
variants = [
|
82
|
+
Prefab::FeatureFlagVariant.new(string: "inactive"),
|
83
|
+
Prefab::FeatureFlagVariant.new(string: "user target"),
|
84
|
+
Prefab::FeatureFlagVariant.new(string: "default"),
|
85
|
+
]
|
74
86
|
flag = Prefab::FeatureFlag.new(
|
75
87
|
active: true,
|
76
|
-
|
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,
|
88
|
+
inactive_variant_idx: 1,
|
82
89
|
user_targets: [
|
83
|
-
variant_idx:
|
90
|
+
variant_idx: 2,
|
84
91
|
identifiers: ["user:1", "user:3"]
|
85
92
|
],
|
86
|
-
|
93
|
+
rules: default_ff_rule(3)
|
87
94
|
)
|
88
95
|
|
89
96
|
assert_equal "user target",
|
90
|
-
@client.evaluate(feature, "user:1", [], flag)
|
97
|
+
@client.evaluate(feature, "user:1", [], flag, variants)
|
91
98
|
assert_equal "default",
|
92
|
-
@client.evaluate(feature, "user:2", [], flag)
|
99
|
+
@client.evaluate(feature, "user:2", [], flag, variants)
|
93
100
|
assert_equal "user target",
|
94
|
-
@client.evaluate(feature, "user:3", [], flag)
|
101
|
+
@client.evaluate(feature, "user:3", [], flag, variants)
|
95
102
|
end
|
96
103
|
|
97
104
|
def test_inclusion_rule
|
98
105
|
feature = "FlagName"
|
106
|
+
variants = [
|
107
|
+
Prefab::FeatureFlagVariant.new(string: "inactive"),
|
108
|
+
Prefab::FeatureFlagVariant.new(string: "rule target"),
|
109
|
+
Prefab::FeatureFlagVariant.new(string: "default"),
|
110
|
+
]
|
99
111
|
flag = Prefab::FeatureFlag.new(
|
100
112
|
active: true,
|
101
|
-
|
102
|
-
|
103
|
-
Prefab::
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
113
|
+
inactive_variant_idx: 1,
|
114
|
+
rules: [
|
115
|
+
Prefab::Rule.new(
|
116
|
+
variant_weights: [
|
117
|
+
Prefab::VariantWeight.new(weight: 1000,
|
118
|
+
variant_idx: 2)
|
119
|
+
],
|
120
|
+
criteria: Prefab::Criteria.new(
|
121
|
+
operator: "LOOKUP_KEY_IN",
|
122
|
+
values: ["user:1"]
|
123
|
+
)
|
124
|
+
),
|
125
|
+
Prefab::Rule.new(
|
126
|
+
criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
|
127
|
+
variant_weights: [
|
128
|
+
Prefab::VariantWeight.new(weight: 1000,
|
129
|
+
variant_idx: 3)
|
130
|
+
]
|
112
131
|
)
|
113
|
-
|
114
|
-
|
132
|
+
|
133
|
+
],
|
115
134
|
)
|
116
135
|
|
117
136
|
assert_equal "rule target",
|
118
|
-
@client.evaluate(feature, "user:1", [], flag)
|
137
|
+
@client.evaluate(feature, "user:1", [], flag, variants)
|
119
138
|
assert_equal "default",
|
120
|
-
@client.evaluate(feature, "user:2", [], flag)
|
139
|
+
@client.evaluate(feature, "user:2", [], flag, variants)
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_property_is_one_of
|
144
|
+
feature = "FlagName"
|
145
|
+
variants = [
|
146
|
+
Prefab::FeatureFlagVariant.new(string: "inactive"),
|
147
|
+
Prefab::FeatureFlagVariant.new(string: "rule target"),
|
148
|
+
Prefab::FeatureFlagVariant.new(string: "default"),
|
149
|
+
]
|
150
|
+
flag = Prefab::FeatureFlag.new(
|
151
|
+
active: true,
|
152
|
+
inactive_variant_idx: 1,
|
153
|
+
rules: [
|
154
|
+
Prefab::Rule.new(
|
155
|
+
variant_weights: [
|
156
|
+
Prefab::VariantWeight.new(weight: 1000,
|
157
|
+
variant_idx: 2)
|
158
|
+
],
|
159
|
+
criteria: Prefab::Criteria.new(
|
160
|
+
operator: "PROP_IS_ONE_OF",
|
161
|
+
values: ["a@example.com", "b@example.com"],
|
162
|
+
property: "email"
|
163
|
+
)
|
164
|
+
),
|
165
|
+
Prefab::Rule.new(
|
166
|
+
criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
|
167
|
+
variant_weights: [
|
168
|
+
Prefab::VariantWeight.new(weight: 1000,
|
169
|
+
variant_idx: 3)
|
170
|
+
]
|
171
|
+
)
|
172
|
+
|
173
|
+
],
|
174
|
+
)
|
175
|
+
|
176
|
+
assert_equal "default",
|
177
|
+
@client.evaluate(feature, "user:1", {email: "not@example.com"}, flag, variants)
|
178
|
+
assert_equal "default",
|
179
|
+
@client.evaluate(feature, "user:2", {}, flag, variants)
|
180
|
+
assert_equal "rule target",
|
181
|
+
@client.evaluate(feature, "user:2", {email: "b@example.com"}, flag, variants)
|
182
|
+
assert_equal "rule target",
|
183
|
+
@client.evaluate(feature, "user:2", {"email" => "b@example.com"}, flag, variants)
|
121
184
|
|
122
185
|
end
|
123
186
|
|
@@ -135,86 +198,110 @@ class TestFeatureFlagClient < Minitest::Test
|
|
135
198
|
|
136
199
|
def test_segments
|
137
200
|
segment_key = "prefab-segment-beta-group"
|
138
|
-
@mock_base_client.mock_this_config(segment_key,
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
201
|
+
@mock_base_client.config_client.mock_this_config(segment_key,
|
202
|
+
Prefab::Segment.new(
|
203
|
+
name: "Beta Group",
|
204
|
+
includes: ["user:1"]
|
205
|
+
)
|
143
206
|
)
|
144
207
|
|
145
208
|
feature = "FlagName"
|
209
|
+
variants = [
|
210
|
+
Prefab::FeatureFlagVariant.new(string: "inactive"),
|
211
|
+
Prefab::FeatureFlagVariant.new(string: "rule target"),
|
212
|
+
Prefab::FeatureFlagVariant.new(string: "default"),
|
213
|
+
]
|
146
214
|
flag = Prefab::FeatureFlag.new(
|
147
215
|
active: true,
|
148
|
-
|
149
|
-
|
150
|
-
Prefab::
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
216
|
+
inactive_variant_idx: 1,
|
217
|
+
rules: [
|
218
|
+
Prefab::Rule.new(
|
219
|
+
variant_weights: [
|
220
|
+
Prefab::VariantWeight.new(weight: 1000,
|
221
|
+
variant_idx: 2)
|
222
|
+
],
|
223
|
+
criteria: Prefab::Criteria.new(
|
224
|
+
operator: "IN_SEG",
|
225
|
+
values: [segment_key]
|
226
|
+
)
|
227
|
+
),
|
228
|
+
Prefab::Rule.new(
|
229
|
+
criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
|
230
|
+
variant_weights: [
|
231
|
+
Prefab::VariantWeight.new(weight: 1000,
|
232
|
+
variant_idx: 3)
|
233
|
+
]
|
159
234
|
)
|
160
|
-
|
161
|
-
|
235
|
+
|
236
|
+
],
|
162
237
|
)
|
163
238
|
|
164
239
|
assert_equal "rule target",
|
165
|
-
@client.evaluate(feature, "user:1", [], flag)
|
240
|
+
@client.evaluate(feature, "user:1", [], flag, variants)
|
166
241
|
assert_equal "default",
|
167
|
-
@client.evaluate(feature, "user:2", [], flag)
|
242
|
+
@client.evaluate(feature, "user:2", [], flag, variants)
|
168
243
|
|
169
244
|
end
|
170
245
|
|
171
246
|
def test_in_multiple_segments_has_or_behavior
|
172
247
|
segment_key_one = "prefab-segment-segment-1"
|
173
|
-
@mock_base_client.mock_this_config(segment_key_one,
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
248
|
+
@mock_base_client.config_client.mock_this_config(segment_key_one,
|
249
|
+
Prefab::Segment.new(
|
250
|
+
name: "Segment-1",
|
251
|
+
includes: ["user:1", "user:2"],
|
252
|
+
excludes: ["user:3"]
|
253
|
+
)
|
179
254
|
)
|
180
255
|
segment_key_two = "prefab-segment-segment-2"
|
181
|
-
@mock_base_client.mock_this_config(segment_key_two,
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
256
|
+
@mock_base_client.config_client.mock_this_config(segment_key_two,
|
257
|
+
Prefab::Segment.new(
|
258
|
+
name: "Segment-2",
|
259
|
+
includes: ["user:3", "user:4"],
|
260
|
+
excludes: ["user:2"]
|
261
|
+
)
|
187
262
|
)
|
188
263
|
|
189
264
|
feature = "FlagName"
|
265
|
+
variants = [
|
266
|
+
Prefab::FeatureFlagVariant.new(string: "inactive"),
|
267
|
+
Prefab::FeatureFlagVariant.new(string: "rule target"),
|
268
|
+
Prefab::FeatureFlagVariant.new(string: "default"),
|
269
|
+
]
|
190
270
|
flag = Prefab::FeatureFlag.new(
|
191
271
|
active: true,
|
192
|
-
|
193
|
-
|
194
|
-
Prefab::
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
272
|
+
inactive_variant_idx: 1,
|
273
|
+
rules: [
|
274
|
+
Prefab::Rule.new(
|
275
|
+
variant_weights: [
|
276
|
+
Prefab::VariantWeight.new(weight: 1000,
|
277
|
+
variant_idx: 2)
|
278
|
+
],
|
279
|
+
criteria: Prefab::Criteria.new(
|
280
|
+
operator: "IN_SEG",
|
281
|
+
values: [segment_key_one, segment_key_two]
|
282
|
+
)
|
283
|
+
),
|
284
|
+
Prefab::Rule.new(
|
285
|
+
criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
|
286
|
+
variant_weights: [
|
287
|
+
Prefab::VariantWeight.new(weight: 1000,
|
288
|
+
variant_idx: 3)
|
289
|
+
]
|
203
290
|
)
|
204
|
-
|
205
|
-
default: Prefab::VariantDistribution.new(variant_idx: 2)
|
291
|
+
]
|
206
292
|
)
|
207
293
|
|
208
294
|
assert_equal "rule target",
|
209
|
-
@client.evaluate(feature, "user:1", [], flag)
|
295
|
+
@client.evaluate(feature, "user:1", [], flag, variants)
|
210
296
|
assert_equal "rule target",
|
211
|
-
@client.evaluate(feature, "user:2", [], flag), "matches segment 1"
|
297
|
+
@client.evaluate(feature, "user:2", [], flag, variants), "matches segment 1"
|
212
298
|
assert_equal "rule target",
|
213
|
-
@client.evaluate(feature, "user:3", [], flag)
|
299
|
+
@client.evaluate(feature, "user:3", [], flag, variants)
|
214
300
|
assert_equal "rule target",
|
215
|
-
@client.evaluate(feature, "user:4", [], flag)
|
301
|
+
@client.evaluate(feature, "user:4", [], flag, variants)
|
216
302
|
assert_equal "default",
|
217
|
-
@client.evaluate(feature, "user:5", [], flag)
|
303
|
+
@client.evaluate(feature, "user:5", [], flag, variants)
|
218
304
|
|
219
305
|
end
|
306
|
+
|
220
307
|
end
|
data/test/test_helper.rb
CHANGED
@@ -2,32 +2,68 @@ require 'minitest/autorun'
|
|
2
2
|
require 'prefab-cloud-ruby'
|
3
3
|
|
4
4
|
class MockBaseClient
|
5
|
-
|
5
|
+
STAGING_ENV_ID = 1
|
6
|
+
PRODUCTION_ENV_ID = 2
|
7
|
+
TEST_ENV_ID = 3
|
8
|
+
attr_reader :namespace, :logger, :config_client
|
9
|
+
|
10
|
+
def initialize(namespace: "")
|
6
11
|
|
7
|
-
def initialize(environment: "test", namespace: "")
|
8
|
-
@environment = environment
|
9
12
|
@namespace = namespace
|
10
13
|
@logger = Logger.new($stdout)
|
11
|
-
@
|
14
|
+
@config_client = MockConfigClient.new
|
12
15
|
end
|
13
16
|
|
14
17
|
def project_id
|
15
18
|
1
|
16
19
|
end
|
17
20
|
|
21
|
+
def log
|
22
|
+
@logger
|
23
|
+
end
|
24
|
+
|
18
25
|
def log_internal level, message
|
19
26
|
end
|
20
27
|
|
21
|
-
def
|
22
|
-
@config_values[key]
|
28
|
+
def config_value key
|
29
|
+
@config_values[key]
|
23
30
|
end
|
24
31
|
|
32
|
+
end
|
33
|
+
|
34
|
+
class MockConfigClient
|
35
|
+
def initialize(config_values = {})
|
36
|
+
@config_values = config_values
|
37
|
+
end
|
25
38
|
def get(key)
|
26
39
|
@config_values[key]
|
27
40
|
end
|
41
|
+
|
42
|
+
def get_config(key)
|
43
|
+
Prefab::Config.new(value: @config_values[key], key: key)
|
44
|
+
end
|
45
|
+
|
46
|
+
def mock_this_config key, config_value
|
47
|
+
@config_values[key] = config_value
|
48
|
+
end
|
28
49
|
end
|
29
50
|
|
30
51
|
class MockConfigLoader
|
31
52
|
def calc_config
|
32
53
|
end
|
33
54
|
end
|
55
|
+
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def default_ff_rule(variant_idx)
|
60
|
+
[
|
61
|
+
Prefab::Rule.new(
|
62
|
+
criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
|
63
|
+
variant_weights: [
|
64
|
+
Prefab::VariantWeight.new(weight: 1000,
|
65
|
+
variant_idx: variant_idx)
|
66
|
+
]
|
67
|
+
)
|
68
|
+
]
|
69
|
+
end
|
data/test/test_logger.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prefab-cloud-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Dwyer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -44,6 +44,20 @@ dependencies:
|
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: ld-eventsource
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
47
61
|
- !ruby/object:Gem::Dependency
|
48
62
|
name: grpc
|
49
63
|
requirement: !ruby/object:Gem::Requirement
|