prefab-cloud-ruby 0.24.4 → 0.24.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -1
- data/VERSION +1 -1
- data/compile_protos.sh +7 -0
- data/lib/prefab/client.rb +17 -4
- data/lib/prefab/config_client.rb +10 -10
- data/lib/prefab/config_value_unwrapper.rb +20 -9
- data/lib/prefab/context.rb +39 -7
- data/lib/prefab/context_shape_aggregator.rb +1 -1
- data/lib/prefab/criteria_evaluator.rb +24 -16
- data/lib/prefab/evaluated_keys_aggregator.rb +1 -1
- data/lib/prefab/evaluation.rb +48 -0
- data/lib/prefab/evaluation_summary_aggregator.rb +85 -0
- data/lib/prefab/example_contexts_aggregator.rb +76 -0
- data/lib/prefab/exponential_backoff.rb +5 -0
- data/lib/prefab/http_connection.rb +5 -1
- data/lib/prefab/log_path_aggregator.rb +1 -1
- data/lib/prefab/logger_client.rb +12 -13
- data/lib/prefab/options.rb +27 -14
- data/lib/prefab/periodic_sync.rb +30 -13
- data/lib/prefab/rate_limit_cache.rb +41 -0
- data/lib/prefab/resolved_config_presenter.rb +2 -4
- data/lib/prefab/weighted_value_resolver.rb +1 -1
- data/lib/prefab-cloud-ruby.rb +6 -3
- data/lib/prefab_pb.rb +11 -1
- data/prefab-cloud-ruby.gemspec +14 -5
- data/test/support/common_helpers.rb +105 -0
- data/test/support/mock_base_client.rb +44 -0
- data/test/support/mock_config_client.rb +19 -0
- data/test/support/mock_config_loader.rb +1 -0
- data/test/test_client.rb +257 -2
- data/test/test_config_resolver.rb +25 -24
- data/test/test_config_value_unwrapper.rb +22 -32
- data/test/test_context_shape_aggregator.rb +0 -1
- data/test/test_criteria_evaluator.rb +179 -133
- data/test/test_evaluation_summary_aggregator.rb +162 -0
- data/test/test_example_contexts_aggregator.rb +238 -0
- data/test/test_helper.rb +5 -131
- data/test/test_local_config_parser.rb +2 -2
- data/test/test_logger.rb +5 -5
- data/test/test_options.rb +8 -0
- data/test/test_rate_limit_cache.rb +44 -0
- data/test/test_weighted_value_resolver.rb +13 -7
- metadata +13 -4
- data/lib/prefab/evaluated_configs_aggregator.rb +0 -60
- data/test/test_evaluated_configs_aggregator.rb +0 -254
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
class TestEvaluationSummaryAggregator < Minitest::Test
|
7
|
+
EFFECTIVELY_NEVER = 99_999 # we sync manually
|
8
|
+
|
9
|
+
EXAMPLE_VALUE_1 = PrefabProto::ConfigValue.new(bool: true)
|
10
|
+
EXAMPLE_VALUE_2 = PrefabProto::ConfigValue.new(bool: false)
|
11
|
+
|
12
|
+
EXAMPLE_COUNTER = {
|
13
|
+
config_id: 1,
|
14
|
+
selected_index: 2,
|
15
|
+
config_row_index: 3,
|
16
|
+
conditional_value_index: 4,
|
17
|
+
weighted_value_index: 5,
|
18
|
+
seleced_value: EXAMPLE_VALUE_1
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
def test_increments_counts
|
22
|
+
aggregator = Prefab::EvaluationSummaryAggregator.new(client: MockBaseClient.new, max_keys: 10,
|
23
|
+
sync_interval: EFFECTIVELY_NEVER)
|
24
|
+
|
25
|
+
aggregator.record(config_key: 'foo', config_type: 'bar', counter: EXAMPLE_COUNTER)
|
26
|
+
|
27
|
+
assert_equal 1, aggregator.data[%w[foo bar]][EXAMPLE_COUNTER]
|
28
|
+
|
29
|
+
2.times { aggregator.record(config_key: 'foo', config_type: 'bar', counter: EXAMPLE_COUNTER) }
|
30
|
+
assert_equal 3, aggregator.data[%w[foo bar]][EXAMPLE_COUNTER]
|
31
|
+
|
32
|
+
another_counter = EXAMPLE_COUNTER.merge(selected_index: EXAMPLE_COUNTER[:selected_index] + 1)
|
33
|
+
|
34
|
+
aggregator.record(config_key: 'foo', config_type: 'bar', counter: another_counter)
|
35
|
+
assert_equal 3, aggregator.data[%w[foo bar]][EXAMPLE_COUNTER]
|
36
|
+
assert_equal 1, aggregator.data[%w[foo bar]][another_counter]
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_prepare_data
|
40
|
+
aggregator = Prefab::EvaluationSummaryAggregator.new(client: MockBaseClient.new, max_keys: 10,
|
41
|
+
sync_interval: EFFECTIVELY_NEVER)
|
42
|
+
|
43
|
+
expected = {
|
44
|
+
['config-1', :CONFIG] => {
|
45
|
+
{ config_id: 1, selected_index: 2, config_row_index: 3, conditional_value_index: 4,
|
46
|
+
weighted_value_index: 5, selected_value: EXAMPLE_VALUE_1 } => 3,
|
47
|
+
{ config_id: 1, selected_index: 3, config_row_index: 7, conditional_value_index: 8,
|
48
|
+
weighted_value_index: 10, selected_value: EXAMPLE_VALUE_2 } => 1
|
49
|
+
},
|
50
|
+
['config-2', :FEATURE_FLAG] => {
|
51
|
+
{ config_id: 2, selected_index: 3, config_row_index: 5, conditional_value_index: 7,
|
52
|
+
weighted_value_index: 6, selected_value: EXAMPLE_VALUE_1 } => 9
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
add_example_data(aggregator)
|
57
|
+
assert_equal expected, aggregator.prepare_data
|
58
|
+
assert aggregator.data.empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_sync
|
62
|
+
awhile_ago = Time.now - 60
|
63
|
+
now = Time.now
|
64
|
+
|
65
|
+
client = MockBaseClient.new
|
66
|
+
|
67
|
+
aggregator = nil
|
68
|
+
|
69
|
+
Timecop.freeze(awhile_ago) do
|
70
|
+
# start the aggregator in the past
|
71
|
+
aggregator = Prefab::EvaluationSummaryAggregator.new(client: client, max_keys: 10,
|
72
|
+
sync_interval: EFFECTIVELY_NEVER)
|
73
|
+
end
|
74
|
+
|
75
|
+
add_example_data(aggregator)
|
76
|
+
|
77
|
+
expected_post = PrefabProto::TelemetryEvents.new(
|
78
|
+
instance_hash: client.instance_hash,
|
79
|
+
events: [
|
80
|
+
PrefabProto::TelemetryEvent.new(
|
81
|
+
summaries:
|
82
|
+
|
83
|
+
PrefabProto::ConfigEvaluationSummaries.new(
|
84
|
+
start: awhile_ago.to_i * 1000,
|
85
|
+
end: now.to_i * 1000,
|
86
|
+
summaries: [
|
87
|
+
PrefabProto::ConfigEvaluationSummary.new(
|
88
|
+
key: 'config-1',
|
89
|
+
type: :CONFIG,
|
90
|
+
counters: [
|
91
|
+
PrefabProto::ConfigEvaluationCounter.new(
|
92
|
+
config_id: 1,
|
93
|
+
selected_index: 2,
|
94
|
+
config_row_index: 3,
|
95
|
+
conditional_value_index: 4,
|
96
|
+
weighted_value_index: 5,
|
97
|
+
selected_value: EXAMPLE_VALUE_1,
|
98
|
+
count: 3
|
99
|
+
),
|
100
|
+
PrefabProto::ConfigEvaluationCounter.new(
|
101
|
+
config_id: 1,
|
102
|
+
selected_index: 3,
|
103
|
+
config_row_index: 7,
|
104
|
+
conditional_value_index: 8,
|
105
|
+
weighted_value_index: 10,
|
106
|
+
selected_value: EXAMPLE_VALUE_2,
|
107
|
+
count: 1
|
108
|
+
)
|
109
|
+
]
|
110
|
+
),
|
111
|
+
PrefabProto::ConfigEvaluationSummary.new(
|
112
|
+
key: 'config-2',
|
113
|
+
type: :FEATURE_FLAG,
|
114
|
+
counters: [
|
115
|
+
PrefabProto::ConfigEvaluationCounter.new(
|
116
|
+
config_id: 2,
|
117
|
+
selected_index: 3,
|
118
|
+
config_row_index: 5,
|
119
|
+
conditional_value_index: 7,
|
120
|
+
weighted_value_index: 6,
|
121
|
+
selected_value: EXAMPLE_VALUE_1,
|
122
|
+
count: 9
|
123
|
+
)
|
124
|
+
]
|
125
|
+
)
|
126
|
+
]
|
127
|
+
)
|
128
|
+
)
|
129
|
+
]
|
130
|
+
)
|
131
|
+
|
132
|
+
requests = wait_for_post_requests(client) do
|
133
|
+
Timecop.freeze(now) do
|
134
|
+
aggregator.sync
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
assert_equal [[
|
139
|
+
'/api/v1/telemetry',
|
140
|
+
expected_post
|
141
|
+
]], requests
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def add_example_data(aggregator)
|
147
|
+
data = {
|
148
|
+
['config-1', :CONFIG] => {
|
149
|
+
{ config_id: 1, selected_index: 2, config_row_index: 3, conditional_value_index: 4,
|
150
|
+
weighted_value_index: 5, selected_value: EXAMPLE_VALUE_1 } => 3,
|
151
|
+
{ config_id: 1, selected_index: 3, config_row_index: 7, conditional_value_index: 8,
|
152
|
+
weighted_value_index: 10, selected_value: EXAMPLE_VALUE_2 } => 1
|
153
|
+
},
|
154
|
+
['config-2', :FEATURE_FLAG] => {
|
155
|
+
{ config_id: 2, selected_index: 3, config_row_index: 5, conditional_value_index: 7,
|
156
|
+
weighted_value_index: 6, selected_value: EXAMPLE_VALUE_1 } => 9
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
aggregator.instance_variable_set('@data', data)
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
class TestExampleContextsAggregator < Minitest::Test
|
7
|
+
EFFECTIVELY_NEVER = 99_999 # we sync manually
|
8
|
+
|
9
|
+
def test_record
|
10
|
+
aggregator = Prefab::ExampleContextsAggregator.new(client: MockBaseClient.new, max_contexts: 2,
|
11
|
+
sync_interval: EFFECTIVELY_NEVER)
|
12
|
+
|
13
|
+
context = Prefab::Context.new(
|
14
|
+
user: { key: 'abc' },
|
15
|
+
device: { key: 'def', mobile: true }
|
16
|
+
)
|
17
|
+
|
18
|
+
aggregator.record(context)
|
19
|
+
assert_equal [context], aggregator.data
|
20
|
+
|
21
|
+
# This doesn't get updated because we already have a context for this user/device
|
22
|
+
aggregator.record(context)
|
23
|
+
assert_equal [context], aggregator.data
|
24
|
+
|
25
|
+
new_context = Prefab::Context.new(
|
26
|
+
user: { key: 'ghi', admin: true },
|
27
|
+
team: { key: '999' }
|
28
|
+
)
|
29
|
+
|
30
|
+
aggregator.record(new_context)
|
31
|
+
assert_equal [context, new_context], aggregator.data
|
32
|
+
|
33
|
+
# this doesn't get recorded because we're at max_contexts
|
34
|
+
aggregator.record(Prefab::Context.new(user: { key: 'new' }))
|
35
|
+
assert_equal [context, new_context], aggregator.data
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_prepare_data
|
39
|
+
aggregator = Prefab::ExampleContextsAggregator.new(client: MockBaseClient.new, max_contexts: 10,
|
40
|
+
sync_interval: EFFECTIVELY_NEVER)
|
41
|
+
|
42
|
+
context = Prefab::Context.new(
|
43
|
+
user: { key: 'abc' },
|
44
|
+
device: { key: 'def', mobile: true }
|
45
|
+
)
|
46
|
+
|
47
|
+
aggregator.record(context)
|
48
|
+
|
49
|
+
assert_equal [context], aggregator.prepare_data
|
50
|
+
assert aggregator.data.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_record_with_expiry
|
54
|
+
aggregator = Prefab::ExampleContextsAggregator.new(client: MockBaseClient.new, max_contexts: 10,
|
55
|
+
sync_interval: EFFECTIVELY_NEVER)
|
56
|
+
|
57
|
+
context = Prefab::Context.new(
|
58
|
+
user: { key: 'abc' },
|
59
|
+
device: { key: 'def', mobile: true }
|
60
|
+
)
|
61
|
+
|
62
|
+
aggregator.record(context)
|
63
|
+
|
64
|
+
assert_equal [context], aggregator.data
|
65
|
+
|
66
|
+
Timecop.travel(Time.now + (60 * 60) - 1) do
|
67
|
+
aggregator.record(context)
|
68
|
+
|
69
|
+
# This doesn't get updated because we already have a context for this user/device in the timeframe
|
70
|
+
assert_equal [context], aggregator.data
|
71
|
+
end
|
72
|
+
|
73
|
+
Timecop.travel(Time.now + ((60 * 60) + 1)) do
|
74
|
+
# this is new because we've passed the expiry
|
75
|
+
aggregator.record(context)
|
76
|
+
|
77
|
+
assert_equal [context, context], aggregator.data
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_sync
|
82
|
+
now = Time.now
|
83
|
+
|
84
|
+
client = MockBaseClient.new
|
85
|
+
|
86
|
+
aggregator = Prefab::ExampleContextsAggregator.new(client: client, max_contexts: 10,
|
87
|
+
sync_interval: EFFECTIVELY_NEVER)
|
88
|
+
|
89
|
+
context = Prefab::Context.new(
|
90
|
+
user: { key: 'abc' },
|
91
|
+
device: { key: 'def', mobile: true }
|
92
|
+
)
|
93
|
+
aggregator.record(context)
|
94
|
+
|
95
|
+
# This is the same as above so we shouldn't get anything new
|
96
|
+
aggregator.record(context)
|
97
|
+
|
98
|
+
aggregator.record(
|
99
|
+
Prefab::Context.new(
|
100
|
+
user: { key: 'ghi' },
|
101
|
+
device: { key: 'jkl', mobile: false }
|
102
|
+
)
|
103
|
+
)
|
104
|
+
|
105
|
+
aggregator.record(Prefab::Context.new(user: { key: 'kev', name: 'kevin', age: 48.5 }))
|
106
|
+
|
107
|
+
assert_equal 3, aggregator.cache.data.size
|
108
|
+
|
109
|
+
expected_post = PrefabProto::TelemetryEvents.new(
|
110
|
+
instance_hash: client.instance_hash,
|
111
|
+
events: [
|
112
|
+
PrefabProto::TelemetryEvent.new(
|
113
|
+
example_contexts: PrefabProto::ExampleContexts.new(
|
114
|
+
examples: [
|
115
|
+
PrefabProto::ExampleContext.new(
|
116
|
+
timestamp: now.utc.to_i * 1000,
|
117
|
+
contextSet: PrefabProto::ContextSet.new(
|
118
|
+
contexts: [
|
119
|
+
PrefabProto::Context.new(
|
120
|
+
type: 'user',
|
121
|
+
values: {
|
122
|
+
'key' => PrefabProto::ConfigValue.new(string: 'abc')
|
123
|
+
}
|
124
|
+
),
|
125
|
+
PrefabProto::Context.new(
|
126
|
+
type: 'device',
|
127
|
+
values: {
|
128
|
+
'key' => PrefabProto::ConfigValue.new(string: 'def'),
|
129
|
+
'mobile' => PrefabProto::ConfigValue.new(bool: true)
|
130
|
+
}
|
131
|
+
)
|
132
|
+
]
|
133
|
+
)
|
134
|
+
),
|
135
|
+
|
136
|
+
PrefabProto::ExampleContext.new(
|
137
|
+
timestamp: now.utc.to_i * 1000,
|
138
|
+
contextSet: PrefabProto::ContextSet.new(
|
139
|
+
contexts: [
|
140
|
+
PrefabProto::Context.new(
|
141
|
+
type: 'user',
|
142
|
+
values: {
|
143
|
+
'key' => PrefabProto::ConfigValue.new(string: 'ghi')
|
144
|
+
}
|
145
|
+
),
|
146
|
+
PrefabProto::Context.new(
|
147
|
+
type: 'device',
|
148
|
+
values: {
|
149
|
+
'key' => PrefabProto::ConfigValue.new(string: 'jkl'),
|
150
|
+
'mobile' => PrefabProto::ConfigValue.new(bool: false)
|
151
|
+
}
|
152
|
+
)
|
153
|
+
]
|
154
|
+
)
|
155
|
+
),
|
156
|
+
|
157
|
+
PrefabProto::ExampleContext.new(
|
158
|
+
timestamp: now.utc.to_i * 1000,
|
159
|
+
contextSet: PrefabProto::ContextSet.new(
|
160
|
+
contexts: [
|
161
|
+
PrefabProto::Context.new(
|
162
|
+
type: 'user',
|
163
|
+
values: {
|
164
|
+
'key' => PrefabProto::ConfigValue.new(string: 'kev'),
|
165
|
+
'name' => PrefabProto::ConfigValue.new(string: 'kevin'),
|
166
|
+
'age' => PrefabProto::ConfigValue.new(double: 48.5)
|
167
|
+
}
|
168
|
+
)
|
169
|
+
]
|
170
|
+
)
|
171
|
+
)
|
172
|
+
]
|
173
|
+
)
|
174
|
+
)
|
175
|
+
]
|
176
|
+
)
|
177
|
+
|
178
|
+
requests = wait_for_post_requests(client) do
|
179
|
+
Timecop.freeze(now + (60 * 60) - 1) do
|
180
|
+
aggregator.sync
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
assert_equal [[
|
185
|
+
'/api/v1/telemetry',
|
186
|
+
expected_post
|
187
|
+
]], requests
|
188
|
+
|
189
|
+
# this hasn't changed because not enough time has passed
|
190
|
+
assert_equal 3, aggregator.cache.data.size
|
191
|
+
|
192
|
+
# a sync past the expiry should clear the cache
|
193
|
+
Timecop.freeze(now + (60 * 60) + 1) do
|
194
|
+
# we need a new piece of data for the sync to happen
|
195
|
+
aggregator.record(Prefab::Context.new(user: { key: 'bozo', name: 'Bozo', age: 99 }))
|
196
|
+
|
197
|
+
requests = wait_for_post_requests(client) do
|
198
|
+
aggregator.sync
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
expected_post = PrefabProto::TelemetryEvents.new(
|
203
|
+
instance_hash: client.instance_hash,
|
204
|
+
events: [
|
205
|
+
PrefabProto::TelemetryEvent.new(
|
206
|
+
example_contexts: PrefabProto::ExampleContexts.new(
|
207
|
+
examples: [
|
208
|
+
PrefabProto::ExampleContext.new(
|
209
|
+
timestamp: (now.utc.to_i + (60 * 60) + 1) * 1000,
|
210
|
+
contextSet: PrefabProto::ContextSet.new(
|
211
|
+
contexts: [
|
212
|
+
PrefabProto::Context.new(
|
213
|
+
type: 'user',
|
214
|
+
values: {
|
215
|
+
'key' => PrefabProto::ConfigValue.new(string: 'bozo'),
|
216
|
+
'name' => PrefabProto::ConfigValue.new(string: 'Bozo'),
|
217
|
+
'age' => PrefabProto::ConfigValue.new(int: 99)
|
218
|
+
}
|
219
|
+
)
|
220
|
+
]
|
221
|
+
)
|
222
|
+
)
|
223
|
+
]
|
224
|
+
)
|
225
|
+
)
|
226
|
+
]
|
227
|
+
)
|
228
|
+
|
229
|
+
assert_equal [[
|
230
|
+
'/api/v1/telemetry',
|
231
|
+
expected_post
|
232
|
+
]], requests
|
233
|
+
|
234
|
+
# The last sync should have cleared the cache of everything except the latest context
|
235
|
+
assert_equal 1, aggregator.cache.data.size
|
236
|
+
assert_equal ['user:bozo'], aggregator.cache.data.keys
|
237
|
+
end
|
238
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -7,137 +7,11 @@ Minitest::Reporters.use!
|
|
7
7
|
|
8
8
|
require 'prefab-cloud-ruby'
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
PRODUCTION_ENV_ID = 2
|
13
|
-
TEST_ENV_ID = 3
|
14
|
-
attr_reader :namespace
|
15
|
-
attr_reader :logger
|
16
|
-
attr_reader :config_client
|
17
|
-
attr_reader :options
|
18
|
-
|
19
|
-
def initialize(options = Prefab::Options.new)
|
20
|
-
@options = options
|
21
|
-
@namespace = namespace
|
22
|
-
@logger = Prefab::LoggerClient.new($stdout)
|
23
|
-
@config_client = MockConfigClient.new
|
24
|
-
end
|
25
|
-
|
26
|
-
def project_id
|
27
|
-
1
|
28
|
-
end
|
29
|
-
|
30
|
-
def log
|
31
|
-
@logger
|
32
|
-
end
|
33
|
-
|
34
|
-
def log_internal(level, message); end
|
35
|
-
|
36
|
-
def context_shape_aggregator; end
|
37
|
-
|
38
|
-
def evaluated_keys_aggregator; end
|
39
|
-
|
40
|
-
def evaluated_configs_aggregator; end
|
41
|
-
|
42
|
-
def config_value(key)
|
43
|
-
@config_values[key]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
class MockConfigClient
|
48
|
-
def initialize(config_values = {})
|
49
|
-
@config_values = config_values
|
50
|
-
end
|
51
|
-
|
52
|
-
def get(key, default = nil)
|
53
|
-
@config_values.fetch(key, default)
|
54
|
-
end
|
55
|
-
|
56
|
-
def get_config(key)
|
57
|
-
PrefabProto::Config.new(value: @config_values[key], key: key)
|
58
|
-
end
|
59
|
-
|
60
|
-
def mock_this_config(key, config_value)
|
61
|
-
@config_values[key] = config_value
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
class MockConfigLoader
|
66
|
-
def calc_config; end
|
67
|
-
end
|
68
|
-
|
69
|
-
private
|
70
|
-
|
71
|
-
def default_ff_rule(variant_idx)
|
72
|
-
[
|
73
|
-
Prefab::Rule.new(
|
74
|
-
criteria: Prefab::Criteria.new(operator: Prefab::Criteria::CriteriaOperator::ALWAYS_TRUE),
|
75
|
-
variant_weights: [
|
76
|
-
Prefab::VariantWeight.new(weight: 1000,
|
77
|
-
variant_idx: variant_idx)
|
78
|
-
]
|
79
|
-
)
|
80
|
-
]
|
81
|
-
end
|
82
|
-
|
83
|
-
def with_env(key, value, &block)
|
84
|
-
old_value = ENV[key]
|
85
|
-
|
86
|
-
ENV[key] = value
|
87
|
-
block.call
|
88
|
-
ensure
|
89
|
-
ENV[key] = old_value
|
90
|
-
end
|
91
|
-
|
92
|
-
def new_client(overrides = {})
|
93
|
-
options = Prefab::Options.new(**{
|
94
|
-
prefab_config_override_dir: 'none',
|
95
|
-
prefab_config_classpath_dir: 'test',
|
96
|
-
prefab_envs: ['unit_tests'],
|
97
|
-
prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
|
98
|
-
}.merge(overrides))
|
99
|
-
|
100
|
-
Prefab::Client.new(options)
|
101
|
-
end
|
102
|
-
|
103
|
-
def string_list(values)
|
104
|
-
PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: values))
|
105
|
-
end
|
106
|
-
|
107
|
-
def inject_config(client, config)
|
108
|
-
resolver = client.config_client.instance_variable_get('@config_resolver')
|
109
|
-
store = resolver.instance_variable_get('@local_store')
|
110
|
-
|
111
|
-
store[config.key] = { config: config }
|
10
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'support', '**', '*.rb')).each do |file|
|
11
|
+
require file
|
112
12
|
end
|
113
13
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
end
|
118
|
-
|
119
|
-
def wait_for_post_requests(client)
|
120
|
-
max_wait = 2
|
121
|
-
sleep_time = 0.01
|
122
|
-
|
123
|
-
requests = []
|
124
|
-
|
125
|
-
client.define_singleton_method(:post) do |*params|
|
126
|
-
requests.push(params)
|
127
|
-
|
128
|
-
OpenStruct.new(status: 200)
|
129
|
-
end
|
130
|
-
|
131
|
-
yield
|
132
|
-
|
133
|
-
# let the flush thread run
|
134
|
-
wait_time = 0
|
135
|
-
while requests.empty?
|
136
|
-
wait_time += sleep_time
|
137
|
-
sleep sleep_time
|
138
|
-
|
139
|
-
raise "Waited #{max_wait} seconds for the flush thread to run, but it never did" if wait_time > max_wait
|
140
|
-
end
|
141
|
-
|
142
|
-
requests
|
14
|
+
MiniTest::Test.class_eval do
|
15
|
+
include CommonHelpers
|
16
|
+
extend CommonHelpers
|
143
17
|
end
|
@@ -34,7 +34,7 @@ class TestLocalConfigParser < Minitest::Test
|
|
34
34
|
assert_equal 1, config.rows[0].values.size
|
35
35
|
|
36
36
|
value_row = config.rows[0].values[0]
|
37
|
-
assert_equal 'all-features', Prefab::ConfigValueUnwrapper.
|
37
|
+
assert_equal 'all-features', Prefab::ConfigValueUnwrapper.deepest_value(value_row.value, key, {}).unwrap
|
38
38
|
end
|
39
39
|
|
40
40
|
def test_flag_in_user_key
|
@@ -53,7 +53,7 @@ class TestLocalConfigParser < Minitest::Test
|
|
53
53
|
assert_equal 1, config.rows[0].values[0].criteria.size
|
54
54
|
|
55
55
|
value_row = config.rows[0].values[0]
|
56
|
-
assert_equal true, Prefab::ConfigValueUnwrapper.
|
56
|
+
assert_equal true, Prefab::ConfigValueUnwrapper.deepest_value(value_row.value, key, {}).unwrap
|
57
57
|
|
58
58
|
assert_equal 'user.key', value_row.criteria[0].property_name
|
59
59
|
assert_equal :PROP_IS_ONE_OF, value_row.criteria[0].operator
|
data/test/test_logger.rb
CHANGED
@@ -65,20 +65,20 @@ class TestLogger < Minitest::Test
|
|
65
65
|
assert_equal ::Logger::INFO,
|
66
66
|
@logger.level_of('app.models.user'), 'PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL is info'
|
67
67
|
|
68
|
-
@logger.
|
68
|
+
@logger.config_client = MockConfigClient.new({})
|
69
69
|
assert_equal ::Logger::WARN,
|
70
70
|
@logger.level_of('app.models.user'), 'default is warn'
|
71
71
|
|
72
|
-
@logger.
|
72
|
+
@logger.config_client = MockConfigClient.new('log-level.app' => :INFO)
|
73
73
|
assert_equal ::Logger::INFO,
|
74
74
|
@logger.level_of('app.models.user')
|
75
75
|
|
76
|
-
@logger.
|
76
|
+
@logger.config_client = MockConfigClient.new('log-level.app' => :DEBUG)
|
77
77
|
assert_equal ::Logger::DEBUG,
|
78
78
|
@logger.level_of('app.models.user')
|
79
79
|
|
80
|
-
@logger.
|
81
|
-
|
80
|
+
@logger.config_client = MockConfigClient.new('log-level.app' => :DEBUG,
|
81
|
+
'log-level.app.models' => :ERROR)
|
82
82
|
assert_equal ::Logger::ERROR,
|
83
83
|
@logger.level_of('app.models.user'), 'test leveling'
|
84
84
|
end
|
data/test/test_options.rb
CHANGED
@@ -41,4 +41,12 @@ class TestOptions < Minitest::Test
|
|
41
41
|
collect_logs: false)
|
42
42
|
assert_equal 0, options.collect_max_paths
|
43
43
|
end
|
44
|
+
|
45
|
+
def test_collect_max_evaluation_summaries
|
46
|
+
assert_equal 0, Prefab::Options.new.collect_max_evaluation_summaries
|
47
|
+
assert_equal 100_000, Prefab::Options.new(collect_evaluation_summaries: true).collect_max_evaluation_summaries
|
48
|
+
assert_equal 3,
|
49
|
+
Prefab::Options.new(collect_evaluation_summaries: true,
|
50
|
+
collect_max_evaluation_summaries: 3).collect_max_evaluation_summaries
|
51
|
+
end
|
44
52
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
class RateLimitCacheTest < Minitest::Test
|
7
|
+
def test_set_and_fresh
|
8
|
+
cache = Prefab::RateLimitCache.new(5)
|
9
|
+
cache.set('key')
|
10
|
+
assert cache.fresh?('key')
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_fresh_with_no_set
|
14
|
+
cache = Prefab::RateLimitCache.new(5)
|
15
|
+
refute cache.fresh?('key')
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_get_after_expiration
|
19
|
+
cache = Prefab::RateLimitCache.new(5)
|
20
|
+
|
21
|
+
Timecop.freeze(Time.now - 6) do
|
22
|
+
cache.set('key')
|
23
|
+
assert cache.fresh?('key')
|
24
|
+
end
|
25
|
+
|
26
|
+
refute cache.fresh?('key')
|
27
|
+
|
28
|
+
# but the data is still there
|
29
|
+
assert cache.data.get('key')
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_prune
|
33
|
+
cache = Prefab::RateLimitCache.new(5)
|
34
|
+
|
35
|
+
Timecop.freeze(Time.now - 6) do
|
36
|
+
cache.set('key')
|
37
|
+
assert cache.fresh?('key')
|
38
|
+
end
|
39
|
+
|
40
|
+
cache.prune
|
41
|
+
|
42
|
+
refute cache.fresh?('key')
|
43
|
+
end
|
44
|
+
end
|
@@ -8,30 +8,36 @@ class TestWeightedValueResolver < Minitest::Test
|
|
8
8
|
def test_resolving_single_value
|
9
9
|
values = weighted_values([['abc', 1]])
|
10
10
|
resolver = Prefab::WeightedValueResolver.new(values, KEY, nil)
|
11
|
-
assert_equal 'abc', resolver.resolve.value.string
|
11
|
+
assert_equal 'abc', resolver.resolve[0].value.string
|
12
|
+
assert_equal 0, resolver.resolve[1]
|
12
13
|
end
|
13
14
|
|
14
15
|
def test_resolving_multiple_values_evenly_distributed
|
15
16
|
values = weighted_values([['abc', 1], ['def', 1]])
|
16
17
|
|
17
18
|
resolver = Prefab::WeightedValueResolver.new(values, KEY, 'user:001')
|
18
|
-
assert_equal 'abc', resolver.resolve.value.string
|
19
|
+
assert_equal 'abc', resolver.resolve[0].value.string
|
20
|
+
assert_equal 0, resolver.resolve[1]
|
19
21
|
|
20
22
|
resolver = Prefab::WeightedValueResolver.new(values, KEY, 'user:456')
|
21
|
-
assert_equal 'def', resolver.resolve.value.string
|
23
|
+
assert_equal 'def', resolver.resolve[0].value.string
|
24
|
+
assert_equal 1, resolver.resolve[1]
|
22
25
|
end
|
23
26
|
|
24
27
|
def test_resolving_multiple_values_unevenly_distributed
|
25
28
|
values = weighted_values([['abc', 1], ['def', 98], ['ghi', 1]])
|
26
29
|
|
27
30
|
resolver = Prefab::WeightedValueResolver.new(values, KEY, 'user:456')
|
28
|
-
assert_equal 'def', resolver.resolve.value.string
|
31
|
+
assert_equal 'def', resolver.resolve[0].value.string
|
32
|
+
assert_equal 1, resolver.resolve[1]
|
29
33
|
|
30
34
|
resolver = Prefab::WeightedValueResolver.new(values, KEY, 'user:103')
|
31
|
-
assert_equal 'ghi', resolver.resolve.value.string
|
35
|
+
assert_equal 'ghi', resolver.resolve[0].value.string
|
36
|
+
assert_equal 2, resolver.resolve[1]
|
32
37
|
|
33
38
|
resolver = Prefab::WeightedValueResolver.new(values, KEY, 'user:119')
|
34
|
-
assert_equal 'abc', resolver.resolve.value.string
|
39
|
+
assert_equal 'abc', resolver.resolve[0].value.string
|
40
|
+
assert_equal 0, resolver.resolve[1]
|
35
41
|
end
|
36
42
|
|
37
43
|
def test_resolving_multiple_values_with_simulation
|
@@ -39,7 +45,7 @@ class TestWeightedValueResolver < Minitest::Test
|
|
39
45
|
results = {}
|
40
46
|
|
41
47
|
10_000.times do |i|
|
42
|
-
result = Prefab::WeightedValueResolver.new(values, KEY, "user:#{i}").resolve.value.string
|
48
|
+
result = Prefab::WeightedValueResolver.new(values, KEY, "user:#{i}").resolve[0].value.string
|
43
49
|
results[result] ||= 0
|
44
50
|
results[result] += 1
|
45
51
|
end
|