prefab-cloud-ruby 0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|