prefab-cloud-ruby 0.24.5 → 1.0.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/CHANGELOG.md +15 -0
- data/VERSION +1 -1
- data/compile_protos.sh +7 -0
- data/lib/prefab/client.rb +20 -46
- data/lib/prefab/config_client.rb +9 -12
- data/lib/prefab/config_resolver.rb +2 -1
- data/lib/prefab/config_value_unwrapper.rb +20 -9
- data/lib/prefab/context.rb +43 -7
- data/lib/prefab/context_shape_aggregator.rb +1 -1
- data/lib/prefab/criteria_evaluator.rb +24 -16
- 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/feature_flag_client.rb +0 -2
- data/lib/prefab/log_path_aggregator.rb +1 -1
- data/lib/prefab/logger_client.rb +12 -13
- data/lib/prefab/options.rb +52 -43
- 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 +5 -5
- data/lib/prefab_pb.rb +11 -1
- data/prefab-cloud-ruby.gemspec +14 -9
- data/test/integration_test.rb +1 -3
- data/test/integration_test_helpers.rb +0 -1
- data/test/support/common_helpers.rb +174 -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 +354 -40
- data/test/test_config_client.rb +1 -0
- data/test/test_config_loader.rb +1 -0
- data/test/test_config_resolver.rb +25 -24
- data/test/test_config_value_unwrapper.rb +22 -32
- data/test/test_context.rb +1 -0
- data/test/test_context_shape_aggregator.rb +11 -1
- data/test/test_criteria_evaluator.rb +180 -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_integration.rb +6 -4
- data/test/test_local_config_parser.rb +2 -2
- data/test/test_log_path_aggregator.rb +9 -1
- data/test/test_logger.rb +6 -5
- data/test/test_options.rb +33 -2
- data/test/test_rate_limit_cache.rb +44 -0
- data/test/test_weighted_value_resolver.rb +13 -7
- metadata +13 -8
- data/lib/prefab/evaluated_configs_aggregator.rb +0 -60
- data/lib/prefab/evaluated_keys_aggregator.rb +0 -41
- data/lib/prefab/noop_cache.rb +0 -15
- data/lib/prefab/noop_stats.rb +0 -8
- data/test/test_evaluated_configs_aggregator.rb +0 -254
- data/test/test_evaluated_keys_aggregator.rb +0 -54
@@ -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
|
data/test/test_integration.rb
CHANGED
@@ -25,11 +25,13 @@ class TestIntegration < Minitest::Test
|
|
25
25
|
assert_match(/#{it.expected[:message]}/, err.message)
|
26
26
|
when :nil
|
27
27
|
assert_nil it.test_client.send(it.func, *it.input)
|
28
|
-
when :feature_flag
|
29
|
-
flag, context = *it.input
|
30
|
-
assert_equal it.expected[:value], it.test_client.send(it.func, flag, context)
|
31
28
|
when :simple_equality
|
32
|
-
|
29
|
+
if it.func == :enabled?
|
30
|
+
flag, _default, context = *it.input
|
31
|
+
assert_equal it.expected[:value], it.test_client.send(it.func, flag, context)
|
32
|
+
else
|
33
|
+
assert_equal it.expected[:value], it.test_client.send(it.func, *it.input)
|
34
|
+
end
|
33
35
|
when :log_level
|
34
36
|
assert_equal it.expected[:value].to_sym, it.test_client.send(it.func, *it.input)
|
35
37
|
else
|
@@ -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
|
@@ -8,7 +8,8 @@ class TestLogPathAggregator < Minitest::Test
|
|
8
8
|
SLEEP_TIME = 0.01
|
9
9
|
|
10
10
|
def test_push
|
11
|
-
|
11
|
+
client = new_client
|
12
|
+
aggregator = Prefab::LogPathAggregator.new(client: client, max_paths: 2, sync_interval: 1000)
|
12
13
|
|
13
14
|
aggregator.push('test.test_log_path_aggregator.test_push.1', ::Logger::INFO)
|
14
15
|
aggregator.push('test.test_log_path_aggregator.test_push.2', ::Logger::DEBUG)
|
@@ -18,6 +19,8 @@ class TestLogPathAggregator < Minitest::Test
|
|
18
19
|
# we've reached the limit, so no more
|
19
20
|
aggregator.push('test.test_log_path_aggregator.test_push.3', ::Logger::INFO)
|
20
21
|
assert_equal 2, aggregator.data.size
|
22
|
+
|
23
|
+
assert_only_expected_logs
|
21
24
|
end
|
22
25
|
|
23
26
|
def test_sync
|
@@ -42,6 +45,11 @@ class TestLogPathAggregator < Minitest::Test
|
|
42
45
|
namespace: 'this.is.a.namespace'
|
43
46
|
)
|
44
47
|
]], requests
|
48
|
+
|
49
|
+
assert_logged [
|
50
|
+
'WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client No success loading checkpoints',
|
51
|
+
'ERROR 2023-08-09 15:18:12 -0400: test.test_log_path_aggregator.test_sync here is a message'
|
52
|
+
]
|
45
53
|
end
|
46
54
|
end
|
47
55
|
|
data/test/test_logger.rb
CHANGED
@@ -19,6 +19,7 @@ class TestLogger < Minitest::Test
|
|
19
19
|
)
|
20
20
|
|
21
21
|
def setup
|
22
|
+
super
|
22
23
|
Prefab::LoggerClient.send(:public, :get_path)
|
23
24
|
Prefab::LoggerClient.send(:public, :get_loc_path)
|
24
25
|
Prefab::LoggerClient.send(:public, :level_of)
|
@@ -65,20 +66,20 @@ class TestLogger < Minitest::Test
|
|
65
66
|
assert_equal ::Logger::INFO,
|
66
67
|
@logger.level_of('app.models.user'), 'PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL is info'
|
67
68
|
|
68
|
-
@logger.
|
69
|
+
@logger.config_client = MockConfigClient.new({})
|
69
70
|
assert_equal ::Logger::WARN,
|
70
71
|
@logger.level_of('app.models.user'), 'default is warn'
|
71
72
|
|
72
|
-
@logger.
|
73
|
+
@logger.config_client = MockConfigClient.new('log-level.app' => :INFO)
|
73
74
|
assert_equal ::Logger::INFO,
|
74
75
|
@logger.level_of('app.models.user')
|
75
76
|
|
76
|
-
@logger.
|
77
|
+
@logger.config_client = MockConfigClient.new('log-level.app' => :DEBUG)
|
77
78
|
assert_equal ::Logger::DEBUG,
|
78
79
|
@logger.level_of('app.models.user')
|
79
80
|
|
80
|
-
@logger.
|
81
|
-
|
81
|
+
@logger.config_client = MockConfigClient.new('log-level.app' => :DEBUG,
|
82
|
+
'log-level.app.models' => :ERROR)
|
82
83
|
assert_equal ::Logger::ERROR,
|
83
84
|
@logger.level_of('app.models.user'), 'test leveling'
|
84
85
|
end
|
data/test/test_options.rb
CHANGED
@@ -36,9 +36,40 @@ class TestOptions < Minitest::Test
|
|
36
36
|
assert_equal 0, options.collect_max_paths
|
37
37
|
end
|
38
38
|
|
39
|
-
def
|
39
|
+
def test_collect_max_paths_with_collect_logger_counts_false
|
40
40
|
options = Prefab::Options.new(collect_max_paths: 100,
|
41
|
-
|
41
|
+
collect_logger_counts: false)
|
42
42
|
assert_equal 0, options.collect_max_paths
|
43
43
|
end
|
44
|
+
|
45
|
+
def test_collect_max_evaluation_summaries
|
46
|
+
assert_equal 100_000, Prefab::Options.new.collect_max_evaluation_summaries
|
47
|
+
assert_equal 0, Prefab::Options.new(collect_evaluation_summaries: false).collect_max_evaluation_summaries
|
48
|
+
assert_equal 3,
|
49
|
+
Prefab::Options.new(collect_max_evaluation_summaries: 3).collect_max_evaluation_summaries
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_context_upload_mode_periodic
|
53
|
+
options = Prefab::Options.new(context_upload_mode: :periodic_example, context_max_size: 100)
|
54
|
+
assert_equal 100, options.collect_max_example_contexts
|
55
|
+
|
56
|
+
options = Prefab::Options.new(context_upload_mode: :none)
|
57
|
+
assert_equal 0, options.collect_max_example_contexts
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_context_upload_mode_shape_only
|
61
|
+
options = Prefab::Options.new(context_upload_mode: :shape_only, context_max_size: 100)
|
62
|
+
assert_equal 100, options.collect_max_shapes
|
63
|
+
|
64
|
+
options = Prefab::Options.new(context_upload_mode: :none)
|
65
|
+
assert_equal 0, options.collect_max_shapes
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_context_upload_mode_none
|
69
|
+
options = Prefab::Options.new(context_upload_mode: :none)
|
70
|
+
assert_equal 0, options.collect_max_example_contexts
|
71
|
+
|
72
|
+
options = Prefab::Options.new(context_upload_mode: :none)
|
73
|
+
assert_equal 0, options.collect_max_shapes
|
74
|
+
end
|
44
75
|
end
|