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,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestLocalConfigParser < Minitest::Test
|
6
|
+
FILE_NAME = 'example-config.yaml'
|
7
|
+
DEFAULT_MATCH = 'default'
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
@mock_resolver = MockResolver.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_parse_int_config
|
15
|
+
key = :sample_int
|
16
|
+
parsed = Prefab::LocalConfigParser.parse(key, 123, {}, FILE_NAME)[key]
|
17
|
+
config = parsed[:config]
|
18
|
+
|
19
|
+
assert_equal FILE_NAME, parsed[:source]
|
20
|
+
assert_equal DEFAULT_MATCH, parsed[:match]
|
21
|
+
assert_equal :CONFIG, config.config_type
|
22
|
+
assert_equal key.to_s, config.key
|
23
|
+
assert_equal 1, config.rows.size
|
24
|
+
assert_equal 1, config.rows[0].values.size
|
25
|
+
assert_equal 123, config.rows[0].values[0].value.int
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_flag_with_a_value
|
29
|
+
key = :flag_with_a_value
|
30
|
+
value = stringify_keys({ feature_flag: true, value: 'all-features' })
|
31
|
+
parsed = Prefab::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
|
32
|
+
config = parsed[:config]
|
33
|
+
|
34
|
+
assert_equal FILE_NAME, parsed[:source]
|
35
|
+
assert_equal key, parsed[:match]
|
36
|
+
assert_equal :FEATURE_FLAG, config.config_type
|
37
|
+
assert_equal key.to_s, config.key
|
38
|
+
assert_equal 1, config.rows.size
|
39
|
+
assert_equal 1, config.rows[0].values.size
|
40
|
+
|
41
|
+
value_row = config.rows[0].values[0]
|
42
|
+
assert_equal 'all-features', Prefab::ConfigValueUnwrapper.deepest_value(value_row.value, key, {}, @mock_resolver).unwrap
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_flag_in_user_key
|
46
|
+
key = :flag_in_user_key
|
47
|
+
value = stringify_keys({ 'feature_flag': 'true', value: true,
|
48
|
+
criterion: { operator: 'PROP_IS_ONE_OF', property: 'user.key', values: %w[abc123 xyz987] } })
|
49
|
+
parsed = Prefab::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
|
50
|
+
config = parsed[:config]
|
51
|
+
|
52
|
+
assert_equal FILE_NAME, parsed[:source]
|
53
|
+
assert_equal key, parsed[:match]
|
54
|
+
assert_equal :FEATURE_FLAG, config.config_type
|
55
|
+
assert_equal key.to_s, config.key
|
56
|
+
assert_equal 1, config.rows.size
|
57
|
+
assert_equal 1, config.rows[0].values.size
|
58
|
+
assert_equal 1, config.rows[0].values[0].criteria.size
|
59
|
+
|
60
|
+
value_row = config.rows[0].values[0]
|
61
|
+
assert_equal true, Prefab::ConfigValueUnwrapper.deepest_value(value_row.value, key, {}, @mock_resolver).unwrap
|
62
|
+
|
63
|
+
assert_equal 'user.key', value_row.criteria[0].property_name
|
64
|
+
assert_equal :PROP_IS_ONE_OF, value_row.criteria[0].operator
|
65
|
+
assert_equal %w[abc123 xyz987], value_row.criteria[0].value_to_match.string_list.values
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_provided_values
|
69
|
+
with_env('LOOKUP_ENV', 'from env') do
|
70
|
+
key = :test_provided
|
71
|
+
value = stringify_keys({type: 'provided', source: 'ENV_VAR', lookup: 'LOOKUP_ENV'})
|
72
|
+
parsed = Prefab::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
|
73
|
+
config = parsed[:config]
|
74
|
+
|
75
|
+
assert_equal FILE_NAME, parsed[:source]
|
76
|
+
assert_equal 'LOOKUP_ENV', parsed[:match]
|
77
|
+
assert_equal :CONFIG, config.config_type
|
78
|
+
assert_equal key.to_s, config.key
|
79
|
+
assert_equal 1, config.rows.size
|
80
|
+
assert_equal 1, config.rows[0].values.size
|
81
|
+
|
82
|
+
value_row = config.rows[0].values[0]
|
83
|
+
provided = value_row.value.provided
|
84
|
+
assert_equal :ENV_VAR, provided.source
|
85
|
+
assert_equal 'LOOKUP_ENV', provided.lookup
|
86
|
+
assert_equal 'from env', Prefab::ConfigValueUnwrapper.deepest_value(value_row.value, config, {}, @mock_resolver).unwrap
|
87
|
+
reportable_value = Prefab::ConfigValueUnwrapper.deepest_value(value_row.value, config, {}, @mock_resolver).reportable_value
|
88
|
+
assert_equal 'from env', reportable_value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_confidential_provided_values
|
93
|
+
with_env('LOOKUP_ENV', 'from env') do
|
94
|
+
key = :test_provided
|
95
|
+
value = stringify_keys({type: 'provided', source: 'ENV_VAR', lookup: 'LOOKUP_ENV', confidential: true})
|
96
|
+
parsed = Prefab::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
|
97
|
+
config = parsed[:config]
|
98
|
+
|
99
|
+
value_row = config.rows[0].values[0]
|
100
|
+
provided = value_row.value.provided
|
101
|
+
assert_equal :ENV_VAR, provided.source
|
102
|
+
assert_equal 'LOOKUP_ENV', provided.lookup
|
103
|
+
assert_equal 'from env', Prefab::ConfigValueUnwrapper.deepest_value(value_row.value, config, {}, @mock_resolver).unwrap
|
104
|
+
reportable_value = Prefab::ConfigValueUnwrapper.deepest_value(value_row.value, config, {}, @mock_resolver).reportable_value
|
105
|
+
assert reportable_value.start_with? Prefab::ConfigValueUnwrapper::CONFIDENTIAL_PREFIX
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_confidential_values
|
110
|
+
key = :test_confidential
|
111
|
+
value = stringify_keys({value: 'a confidential string', confidential: true})
|
112
|
+
parsed = Prefab::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
|
113
|
+
config = parsed[:config]
|
114
|
+
|
115
|
+
assert_equal FILE_NAME, parsed[:source]
|
116
|
+
assert_equal :CONFIG, config.config_type
|
117
|
+
assert_equal key.to_s, config.key
|
118
|
+
assert_equal 1, config.rows.size
|
119
|
+
assert_equal 1, config.rows[0].values.size
|
120
|
+
|
121
|
+
value_row = config.rows[0].values[0]
|
122
|
+
config_value = value_row.value
|
123
|
+
assert_equal 'a confidential string', Prefab::ConfigValueUnwrapper.deepest_value(config_value, key, {}, @mock_resolver).unwrap
|
124
|
+
reportable_value = Prefab::ConfigValueUnwrapper.deepest_value(config_value, key, {}, @mock_resolver).reportable_value
|
125
|
+
assert reportable_value.start_with? Prefab::ConfigValueUnwrapper::CONFIDENTIAL_PREFIX
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def stringify_keys(hash)
|
131
|
+
deep_transform_keys(hash, &:to_s)
|
132
|
+
end
|
133
|
+
|
134
|
+
def deep_transform_keys(hash, &block)
|
135
|
+
result = {}
|
136
|
+
hash.each do |key, value|
|
137
|
+
result[yield(key)] = value.is_a?(Hash) ? deep_transform_keys(value, &block) : value
|
138
|
+
end
|
139
|
+
result
|
140
|
+
end
|
141
|
+
|
142
|
+
class MockResolver
|
143
|
+
def get(key)
|
144
|
+
raise "unexpected key"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
class TestLogPathAggregator < Minitest::Test
|
7
|
+
MAX_WAIT = 2
|
8
|
+
SLEEP_TIME = 0.01
|
9
|
+
|
10
|
+
def test_push
|
11
|
+
client = new_client
|
12
|
+
aggregator = Prefab::LogPathAggregator.new(client: client, max_paths: 2, sync_interval: 1000)
|
13
|
+
|
14
|
+
aggregator.push('test.test_log_path_aggregator.test_push.1', ::Logger::INFO)
|
15
|
+
aggregator.push('test.test_log_path_aggregator.test_push.2', ::Logger::DEBUG)
|
16
|
+
|
17
|
+
assert_equal 2, aggregator.data.size
|
18
|
+
|
19
|
+
# we've reached the limit, so no more
|
20
|
+
aggregator.push('test.test_log_path_aggregator.test_push.3', ::Logger::INFO)
|
21
|
+
assert_equal 2, aggregator.data.size
|
22
|
+
|
23
|
+
assert_only_expected_logs
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_sync
|
27
|
+
Timecop.freeze do
|
28
|
+
client = new_client(namespace: 'this.is.a.namespace')
|
29
|
+
|
30
|
+
2.times { client.log.info('here is a message') }
|
31
|
+
3.times { client.log.error('here is a message') }
|
32
|
+
|
33
|
+
requests = wait_for_post_requests(client) do
|
34
|
+
client.log_path_aggregator.send(:sync)
|
35
|
+
end
|
36
|
+
|
37
|
+
assert_equal '/api/v1/known-loggers', requests[0][0]
|
38
|
+
sent_logger = requests[0][1]
|
39
|
+
assert_equal 'this.is.a.namespace', sent_logger.namespace
|
40
|
+
assert_equal Prefab::TimeHelpers.now_in_ms, sent_logger.start_at
|
41
|
+
assert_equal Prefab::TimeHelpers.now_in_ms, sent_logger.end_at
|
42
|
+
assert_equal client.instance_hash, sent_logger.instance_hash
|
43
|
+
assert_includes sent_logger.loggers, PrefabProto::Logger.new(logger_name: 'test.test_log_path_aggregator.test_sync', infos: 2, errors: 3)
|
44
|
+
assert_includes sent_logger.loggers, PrefabProto::Logger.new(logger_name: 'cloud.prefab.client.client', debugs: 1) # spot test that internal logging is here too
|
45
|
+
|
46
|
+
assert_logged [
|
47
|
+
'WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client.configclient No success loading checkpoints',
|
48
|
+
'ERROR 2023-08-09 15:18:12 -0400: test.test_log_path_aggregator.test_sync here is a message'
|
49
|
+
]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def new_client(overrides = {})
|
56
|
+
super(**{
|
57
|
+
prefab_datasources: Prefab::Options::DATASOURCES::ALL,
|
58
|
+
api_key: '123-development-yourapikey-SDK',
|
59
|
+
collect_sync_interval: 1000 # we'll trigger sync manually in our test
|
60
|
+
}.merge(overrides))
|
61
|
+
end
|
62
|
+
end
|