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,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommonHelpers
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
def setup
|
7
|
+
$oldstderr, $stderr = $stderr, StringIO.new
|
8
|
+
|
9
|
+
$logs = nil
|
10
|
+
Timecop.freeze('2023-08-09 15:18:12 -0400')
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
if $logs && !$logs.string.empty?
|
15
|
+
raise "Unexpected logs. Handle logs with assert_only_expected_logs or assert_logged\n\n#{$logs.string}"
|
16
|
+
end
|
17
|
+
|
18
|
+
if $stderr != $oldstderr && !$stderr.string.empty?
|
19
|
+
# we ignore 2.X because of the number of `instance variable @xyz not initialized` warnings
|
20
|
+
if !RUBY_VERSION.start_with?('2.')
|
21
|
+
raise "Unexpected stderr. Handle stderr with assert_stderr\n\n#{$stderr.string}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
$stderr = $oldstderr
|
26
|
+
|
27
|
+
Timecop.return
|
28
|
+
end
|
29
|
+
|
30
|
+
def with_env(key, value, &block)
|
31
|
+
old_value = ENV.fetch(key, nil)
|
32
|
+
|
33
|
+
ENV[key] = value
|
34
|
+
block.call
|
35
|
+
ensure
|
36
|
+
ENV[key] = old_value
|
37
|
+
end
|
38
|
+
|
39
|
+
DEFAULT_NEW_CLIENT_OPTIONS = {
|
40
|
+
prefab_config_override_dir: 'none',
|
41
|
+
prefab_config_classpath_dir: 'test',
|
42
|
+
prefab_envs: ['unit_tests'],
|
43
|
+
prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
|
44
|
+
}.freeze
|
45
|
+
|
46
|
+
def new_client(overrides = {})
|
47
|
+
$logs ||= StringIO.new
|
48
|
+
|
49
|
+
config = overrides.delete(:config)
|
50
|
+
project_env_id = overrides.delete(:project_env_id)
|
51
|
+
|
52
|
+
options = Prefab::Options.new(
|
53
|
+
**DEFAULT_NEW_CLIENT_OPTIONS.merge(
|
54
|
+
overrides.merge(logdev: $logs)
|
55
|
+
)
|
56
|
+
)
|
57
|
+
|
58
|
+
Prefab::Client.new(options).tap do |client|
|
59
|
+
inject_config(client, config) if config
|
60
|
+
|
61
|
+
client.resolver.project_env_id = project_env_id if project_env_id
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def string_list(values)
|
66
|
+
PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: values))
|
67
|
+
end
|
68
|
+
|
69
|
+
def inject_config(client, config)
|
70
|
+
resolver = client.config_client.instance_variable_get('@config_resolver')
|
71
|
+
store = resolver.instance_variable_get('@local_store')
|
72
|
+
|
73
|
+
Array(config).each do |c|
|
74
|
+
store[c.key] = { config: c }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def inject_project_env_id(client, project_env_id)
|
79
|
+
resolver = client.config_client.instance_variable_get('@config_resolver')
|
80
|
+
resolver.project_env_id = project_env_id
|
81
|
+
end
|
82
|
+
|
83
|
+
FakeResponse = Struct.new(:status, :body)
|
84
|
+
|
85
|
+
def wait_for_post_requests(client, max_wait: 2, sleep_time: 0.01)
|
86
|
+
# we use ivars to avoid re-mocking the post method on subsequent calls
|
87
|
+
client.instance_variable_set("@_requests", [])
|
88
|
+
|
89
|
+
if !client.instance_variable_get("@_already_faked_post")
|
90
|
+
client.define_singleton_method(:post) do |*params|
|
91
|
+
@_requests.push(params)
|
92
|
+
|
93
|
+
FakeResponse.new(200, '')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
client.instance_variable_set("@_already_faked_post", true)
|
98
|
+
|
99
|
+
yield
|
100
|
+
|
101
|
+
# let the flush thread run
|
102
|
+
wait_time = 0
|
103
|
+
while client.instance_variable_get("@_requests").empty?
|
104
|
+
wait_time += sleep_time
|
105
|
+
sleep sleep_time
|
106
|
+
|
107
|
+
raise "Waited #{max_wait} seconds for the flush thread to run, but it never did" if wait_time > max_wait
|
108
|
+
end
|
109
|
+
|
110
|
+
client.instance_variable_get("@_requests")
|
111
|
+
end
|
112
|
+
|
113
|
+
def assert_summary(client, data)
|
114
|
+
raise 'Evaluation summary aggregator not enabled' unless client.evaluation_summary_aggregator
|
115
|
+
|
116
|
+
assert_equal data, client.evaluation_summary_aggregator.data
|
117
|
+
end
|
118
|
+
|
119
|
+
def assert_example_contexts(client, data)
|
120
|
+
raise 'Example contexts aggregator not enabled' unless client.example_contexts_aggregator
|
121
|
+
|
122
|
+
assert_equal data, client.example_contexts_aggregator.data
|
123
|
+
end
|
124
|
+
|
125
|
+
def weighted_values(values_and_weights, hash_by_property_name: 'user.key')
|
126
|
+
values = values_and_weights.map do |value, weight|
|
127
|
+
weighted_value(value, weight)
|
128
|
+
end
|
129
|
+
|
130
|
+
PrefabProto::WeightedValues.new(weighted_values: values, hash_by_property_name: hash_by_property_name)
|
131
|
+
end
|
132
|
+
|
133
|
+
def weighted_value(string, weight)
|
134
|
+
PrefabProto::WeightedValue.new(
|
135
|
+
value: PrefabProto::ConfigValue.new(string: string), weight: weight
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def context(properties)
|
140
|
+
Prefab::Context.new(properties)
|
141
|
+
end
|
142
|
+
|
143
|
+
def assert_only_expected_logs
|
144
|
+
assert_equal "WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client No success loading checkpoints\n", $logs.string
|
145
|
+
# mark nil to indicate we handled it
|
146
|
+
$logs = nil
|
147
|
+
end
|
148
|
+
|
149
|
+
def assert_logged(expected)
|
150
|
+
# we do a uniq here because logging can happen in a separate thread so the
|
151
|
+
# number of times a log might happen could be slightly variable.
|
152
|
+
assert_equal expected, $logs.string.split("\n").uniq
|
153
|
+
# mark nil to indicate we handled it
|
154
|
+
$logs = nil
|
155
|
+
end
|
156
|
+
|
157
|
+
def assert_stderr(expected)
|
158
|
+
assert ($stderr.string.split("\n").uniq & expected).size > 0
|
159
|
+
|
160
|
+
# Ruby 2.X has a lot of warnings about instance variables not being
|
161
|
+
# initialized so we don't try to assert on stderr for those versions.
|
162
|
+
# Instead we just stop after asserting that our expected errors are
|
163
|
+
# included in the output.
|
164
|
+
if RUBY_VERSION.start_with?('2.')
|
165
|
+
puts $stderr.string
|
166
|
+
return
|
167
|
+
end
|
168
|
+
|
169
|
+
assert_equal expected, $stderr.string.split("\n")
|
170
|
+
|
171
|
+
# restore since we've handled it
|
172
|
+
$stderr = $oldstderr
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MockBaseClient
|
4
|
+
STAGING_ENV_ID = 1
|
5
|
+
PRODUCTION_ENV_ID = 2
|
6
|
+
TEST_ENV_ID = 3
|
7
|
+
attr_reader :namespace, :logger, :config_client, :options, :posts
|
8
|
+
|
9
|
+
def initialize(options = Prefab::Options.new)
|
10
|
+
@options = options
|
11
|
+
@namespace = namespace
|
12
|
+
@logger = Prefab::LoggerClient.new($stdout)
|
13
|
+
@config_client = MockConfigClient.new
|
14
|
+
@posts = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def instance_hash
|
18
|
+
'mock-base-client-instance-hash'
|
19
|
+
end
|
20
|
+
|
21
|
+
def project_id
|
22
|
+
1
|
23
|
+
end
|
24
|
+
|
25
|
+
def post(_, _)
|
26
|
+
raise 'Use wait_for_post_requests'
|
27
|
+
end
|
28
|
+
|
29
|
+
def log
|
30
|
+
@logger
|
31
|
+
end
|
32
|
+
|
33
|
+
def log_internal(level, message); end
|
34
|
+
|
35
|
+
def context_shape_aggregator; end
|
36
|
+
|
37
|
+
def evaluation_summary_aggregator; end
|
38
|
+
|
39
|
+
def example_contexts_aggregator; end
|
40
|
+
|
41
|
+
def config_value(key)
|
42
|
+
@config_values[key]
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MockConfigClient
|
4
|
+
def initialize(config_values = {})
|
5
|
+
@config_values = config_values
|
6
|
+
end
|
7
|
+
|
8
|
+
def get(key, default = nil)
|
9
|
+
@config_values.fetch(key, default)
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_config(key)
|
13
|
+
PrefabProto::Config.new(value: @config_values[key], key: key)
|
14
|
+
end
|
15
|
+
|
16
|
+
def mock_this_config(key, config_value)
|
17
|
+
@config_values[key] = config_value
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# frozen_string_literal: true
|