prefab-cloud-ruby 0.24.5 → 0.24.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/VERSION +1 -1
  4. data/compile_protos.sh +7 -0
  5. data/lib/prefab/client.rb +17 -4
  6. data/lib/prefab/config_client.rb +8 -9
  7. data/lib/prefab/config_value_unwrapper.rb +20 -9
  8. data/lib/prefab/context.rb +39 -7
  9. data/lib/prefab/context_shape_aggregator.rb +1 -1
  10. data/lib/prefab/criteria_evaluator.rb +24 -16
  11. data/lib/prefab/evaluated_keys_aggregator.rb +1 -1
  12. data/lib/prefab/evaluation.rb +48 -0
  13. data/lib/prefab/evaluation_summary_aggregator.rb +85 -0
  14. data/lib/prefab/example_contexts_aggregator.rb +76 -0
  15. data/lib/prefab/exponential_backoff.rb +5 -0
  16. data/lib/prefab/log_path_aggregator.rb +1 -1
  17. data/lib/prefab/logger_client.rb +12 -13
  18. data/lib/prefab/options.rb +27 -14
  19. data/lib/prefab/periodic_sync.rb +30 -13
  20. data/lib/prefab/rate_limit_cache.rb +41 -0
  21. data/lib/prefab/resolved_config_presenter.rb +2 -4
  22. data/lib/prefab/weighted_value_resolver.rb +1 -1
  23. data/lib/prefab-cloud-ruby.rb +5 -2
  24. data/lib/prefab_pb.rb +11 -1
  25. data/prefab-cloud-ruby.gemspec +14 -5
  26. data/test/support/common_helpers.rb +105 -0
  27. data/test/support/mock_base_client.rb +44 -0
  28. data/test/support/mock_config_client.rb +19 -0
  29. data/test/support/mock_config_loader.rb +1 -0
  30. data/test/test_client.rb +257 -2
  31. data/test/test_config_resolver.rb +25 -24
  32. data/test/test_config_value_unwrapper.rb +22 -32
  33. data/test/test_context_shape_aggregator.rb +0 -1
  34. data/test/test_criteria_evaluator.rb +179 -133
  35. data/test/test_evaluation_summary_aggregator.rb +162 -0
  36. data/test/test_example_contexts_aggregator.rb +238 -0
  37. data/test/test_helper.rb +5 -131
  38. data/test/test_local_config_parser.rb +2 -2
  39. data/test/test_logger.rb +5 -5
  40. data/test/test_options.rb +8 -0
  41. data/test/test_rate_limit_cache.rb +44 -0
  42. data/test/test_weighted_value_resolver.rb +13 -7
  43. metadata +13 -4
  44. data/lib/prefab/evaluated_configs_aggregator.rb +0 -60
  45. 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
- class MockBaseClient
11
- STAGING_ENV_ID = 1
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
- def inject_project_env_id(client, project_env_id)
115
- resolver = client.config_client.instance_variable_get('@config_resolver')
116
- resolver.project_env_id = project_env_id
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.unwrap(value_row.value, key, {})
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.unwrap(value_row.value, key, {})
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.set_config_client(MockConfigClient.new({}))
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.set_config_client(MockConfigClient.new('log-level.app' => :INFO))
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.set_config_client(MockConfigClient.new('log-level.app' => :DEBUG))
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.set_config_client(MockConfigClient.new('log-level.app' => :DEBUG,
81
- 'log-level.app.models' => :ERROR))
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