prefab-cloud-ruby 0.24.4 → 0.24.6
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/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
|