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.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc.sample +3 -0
  3. data/.github/workflows/ruby.yml +46 -0
  4. data/.gitmodules +3 -0
  5. data/.rubocop.yml +13 -0
  6. data/.tool-versions +1 -0
  7. data/CHANGELOG.md +169 -0
  8. data/CODEOWNERS +1 -0
  9. data/Gemfile +26 -0
  10. data/Gemfile.lock +188 -0
  11. data/LICENSE.txt +20 -0
  12. data/README.md +94 -0
  13. data/Rakefile +50 -0
  14. data/VERSION +1 -0
  15. data/bin/console +21 -0
  16. data/compile_protos.sh +18 -0
  17. data/lib/prefab/client.rb +153 -0
  18. data/lib/prefab/config_client.rb +292 -0
  19. data/lib/prefab/config_client_presenter.rb +18 -0
  20. data/lib/prefab/config_loader.rb +84 -0
  21. data/lib/prefab/config_resolver.rb +77 -0
  22. data/lib/prefab/config_value_unwrapper.rb +115 -0
  23. data/lib/prefab/config_value_wrapper.rb +18 -0
  24. data/lib/prefab/context.rb +179 -0
  25. data/lib/prefab/context_shape.rb +20 -0
  26. data/lib/prefab/context_shape_aggregator.rb +65 -0
  27. data/lib/prefab/criteria_evaluator.rb +136 -0
  28. data/lib/prefab/encryption.rb +65 -0
  29. data/lib/prefab/error.rb +6 -0
  30. data/lib/prefab/errors/env_var_parse_error.rb +11 -0
  31. data/lib/prefab/errors/initialization_timeout_error.rb +13 -0
  32. data/lib/prefab/errors/invalid_api_key_error.rb +19 -0
  33. data/lib/prefab/errors/missing_default_error.rb +13 -0
  34. data/lib/prefab/errors/missing_env_var_error.rb +11 -0
  35. data/lib/prefab/errors/uninitialized_error.rb +13 -0
  36. data/lib/prefab/evaluation.rb +52 -0
  37. data/lib/prefab/evaluation_summary_aggregator.rb +87 -0
  38. data/lib/prefab/example_contexts_aggregator.rb +78 -0
  39. data/lib/prefab/exponential_backoff.rb +21 -0
  40. data/lib/prefab/feature_flag_client.rb +42 -0
  41. data/lib/prefab/http_connection.rb +41 -0
  42. data/lib/prefab/internal_logger.rb +16 -0
  43. data/lib/prefab/local_config_parser.rb +151 -0
  44. data/lib/prefab/log_path_aggregator.rb +69 -0
  45. data/lib/prefab/logger_client.rb +264 -0
  46. data/lib/prefab/murmer3.rb +50 -0
  47. data/lib/prefab/options.rb +208 -0
  48. data/lib/prefab/periodic_sync.rb +69 -0
  49. data/lib/prefab/prefab.rb +56 -0
  50. data/lib/prefab/rate_limit_cache.rb +41 -0
  51. data/lib/prefab/resolved_config_presenter.rb +86 -0
  52. data/lib/prefab/time_helpers.rb +7 -0
  53. data/lib/prefab/weighted_value_resolver.rb +42 -0
  54. data/lib/prefab/yaml_config_parser.rb +34 -0
  55. data/lib/prefab-cloud-ruby.rb +57 -0
  56. data/lib/prefab_pb.rb +93 -0
  57. data/prefab-cloud-ruby.gemspec +155 -0
  58. data/test/.prefab.default.config.yaml +2 -0
  59. data/test/.prefab.unit_tests.config.yaml +28 -0
  60. data/test/integration_test.rb +150 -0
  61. data/test/integration_test_helpers.rb +151 -0
  62. data/test/support/common_helpers.rb +180 -0
  63. data/test/support/mock_base_client.rb +42 -0
  64. data/test/support/mock_config_client.rb +19 -0
  65. data/test/support/mock_config_loader.rb +1 -0
  66. data/test/test_client.rb +444 -0
  67. data/test/test_config_client.rb +109 -0
  68. data/test/test_config_loader.rb +117 -0
  69. data/test/test_config_resolver.rb +430 -0
  70. data/test/test_config_value_unwrapper.rb +224 -0
  71. data/test/test_config_value_wrapper.rb +42 -0
  72. data/test/test_context.rb +203 -0
  73. data/test/test_context_shape.rb +50 -0
  74. data/test/test_context_shape_aggregator.rb +147 -0
  75. data/test/test_criteria_evaluator.rb +726 -0
  76. data/test/test_encryption.rb +16 -0
  77. data/test/test_evaluation_summary_aggregator.rb +162 -0
  78. data/test/test_example_contexts_aggregator.rb +238 -0
  79. data/test/test_exponential_backoff.rb +18 -0
  80. data/test/test_feature_flag_client.rb +48 -0
  81. data/test/test_helper.rb +17 -0
  82. data/test/test_integration.rb +58 -0
  83. data/test/test_local_config_parser.rb +147 -0
  84. data/test/test_log_path_aggregator.rb +62 -0
  85. data/test/test_logger.rb +621 -0
  86. data/test/test_logger_initialization.rb +12 -0
  87. data/test/test_options.rb +75 -0
  88. data/test/test_prefab.rb +12 -0
  89. data/test/test_rate_limit_cache.rb +44 -0
  90. data/test/test_weighted_value_resolver.rb +71 -0
  91. 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