prefab-cloud-ruby 0

Sign up to get free protection for your applications and to get access to all the features.
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,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestEncryption < Minitest::Test
6
+ def test_encryption
7
+ secret = Prefab::Encryption.generate_new_hex_key
8
+
9
+ enc = Prefab::Encryption.new(secret)
10
+
11
+ clear_text = "hello world"
12
+ encrypted = enc.encrypt(clear_text)
13
+ decrypted = enc.decrypt(encrypted)
14
+ assert_equal clear_text, decrypted
15
+ end
16
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+ require 'timecop'
5
+
6
+ class TestEvaluationSummaryAggregator < Minitest::Test
7
+ EFFECTIVELY_NEVER = 99_999 # we sync manually
8
+
9
+ EXAMPLE_VALUE_1 = PrefabProto::ConfigValue.new(bool: true)
10
+ EXAMPLE_VALUE_2 = PrefabProto::ConfigValue.new(bool: false)
11
+
12
+ EXAMPLE_COUNTER = {
13
+ config_id: 1,
14
+ selected_index: 2,
15
+ config_row_index: 3,
16
+ conditional_value_index: 4,
17
+ weighted_value_index: 5,
18
+ seleced_value: EXAMPLE_VALUE_1
19
+ }.freeze
20
+
21
+ def test_increments_counts
22
+ aggregator = Prefab::EvaluationSummaryAggregator.new(client: MockBaseClient.new, max_keys: 10,
23
+ sync_interval: EFFECTIVELY_NEVER)
24
+
25
+ aggregator.record(config_key: 'foo', config_type: 'bar', counter: EXAMPLE_COUNTER)
26
+
27
+ assert_equal 1, aggregator.data[%w[foo bar]][EXAMPLE_COUNTER]
28
+
29
+ 2.times { aggregator.record(config_key: 'foo', config_type: 'bar', counter: EXAMPLE_COUNTER) }
30
+ assert_equal 3, aggregator.data[%w[foo bar]][EXAMPLE_COUNTER]
31
+
32
+ another_counter = EXAMPLE_COUNTER.merge(selected_index: EXAMPLE_COUNTER[:selected_index] + 1)
33
+
34
+ aggregator.record(config_key: 'foo', config_type: 'bar', counter: another_counter)
35
+ assert_equal 3, aggregator.data[%w[foo bar]][EXAMPLE_COUNTER]
36
+ assert_equal 1, aggregator.data[%w[foo bar]][another_counter]
37
+ end
38
+
39
+ def test_prepare_data
40
+ aggregator = Prefab::EvaluationSummaryAggregator.new(client: MockBaseClient.new, max_keys: 10,
41
+ sync_interval: EFFECTIVELY_NEVER)
42
+
43
+ expected = {
44
+ ['config-1', :CONFIG] => {
45
+ { config_id: 1, selected_index: 2, config_row_index: 3, conditional_value_index: 4,
46
+ weighted_value_index: 5, selected_value: EXAMPLE_VALUE_1 } => 3,
47
+ { config_id: 1, selected_index: 3, config_row_index: 7, conditional_value_index: 8,
48
+ weighted_value_index: 10, selected_value: EXAMPLE_VALUE_2 } => 1
49
+ },
50
+ ['config-2', :FEATURE_FLAG] => {
51
+ { config_id: 2, selected_index: 3, config_row_index: 5, conditional_value_index: 7,
52
+ weighted_value_index: 6, selected_value: EXAMPLE_VALUE_1 } => 9
53
+ }
54
+ }
55
+
56
+ add_example_data(aggregator)
57
+ assert_equal expected, aggregator.prepare_data
58
+ assert aggregator.data.empty?
59
+ end
60
+
61
+ def test_sync
62
+ awhile_ago = Time.now - 60
63
+ now = Time.now
64
+
65
+ client = MockBaseClient.new
66
+
67
+ aggregator = nil
68
+
69
+ Timecop.freeze(awhile_ago) do
70
+ # start the aggregator in the past
71
+ aggregator = Prefab::EvaluationSummaryAggregator.new(client: client, max_keys: 10,
72
+ sync_interval: EFFECTIVELY_NEVER)
73
+ end
74
+
75
+ add_example_data(aggregator)
76
+
77
+ expected_post = PrefabProto::TelemetryEvents.new(
78
+ instance_hash: client.instance_hash,
79
+ events: [
80
+ PrefabProto::TelemetryEvent.new(
81
+ summaries:
82
+
83
+ PrefabProto::ConfigEvaluationSummaries.new(
84
+ start: awhile_ago.to_i * 1000,
85
+ end: now.to_i * 1000,
86
+ summaries: [
87
+ PrefabProto::ConfigEvaluationSummary.new(
88
+ key: 'config-1',
89
+ type: :CONFIG,
90
+ counters: [
91
+ PrefabProto::ConfigEvaluationCounter.new(
92
+ config_id: 1,
93
+ selected_index: 2,
94
+ config_row_index: 3,
95
+ conditional_value_index: 4,
96
+ weighted_value_index: 5,
97
+ selected_value: EXAMPLE_VALUE_1,
98
+ count: 3
99
+ ),
100
+ PrefabProto::ConfigEvaluationCounter.new(
101
+ config_id: 1,
102
+ selected_index: 3,
103
+ config_row_index: 7,
104
+ conditional_value_index: 8,
105
+ weighted_value_index: 10,
106
+ selected_value: EXAMPLE_VALUE_2,
107
+ count: 1
108
+ )
109
+ ]
110
+ ),
111
+ PrefabProto::ConfigEvaluationSummary.new(
112
+ key: 'config-2',
113
+ type: :FEATURE_FLAG,
114
+ counters: [
115
+ PrefabProto::ConfigEvaluationCounter.new(
116
+ config_id: 2,
117
+ selected_index: 3,
118
+ config_row_index: 5,
119
+ conditional_value_index: 7,
120
+ weighted_value_index: 6,
121
+ selected_value: EXAMPLE_VALUE_1,
122
+ count: 9
123
+ )
124
+ ]
125
+ )
126
+ ]
127
+ )
128
+ )
129
+ ]
130
+ )
131
+
132
+ requests = wait_for_post_requests(client) do
133
+ Timecop.freeze(now) do
134
+ aggregator.sync
135
+ end
136
+ end
137
+
138
+ assert_equal [[
139
+ '/api/v1/telemetry',
140
+ expected_post
141
+ ]], requests
142
+ end
143
+
144
+ private
145
+
146
+ def add_example_data(aggregator)
147
+ data = {
148
+ ['config-1', :CONFIG] => {
149
+ { config_id: 1, selected_index: 2, config_row_index: 3, conditional_value_index: 4,
150
+ weighted_value_index: 5, selected_value: EXAMPLE_VALUE_1 } => 3,
151
+ { config_id: 1, selected_index: 3, config_row_index: 7, conditional_value_index: 8,
152
+ weighted_value_index: 10, selected_value: EXAMPLE_VALUE_2 } => 1
153
+ },
154
+ ['config-2', :FEATURE_FLAG] => {
155
+ { config_id: 2, selected_index: 3, config_row_index: 5, conditional_value_index: 7,
156
+ weighted_value_index: 6, selected_value: EXAMPLE_VALUE_1 } => 9
157
+ }
158
+ }
159
+
160
+ aggregator.instance_variable_set('@data', data)
161
+ end
162
+ end
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+ require 'timecop'
5
+
6
+ class TestExampleContextsAggregator < Minitest::Test
7
+ EFFECTIVELY_NEVER = 99_999 # we sync manually
8
+
9
+ def test_record
10
+ aggregator = Prefab::ExampleContextsAggregator.new(client: MockBaseClient.new, max_contexts: 2,
11
+ sync_interval: EFFECTIVELY_NEVER)
12
+
13
+ context = Prefab::Context.new(
14
+ user: { key: 'abc' },
15
+ device: { key: 'def', mobile: true }
16
+ )
17
+
18
+ aggregator.record(context)
19
+ assert_equal [context], aggregator.data
20
+
21
+ # This doesn't get updated because we already have a context for this user/device
22
+ aggregator.record(context)
23
+ assert_equal [context], aggregator.data
24
+
25
+ new_context = Prefab::Context.new(
26
+ user: { key: 'ghi', admin: true },
27
+ team: { key: '999' }
28
+ )
29
+
30
+ aggregator.record(new_context)
31
+ assert_equal [context, new_context], aggregator.data
32
+
33
+ # this doesn't get recorded because we're at max_contexts
34
+ aggregator.record(Prefab::Context.new(user: { key: 'new' }))
35
+ assert_equal [context, new_context], aggregator.data
36
+ end
37
+
38
+ def test_prepare_data
39
+ aggregator = Prefab::ExampleContextsAggregator.new(client: MockBaseClient.new, max_contexts: 10,
40
+ sync_interval: EFFECTIVELY_NEVER)
41
+
42
+ context = Prefab::Context.new(
43
+ user: { key: 'abc' },
44
+ device: { key: 'def', mobile: true }
45
+ )
46
+
47
+ aggregator.record(context)
48
+
49
+ assert_equal [context], aggregator.prepare_data
50
+ assert aggregator.data.empty?
51
+ end
52
+
53
+ def test_record_with_expiry
54
+ aggregator = Prefab::ExampleContextsAggregator.new(client: MockBaseClient.new, max_contexts: 10,
55
+ sync_interval: EFFECTIVELY_NEVER)
56
+
57
+ context = Prefab::Context.new(
58
+ user: { key: 'abc' },
59
+ device: { key: 'def', mobile: true }
60
+ )
61
+
62
+ aggregator.record(context)
63
+
64
+ assert_equal [context], aggregator.data
65
+
66
+ Timecop.travel(Time.now + (60 * 60) - 1) do
67
+ aggregator.record(context)
68
+
69
+ # This doesn't get updated because we already have a context for this user/device in the timeframe
70
+ assert_equal [context], aggregator.data
71
+ end
72
+
73
+ Timecop.travel(Time.now + ((60 * 60) + 1)) do
74
+ # this is new because we've passed the expiry
75
+ aggregator.record(context)
76
+
77
+ assert_equal [context, context], aggregator.data
78
+ end
79
+ end
80
+
81
+ def test_sync
82
+ now = Time.now
83
+
84
+ client = MockBaseClient.new
85
+
86
+ aggregator = Prefab::ExampleContextsAggregator.new(client: client, max_contexts: 10,
87
+ sync_interval: EFFECTIVELY_NEVER)
88
+
89
+ context = Prefab::Context.new(
90
+ user: { key: 'abc' },
91
+ device: { key: 'def', mobile: true }
92
+ )
93
+ aggregator.record(context)
94
+
95
+ # This is the same as above so we shouldn't get anything new
96
+ aggregator.record(context)
97
+
98
+ aggregator.record(
99
+ Prefab::Context.new(
100
+ user: { key: 'ghi' },
101
+ device: { key: 'jkl', mobile: false }
102
+ )
103
+ )
104
+
105
+ aggregator.record(Prefab::Context.new(user: { key: 'kev', name: 'kevin', age: 48.5 }))
106
+
107
+ assert_equal 3, aggregator.cache.data.size
108
+
109
+ expected_post = PrefabProto::TelemetryEvents.new(
110
+ instance_hash: client.instance_hash,
111
+ events: [
112
+ PrefabProto::TelemetryEvent.new(
113
+ example_contexts: PrefabProto::ExampleContexts.new(
114
+ examples: [
115
+ PrefabProto::ExampleContext.new(
116
+ timestamp: now.utc.to_i * 1000,
117
+ contextSet: PrefabProto::ContextSet.new(
118
+ contexts: [
119
+ PrefabProto::Context.new(
120
+ type: 'user',
121
+ values: {
122
+ 'key' => PrefabProto::ConfigValue.new(string: 'abc')
123
+ }
124
+ ),
125
+ PrefabProto::Context.new(
126
+ type: 'device',
127
+ values: {
128
+ 'key' => PrefabProto::ConfigValue.new(string: 'def'),
129
+ 'mobile' => PrefabProto::ConfigValue.new(bool: true)
130
+ }
131
+ )
132
+ ]
133
+ )
134
+ ),
135
+
136
+ PrefabProto::ExampleContext.new(
137
+ timestamp: now.utc.to_i * 1000,
138
+ contextSet: PrefabProto::ContextSet.new(
139
+ contexts: [
140
+ PrefabProto::Context.new(
141
+ type: 'user',
142
+ values: {
143
+ 'key' => PrefabProto::ConfigValue.new(string: 'ghi')
144
+ }
145
+ ),
146
+ PrefabProto::Context.new(
147
+ type: 'device',
148
+ values: {
149
+ 'key' => PrefabProto::ConfigValue.new(string: 'jkl'),
150
+ 'mobile' => PrefabProto::ConfigValue.new(bool: false)
151
+ }
152
+ )
153
+ ]
154
+ )
155
+ ),
156
+
157
+ PrefabProto::ExampleContext.new(
158
+ timestamp: now.utc.to_i * 1000,
159
+ contextSet: PrefabProto::ContextSet.new(
160
+ contexts: [
161
+ PrefabProto::Context.new(
162
+ type: 'user',
163
+ values: {
164
+ 'key' => PrefabProto::ConfigValue.new(string: 'kev'),
165
+ 'name' => PrefabProto::ConfigValue.new(string: 'kevin'),
166
+ 'age' => PrefabProto::ConfigValue.new(double: 48.5)
167
+ }
168
+ )
169
+ ]
170
+ )
171
+ )
172
+ ]
173
+ )
174
+ )
175
+ ]
176
+ )
177
+
178
+ requests = wait_for_post_requests(client) do
179
+ Timecop.freeze(now + (60 * 60) - 1) do
180
+ aggregator.sync
181
+ end
182
+ end
183
+
184
+ assert_equal [[
185
+ '/api/v1/telemetry',
186
+ expected_post
187
+ ]], requests
188
+
189
+ # this hasn't changed because not enough time has passed
190
+ assert_equal 3, aggregator.cache.data.size
191
+
192
+ # a sync past the expiry should clear the cache
193
+ Timecop.freeze(now + (60 * 60) + 1) do
194
+ # we need a new piece of data for the sync to happen
195
+ aggregator.record(Prefab::Context.new(user: { key: 'bozo', name: 'Bozo', age: 99 }))
196
+
197
+ requests = wait_for_post_requests(client) do
198
+ aggregator.sync
199
+ end
200
+ end
201
+
202
+ expected_post = PrefabProto::TelemetryEvents.new(
203
+ instance_hash: client.instance_hash,
204
+ events: [
205
+ PrefabProto::TelemetryEvent.new(
206
+ example_contexts: PrefabProto::ExampleContexts.new(
207
+ examples: [
208
+ PrefabProto::ExampleContext.new(
209
+ timestamp: (now.utc.to_i + (60 * 60) + 1) * 1000,
210
+ contextSet: PrefabProto::ContextSet.new(
211
+ contexts: [
212
+ PrefabProto::Context.new(
213
+ type: 'user',
214
+ values: {
215
+ 'key' => PrefabProto::ConfigValue.new(string: 'bozo'),
216
+ 'name' => PrefabProto::ConfigValue.new(string: 'Bozo'),
217
+ 'age' => PrefabProto::ConfigValue.new(int: 99)
218
+ }
219
+ )
220
+ ]
221
+ )
222
+ )
223
+ ]
224
+ )
225
+ )
226
+ ]
227
+ )
228
+
229
+ assert_equal [[
230
+ '/api/v1/telemetry',
231
+ expected_post
232
+ ]], requests
233
+
234
+ # The last sync should have cleared the cache of everything except the latest context
235
+ assert_equal 1, aggregator.cache.data.size
236
+ assert_equal ['user:bozo'], aggregator.cache.data.keys
237
+ end
238
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestExponentialBackoff < Minitest::Test
6
+ def test_backoff
7
+ backoff = Prefab::ExponentialBackoff.new(max_delay: 120)
8
+
9
+ assert_equal 2, backoff.call
10
+ assert_equal 4, backoff.call
11
+ assert_equal 8, backoff.call
12
+ assert_equal 16, backoff.call
13
+ assert_equal 32, backoff.call
14
+ assert_equal 64, backoff.call
15
+ assert_equal 120, backoff.call
16
+ assert_equal 120, backoff.call
17
+ end
18
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestFeatureFlagClient < Minitest::Test
6
+ DEFAULT = 'default'
7
+
8
+ def test_feature_is_on
9
+ ff_client = new_client
10
+
11
+ assert_equal false, ff_client.feature_is_on?('something-that-does-not-exist')
12
+ assert_equal false, ff_client.feature_is_on?('disabled_flag')
13
+ assert_equal true, ff_client.feature_is_on?('enabled_flag')
14
+ assert_equal false, ff_client.feature_is_on?('flag_with_a_value')
15
+ end
16
+
17
+ def test_feature_is_on_for
18
+ ff_client = new_client
19
+
20
+ assert_equal false, ff_client.feature_is_on_for?('something-that-does-not-exist', {})
21
+ assert_equal false, ff_client.feature_is_on_for?('user_key_match', {})
22
+ assert_equal false, ff_client.feature_is_on_for?('user_key_match', { user: { key: 'not-included' } })
23
+ assert_equal true, ff_client.feature_is_on_for?('user_key_match', { user: { key: 'abc123' } })
24
+ assert_equal true, ff_client.feature_is_on_for?('user_key_match', { user: { key: 'xyz987' } })
25
+ end
26
+
27
+ def test_get
28
+ ff_client = new_client
29
+
30
+ # No default
31
+ assert_equal false, ff_client.get('something-that-does-not-exist', {})
32
+ assert_equal false, ff_client.get('disabled_flag', {})
33
+ assert_equal true, ff_client.get('enabled_flag', {})
34
+ assert_equal 'all-features', ff_client.get('flag_with_a_value', {})
35
+
36
+ # with defaults
37
+ assert_equal DEFAULT, ff_client.get('something-that-does-not-exist', {}, default: DEFAULT)
38
+ assert_equal false, ff_client.get('disabled_flag', {}, default: DEFAULT)
39
+ assert_equal true, ff_client.get('enabled_flag', {}, default: DEFAULT)
40
+ assert_equal 'all-features', ff_client.get('flag_with_a_value', {}, default: DEFAULT)
41
+ end
42
+
43
+ private
44
+
45
+ def new_client(overrides = {})
46
+ super(overrides).feature_flag_client
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest/focus'
5
+ require 'minitest/reporters'
6
+ Minitest::Reporters.use! unless ENV['RM_INFO']
7
+
8
+ require 'prefab-cloud-ruby'
9
+
10
+ Dir.glob(File.join(File.dirname(__FILE__), 'support', '**', '*.rb')).each do |file|
11
+ require file
12
+ end
13
+
14
+ MiniTest::Test.class_eval do
15
+ include CommonHelpers
16
+ extend CommonHelpers
17
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+ require 'integration_test_helpers'
5
+ require 'integration_test'
6
+ require 'yaml'
7
+
8
+ class TestIntegration < Minitest::Test
9
+ IntegrationTestHelpers.find_integration_tests.map do |test_file|
10
+ tests = YAML.load(File.read(test_file))['tests']
11
+
12
+ tests.each do |test|
13
+ parent_context = test['context']
14
+
15
+ test['cases'].each do |test_case|
16
+ define_method(:"test_#{test['name']}_#{test_case['name']}") do
17
+ it = IntegrationTest.new(test_case)
18
+
19
+ IntegrationTestHelpers.with_parent_context_maybe(parent_context) do
20
+ case it.test_type
21
+ when :raise
22
+ err = assert_raises(it.expected[:error]) do
23
+ it.test_client.send(it.func, *it.input)
24
+ end
25
+ assert_match(/#{it.expected[:message]}/, err.message)
26
+ when :nil
27
+ assert_nil it.test_client.send(it.func, *it.input)
28
+ when :simple_equality
29
+ if it.func == :enabled?
30
+ flag, _default, context = *it.input
31
+ assert_equal it.expected[:value], it.test_client.send(it.func, flag, context)
32
+ else
33
+ assert_equal it.expected[:value], it.test_client.send(it.func, *it.input)
34
+ end
35
+ when :log_level
36
+ assert_equal it.expected[:value].to_sym, it.test_client.send(it.func, *it.input)
37
+ when :telemetry
38
+ aggregator, get_actual_data, expected = IntegrationTestHelpers.prepare_post_data(it)
39
+ aggregator.sync
40
+
41
+ wait_for -> { it.last_post_result&.status == 200 }
42
+
43
+ assert it.endpoint == it.last_post_endpoint
44
+
45
+ actual = get_actual_data[it.last_data_sent]
46
+
47
+ expected.all? do |expected|
48
+ assert actual.include?(expected)
49
+ end
50
+ else
51
+ raise "Unknown test type: #{it.test_type}"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end