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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/VERSION +1 -1
  4. data/compile_protos.sh +7 -0
  5. data/lib/prefab/client.rb +20 -46
  6. data/lib/prefab/config_client.rb +9 -12
  7. data/lib/prefab/config_resolver.rb +2 -1
  8. data/lib/prefab/config_value_unwrapper.rb +20 -9
  9. data/lib/prefab/context.rb +43 -7
  10. data/lib/prefab/context_shape_aggregator.rb +1 -1
  11. data/lib/prefab/criteria_evaluator.rb +24 -16
  12. data/lib/prefab/evaluation.rb +48 -0
  13. data/lib/prefab/evaluation_summary_aggregator.rb +85 -0
  14. data/lib/prefab/example_contexts_aggregator.rb +76 -0
  15. data/lib/prefab/exponential_backoff.rb +5 -0
  16. data/lib/prefab/feature_flag_client.rb +0 -2
  17. data/lib/prefab/log_path_aggregator.rb +1 -1
  18. data/lib/prefab/logger_client.rb +12 -13
  19. data/lib/prefab/options.rb +52 -43
  20. data/lib/prefab/periodic_sync.rb +30 -13
  21. data/lib/prefab/rate_limit_cache.rb +41 -0
  22. data/lib/prefab/resolved_config_presenter.rb +2 -4
  23. data/lib/prefab/weighted_value_resolver.rb +1 -1
  24. data/lib/prefab-cloud-ruby.rb +5 -5
  25. data/lib/prefab_pb.rb +11 -1
  26. data/prefab-cloud-ruby.gemspec +14 -9
  27. data/test/integration_test.rb +1 -3
  28. data/test/integration_test_helpers.rb +0 -1
  29. data/test/support/common_helpers.rb +174 -0
  30. data/test/support/mock_base_client.rb +44 -0
  31. data/test/support/mock_config_client.rb +19 -0
  32. data/test/support/mock_config_loader.rb +1 -0
  33. data/test/test_client.rb +354 -40
  34. data/test/test_config_client.rb +1 -0
  35. data/test/test_config_loader.rb +1 -0
  36. data/test/test_config_resolver.rb +25 -24
  37. data/test/test_config_value_unwrapper.rb +22 -32
  38. data/test/test_context.rb +1 -0
  39. data/test/test_context_shape_aggregator.rb +11 -1
  40. data/test/test_criteria_evaluator.rb +180 -133
  41. data/test/test_evaluation_summary_aggregator.rb +162 -0
  42. data/test/test_example_contexts_aggregator.rb +238 -0
  43. data/test/test_helper.rb +5 -131
  44. data/test/test_integration.rb +6 -4
  45. data/test/test_local_config_parser.rb +2 -2
  46. data/test/test_log_path_aggregator.rb +9 -1
  47. data/test/test_logger.rb +6 -5
  48. data/test/test_options.rb +33 -2
  49. data/test/test_rate_limit_cache.rb +44 -0
  50. data/test/test_weighted_value_resolver.rb +13 -7
  51. metadata +13 -8
  52. data/lib/prefab/evaluated_configs_aggregator.rb +0 -60
  53. data/lib/prefab/evaluated_keys_aggregator.rb +0 -41
  54. data/lib/prefab/noop_cache.rb +0 -15
  55. data/lib/prefab/noop_stats.rb +0 -8
  56. data/test/test_evaluated_configs_aggregator.rb +0 -254
  57. 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