prefab-cloud-ruby 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 +7 -0
- data/.envrc.sample +3 -0
- data/.github/workflows/ruby.yml +46 -0
- data/.gitmodules +3 -0
- data/.rubocop.yml +13 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +169 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +188 -0
- data/LICENSE.txt +20 -0
- data/README.md +94 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/bin/console +21 -0
- data/compile_protos.sh +18 -0
- data/lib/prefab/client.rb +153 -0
- data/lib/prefab/config_client.rb +292 -0
- data/lib/prefab/config_client_presenter.rb +18 -0
- data/lib/prefab/config_loader.rb +84 -0
- data/lib/prefab/config_resolver.rb +77 -0
- data/lib/prefab/config_value_unwrapper.rb +115 -0
- data/lib/prefab/config_value_wrapper.rb +18 -0
- data/lib/prefab/context.rb +179 -0
- data/lib/prefab/context_shape.rb +20 -0
- data/lib/prefab/context_shape_aggregator.rb +65 -0
- data/lib/prefab/criteria_evaluator.rb +136 -0
- data/lib/prefab/encryption.rb +65 -0
- data/lib/prefab/error.rb +6 -0
- data/lib/prefab/errors/env_var_parse_error.rb +11 -0
- data/lib/prefab/errors/initialization_timeout_error.rb +13 -0
- data/lib/prefab/errors/invalid_api_key_error.rb +19 -0
- data/lib/prefab/errors/missing_default_error.rb +13 -0
- data/lib/prefab/errors/missing_env_var_error.rb +11 -0
- data/lib/prefab/errors/uninitialized_error.rb +13 -0
- data/lib/prefab/evaluation.rb +52 -0
- data/lib/prefab/evaluation_summary_aggregator.rb +87 -0
- data/lib/prefab/example_contexts_aggregator.rb +78 -0
- data/lib/prefab/exponential_backoff.rb +21 -0
- data/lib/prefab/feature_flag_client.rb +42 -0
- data/lib/prefab/http_connection.rb +41 -0
- data/lib/prefab/internal_logger.rb +16 -0
- data/lib/prefab/local_config_parser.rb +151 -0
- data/lib/prefab/log_path_aggregator.rb +69 -0
- data/lib/prefab/logger_client.rb +264 -0
- data/lib/prefab/murmer3.rb +50 -0
- data/lib/prefab/options.rb +208 -0
- data/lib/prefab/periodic_sync.rb +69 -0
- data/lib/prefab/prefab.rb +56 -0
- data/lib/prefab/rate_limit_cache.rb +41 -0
- data/lib/prefab/resolved_config_presenter.rb +86 -0
- data/lib/prefab/time_helpers.rb +7 -0
- data/lib/prefab/weighted_value_resolver.rb +42 -0
- data/lib/prefab/yaml_config_parser.rb +34 -0
- data/lib/prefab-cloud-ruby.rb +57 -0
- data/lib/prefab_pb.rb +93 -0
- data/prefab-cloud-ruby.gemspec +155 -0
- data/test/.prefab.default.config.yaml +2 -0
- data/test/.prefab.unit_tests.config.yaml +28 -0
- data/test/integration_test.rb +150 -0
- data/test/integration_test_helpers.rb +151 -0
- data/test/support/common_helpers.rb +180 -0
- data/test/support/mock_base_client.rb +42 -0
- data/test/support/mock_config_client.rb +19 -0
- data/test/support/mock_config_loader.rb +1 -0
- data/test/test_client.rb +444 -0
- data/test/test_config_client.rb +109 -0
- data/test/test_config_loader.rb +117 -0
- data/test/test_config_resolver.rb +430 -0
- data/test/test_config_value_unwrapper.rb +224 -0
- data/test/test_config_value_wrapper.rb +42 -0
- data/test/test_context.rb +203 -0
- data/test/test_context_shape.rb +50 -0
- data/test/test_context_shape_aggregator.rb +147 -0
- data/test/test_criteria_evaluator.rb +726 -0
- data/test/test_encryption.rb +16 -0
- data/test/test_evaluation_summary_aggregator.rb +162 -0
- data/test/test_example_contexts_aggregator.rb +238 -0
- data/test/test_exponential_backoff.rb +18 -0
- data/test/test_feature_flag_client.rb +48 -0
- data/test/test_helper.rb +17 -0
- data/test/test_integration.rb +58 -0
- data/test/test_local_config_parser.rb +147 -0
- data/test/test_log_path_aggregator.rb +62 -0
- data/test/test_logger.rb +621 -0
- data/test/test_logger_initialization.rb +12 -0
- data/test/test_options.rb +75 -0
- data/test/test_prefab.rb +12 -0
- data/test/test_rate_limit_cache.rb +44 -0
- data/test/test_weighted_value_resolver.rb +71 -0
- metadata +337 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class IntegrationTest
|
4
|
+
attr_reader :func, :input, :expected, :data, :expected_data, :aggregator, :endpoint, :test_client
|
5
|
+
|
6
|
+
def initialize(test_data)
|
7
|
+
@client_overrides = parse_client_overrides(test_data['client_overrides'])
|
8
|
+
@func = parse_function(test_data['function'])
|
9
|
+
@input = parse_input(test_data['input'])
|
10
|
+
@expected = parse_expected(test_data['expected'])
|
11
|
+
@data = test_data['data']
|
12
|
+
@expected_data = test_data['expected_data'] || []
|
13
|
+
@aggregator = test_data['aggregator']
|
14
|
+
@endpoint = test_data['endpoint']
|
15
|
+
@test_client = capture_telemetry(base_client)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_type
|
19
|
+
if @data
|
20
|
+
:telemetry
|
21
|
+
elsif @input[0] && @input[0].start_with?('log-level.')
|
22
|
+
:log_level
|
23
|
+
elsif @expected[:status] == 'raise'
|
24
|
+
:raise
|
25
|
+
elsif @expected[:value].nil?
|
26
|
+
:nil
|
27
|
+
else
|
28
|
+
:simple_equality
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def last_data_sent
|
33
|
+
test_client.last_data_sent
|
34
|
+
end
|
35
|
+
|
36
|
+
def last_post_result
|
37
|
+
test_client.last_post_result
|
38
|
+
end
|
39
|
+
|
40
|
+
def last_post_endpoint
|
41
|
+
test_client.last_post_endpoint
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def parse_client_overrides(overrides)
|
47
|
+
Hash[
|
48
|
+
(overrides || {}).map do |(k, v)|
|
49
|
+
[k.to_sym, v]
|
50
|
+
end
|
51
|
+
]
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_function(function)
|
55
|
+
case function
|
56
|
+
when 'get_or_raise' then :get
|
57
|
+
when 'enabled' then :enabled?
|
58
|
+
else :"#{function}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_input(input)
|
63
|
+
return nil if input.nil?
|
64
|
+
|
65
|
+
if input['key']
|
66
|
+
parse_config_input(input)
|
67
|
+
elsif input['flag']
|
68
|
+
parse_ff_input(input)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_config_input(input)
|
73
|
+
if !input['default'].nil?
|
74
|
+
[input['key'], input['default']]
|
75
|
+
else
|
76
|
+
[input['key']]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_ff_input(input)
|
81
|
+
[input['flag'], input['default'], input['context']]
|
82
|
+
end
|
83
|
+
|
84
|
+
def parse_expected(expected)
|
85
|
+
return {} if expected.nil?
|
86
|
+
|
87
|
+
{
|
88
|
+
status: expected['status'],
|
89
|
+
error: parse_error_type(expected['error']),
|
90
|
+
message: expected['message'],
|
91
|
+
value: expected['value']
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_error_type(error_type)
|
96
|
+
case error_type
|
97
|
+
when 'missing_default' then Prefab::Errors::MissingDefaultError
|
98
|
+
when 'initialization_timeout' then Prefab::Errors::InitializationTimeoutError
|
99
|
+
when 'unable_to_decrypt' then OpenSSL::Cipher::CipherError
|
100
|
+
when 'missing_env_var' then Prefab::Errors::MissingEnvVarError
|
101
|
+
when 'unable_to_coerce_env_var' then Prefab::Errors::EnvVarParseError
|
102
|
+
else
|
103
|
+
unless error_type.nil?
|
104
|
+
throw "Unknown error type: #{error_type}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def base_client
|
110
|
+
@_base_client ||= Prefab::Client.new(base_client_options)
|
111
|
+
end
|
112
|
+
|
113
|
+
def base_client_options
|
114
|
+
@_options ||= Prefab::Options.new(**{
|
115
|
+
prefab_config_override_dir: 'none',
|
116
|
+
prefab_config_classpath_dir: 'test',
|
117
|
+
prefab_envs: ['unit_tests'],
|
118
|
+
prefab_datasources: Prefab::Options::DATASOURCES::ALL,
|
119
|
+
api_key: ENV['PREFAB_INTEGRATION_TEST_API_KEY'],
|
120
|
+
prefab_api_url: 'https://api.staging-prefab.cloud',
|
121
|
+
}.merge(@client_overrides))
|
122
|
+
end
|
123
|
+
|
124
|
+
def capture_telemetry(client)
|
125
|
+
client.define_singleton_method(:post) do |url, data|
|
126
|
+
client.instance_variable_set(:@last_data_sent, data)
|
127
|
+
client.instance_variable_set(:@last_post_endpoint, url)
|
128
|
+
|
129
|
+
result = super(url, data)
|
130
|
+
|
131
|
+
client.instance_variable_set(:@last_post_result, result)
|
132
|
+
|
133
|
+
result
|
134
|
+
end
|
135
|
+
|
136
|
+
client.define_singleton_method(:last_data_sent) do
|
137
|
+
client.instance_variable_get(:@last_data_sent)
|
138
|
+
end
|
139
|
+
|
140
|
+
client.define_singleton_method(:last_post_endpoint) do
|
141
|
+
client.instance_variable_get(:@last_post_endpoint)
|
142
|
+
end
|
143
|
+
|
144
|
+
client.define_singleton_method(:last_post_result) do
|
145
|
+
client.instance_variable_get(:@last_post_result)
|
146
|
+
end
|
147
|
+
|
148
|
+
client
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IntegrationTestHelpers
|
4
|
+
SUBMODULE_PATH = 'test/prefab-cloud-integration-test-data'
|
5
|
+
RAISE_IF_NO_TESTS_FOUND = ENV['PREFAB_INTEGRATION_TEST_RAISE'] == 'true'
|
6
|
+
|
7
|
+
def self.find_integration_tests
|
8
|
+
version = find_integration_test_version
|
9
|
+
|
10
|
+
files = find_versioned_test_files(version)
|
11
|
+
|
12
|
+
if files.none?
|
13
|
+
message = "No integration tests found for version: #{version}"
|
14
|
+
raise message if RAISE_IF_NO_TESTS_FOUND
|
15
|
+
|
16
|
+
puts message
|
17
|
+
end
|
18
|
+
|
19
|
+
files
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.find_integration_test_version
|
23
|
+
File.read(File.join(SUBMODULE_PATH, 'version')).strip
|
24
|
+
rescue StandardError => e
|
25
|
+
puts "No version found for integration tests: #{e.message}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.find_versioned_test_files(version)
|
29
|
+
if version.nil?
|
30
|
+
[]
|
31
|
+
else
|
32
|
+
Dir[File.join(SUBMODULE_PATH, "tests/#{version}/**/*")]
|
33
|
+
.select { |file| file =~ /\.ya?ml$/ }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
SEVERITY_LOOKUP = Prefab::LogPathAggregator::SEVERITY_KEY.invert
|
38
|
+
|
39
|
+
def self.prepare_post_data(it)
|
40
|
+
case it.aggregator
|
41
|
+
when "log_path"
|
42
|
+
aggregator = it.test_client.log_path_aggregator
|
43
|
+
|
44
|
+
it.data.each do |data|
|
45
|
+
data['counts'].each_pair do |severity, count|
|
46
|
+
count.times { aggregator.push(data['logger_name'], SEVERITY_LOOKUP[severity]) }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
expected_loggers = Hash.new { |h, k| h[k] = PrefabProto::Logger.new }
|
51
|
+
|
52
|
+
it.expected_data.each do |data|
|
53
|
+
data["counts"].each do |(severity, count)|
|
54
|
+
expected_loggers[data["logger_name"]][severity] = count
|
55
|
+
expected_loggers[data["logger_name"]]["logger_name"] = data["logger_name"]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
[aggregator, ->(data) { data.loggers }, expected_loggers.values]
|
60
|
+
when "context_shape"
|
61
|
+
aggregator = it.test_client.context_shape_aggregator
|
62
|
+
|
63
|
+
context = Prefab::Context.new(it.data)
|
64
|
+
|
65
|
+
aggregator.push(context)
|
66
|
+
|
67
|
+
expected = it.expected_data.map do |data|
|
68
|
+
PrefabProto::ContextShape.new(
|
69
|
+
name: data["name"],
|
70
|
+
field_types: data["field_types"]
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
[aggregator, ->(data) { data.shapes }, expected]
|
75
|
+
when "evaluation_summary"
|
76
|
+
aggregator = it.test_client.evaluation_summary_aggregator
|
77
|
+
|
78
|
+
aggregator.instance_variable_set("@data", Concurrent::Hash.new)
|
79
|
+
|
80
|
+
it.data.each do |key|
|
81
|
+
it.test_client.get(key)
|
82
|
+
end
|
83
|
+
|
84
|
+
expected_data = []
|
85
|
+
it.expected_data.each do |data|
|
86
|
+
value = if data["value_type"] == "string_list"
|
87
|
+
PrefabProto::StringList.new(values: data["value"])
|
88
|
+
else
|
89
|
+
data["value"]
|
90
|
+
end
|
91
|
+
expected_data << PrefabProto::ConfigEvaluationSummary.new(
|
92
|
+
key: data["key"],
|
93
|
+
type: data["type"].to_sym,
|
94
|
+
counters: [
|
95
|
+
PrefabProto::ConfigEvaluationCounter.new(
|
96
|
+
count: data["count"],
|
97
|
+
config_id: 0,
|
98
|
+
selected_value: PrefabProto::ConfigValue.new(data["value_type"] => value),
|
99
|
+
config_row_index: data["summary"]["config_row_index"],
|
100
|
+
conditional_value_index: data["summary"]["conditional_value_index"] || 0,
|
101
|
+
weighted_value_index: data["summary"]["weighted_value_index"],
|
102
|
+
reason: :UNKNOWN
|
103
|
+
)
|
104
|
+
]
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
[aggregator, ->(data) {
|
109
|
+
data.events[0].summaries.summaries.each { |e|
|
110
|
+
e.counters.each { |c|
|
111
|
+
c.config_id = 0
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}, expected_data]
|
115
|
+
when "example_contexts"
|
116
|
+
aggregator = it.test_client.example_contexts_aggregator
|
117
|
+
|
118
|
+
it.data.each do |key, values|
|
119
|
+
aggregator.record(Prefab::Context.new({ key => values }))
|
120
|
+
end
|
121
|
+
|
122
|
+
expected_data = []
|
123
|
+
it.expected_data.each do |k, vs|
|
124
|
+
expected_data << PrefabProto::ExampleContext.new(
|
125
|
+
timestamp: 0,
|
126
|
+
contextSet: PrefabProto::ContextSet.new(
|
127
|
+
contexts: [
|
128
|
+
PrefabProto::Context.new(
|
129
|
+
type: k,
|
130
|
+
values: vs.each_pair.map do |key, value|
|
131
|
+
[key, Prefab::ConfigValueWrapper.wrap(value)]
|
132
|
+
end.to_h
|
133
|
+
)
|
134
|
+
]
|
135
|
+
)
|
136
|
+
)
|
137
|
+
end
|
138
|
+
[aggregator, ->(data) { data.events[0].example_contexts.examples.each { |e| e.timestamp = 0 } }, expected_data]
|
139
|
+
else
|
140
|
+
puts "unknown aggregator #{it.aggregator}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.with_parent_context_maybe(context, &block)
|
145
|
+
if context
|
146
|
+
Prefab::Context.with_context(context, &block)
|
147
|
+
else
|
148
|
+
yield
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,180 @@
|
|
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
|
+
|
48
|
+
config = overrides.delete(:config)
|
49
|
+
project_env_id = overrides.delete(:project_env_id)
|
50
|
+
|
51
|
+
Prefab::Client.new(prefab_options(overrides)).tap do |client|
|
52
|
+
inject_config(client, config) if config
|
53
|
+
|
54
|
+
client.resolver.project_env_id = project_env_id if project_env_id
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def prefab_options(overrides = {})
|
59
|
+
$logs ||= StringIO.new
|
60
|
+
Prefab::Options.new(
|
61
|
+
**DEFAULT_NEW_CLIENT_OPTIONS.merge(
|
62
|
+
overrides.merge(logdev: $logs)
|
63
|
+
)
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def string_list(values)
|
68
|
+
PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: values))
|
69
|
+
end
|
70
|
+
|
71
|
+
def inject_config(client, config)
|
72
|
+
resolver = client.config_client.instance_variable_get('@config_resolver')
|
73
|
+
store = resolver.instance_variable_get('@local_store')
|
74
|
+
|
75
|
+
Array(config).each do |c|
|
76
|
+
store[c.key] = { config: c }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def inject_project_env_id(client, project_env_id)
|
81
|
+
resolver = client.config_client.instance_variable_get('@config_resolver')
|
82
|
+
resolver.project_env_id = project_env_id
|
83
|
+
end
|
84
|
+
|
85
|
+
FakeResponse = Struct.new(:status, :body)
|
86
|
+
|
87
|
+
def wait_for(condition, max_wait: 2, sleep_time: 0.01)
|
88
|
+
wait_time = 0
|
89
|
+
while !condition.call
|
90
|
+
wait_time += sleep_time
|
91
|
+
sleep sleep_time
|
92
|
+
|
93
|
+
raise "Waited #{max_wait} seconds for the condition to be true, but it never was" if wait_time > max_wait
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def wait_for_post_requests(client, max_wait: 2, sleep_time: 0.01)
|
98
|
+
# we use ivars to avoid re-mocking the post method on subsequent calls
|
99
|
+
client.instance_variable_set("@_requests", [])
|
100
|
+
|
101
|
+
if !client.instance_variable_get("@_already_faked_post")
|
102
|
+
client.define_singleton_method(:post) do |*params|
|
103
|
+
@_requests.push(params)
|
104
|
+
|
105
|
+
FakeResponse.new(200, '')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
client.instance_variable_set("@_already_faked_post", true)
|
110
|
+
|
111
|
+
yield
|
112
|
+
|
113
|
+
# let the flush thread run
|
114
|
+
wait_for -> { client.instance_variable_get("@_requests").size > 0 }, max_wait: max_wait, sleep_time: sleep_time
|
115
|
+
|
116
|
+
client.instance_variable_get("@_requests")
|
117
|
+
end
|
118
|
+
|
119
|
+
def assert_summary(client, data)
|
120
|
+
raise 'Evaluation summary aggregator not enabled' unless client.evaluation_summary_aggregator
|
121
|
+
|
122
|
+
assert_equal data, client.evaluation_summary_aggregator.data
|
123
|
+
end
|
124
|
+
|
125
|
+
def assert_example_contexts(client, data)
|
126
|
+
raise 'Example contexts aggregator not enabled' unless client.example_contexts_aggregator
|
127
|
+
|
128
|
+
assert_equal data, client.example_contexts_aggregator.data
|
129
|
+
end
|
130
|
+
|
131
|
+
def weighted_values(values_and_weights, hash_by_property_name: 'user.key')
|
132
|
+
values = values_and_weights.map do |value, weight|
|
133
|
+
weighted_value(value, weight)
|
134
|
+
end
|
135
|
+
|
136
|
+
PrefabProto::WeightedValues.new(weighted_values: values, hash_by_property_name: hash_by_property_name)
|
137
|
+
end
|
138
|
+
|
139
|
+
def weighted_value(string, weight)
|
140
|
+
PrefabProto::WeightedValue.new(
|
141
|
+
value: PrefabProto::ConfigValue.new(string: string), weight: weight
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
def context(properties)
|
146
|
+
Prefab::Context.new(properties)
|
147
|
+
end
|
148
|
+
|
149
|
+
def assert_only_expected_logs
|
150
|
+
assert_equal "WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client.configclient No success loading checkpoints\n", $logs.string
|
151
|
+
# mark nil to indicate we handled it
|
152
|
+
$logs = nil
|
153
|
+
end
|
154
|
+
|
155
|
+
def assert_logged(expected)
|
156
|
+
# we do a uniq here because logging can happen in a separate thread so the
|
157
|
+
# number of times a log might happen could be slightly variable.
|
158
|
+
assert_equal expected, $logs.string.split("\n").uniq
|
159
|
+
# mark nil to indicate we handled it
|
160
|
+
$logs = nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def assert_stderr(expected)
|
164
|
+
assert ($stderr.string.split("\n").uniq & expected).size > 0
|
165
|
+
|
166
|
+
# Ruby 2.X has a lot of warnings about instance variables not being
|
167
|
+
# initialized so we don't try to assert on stderr for those versions.
|
168
|
+
# Instead we just stop after asserting that our expected errors are
|
169
|
+
# included in the output.
|
170
|
+
if RUBY_VERSION.start_with?('2.')
|
171
|
+
puts $stderr.string
|
172
|
+
return
|
173
|
+
end
|
174
|
+
|
175
|
+
assert_equal expected, $stderr.string.split("\n")
|
176
|
+
|
177
|
+
# restore since we've handled it
|
178
|
+
$stderr = $oldstderr
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,42 @@
|
|
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
|
+
@config_client = MockConfigClient.new
|
13
|
+
Prefab::LoggerClient.new(options.logdev)
|
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 context_shape_aggregator; end
|
34
|
+
|
35
|
+
def evaluation_summary_aggregator; end
|
36
|
+
|
37
|
+
def example_contexts_aggregator; end
|
38
|
+
|
39
|
+
def config_value(key)
|
40
|
+
@config_values[key]
|
41
|
+
end
|
42
|
+
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
|