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
data/test/test_client.rb
ADDED
@@ -0,0 +1,444 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestClient < Minitest::Test
|
6
|
+
LOCAL_ONLY = Prefab::Options::DATASOURCES::LOCAL_ONLY
|
7
|
+
|
8
|
+
PROJECT_ENV_ID = 1
|
9
|
+
KEY = 'the-key'
|
10
|
+
DEFAULT_VALUE = 'default_value'
|
11
|
+
DESIRED_VALUE = 'desired_value'
|
12
|
+
|
13
|
+
IRRELEVANT = 'this should never show up'
|
14
|
+
|
15
|
+
DEFAULT_VALUE_CONFIG = PrefabProto::ConfigValue.new(string: DEFAULT_VALUE)
|
16
|
+
DESIRED_VALUE_CONFIG = PrefabProto::ConfigValue.new(string: DESIRED_VALUE)
|
17
|
+
|
18
|
+
TRUE_CONFIG = PrefabProto::ConfigValue.new(bool: true)
|
19
|
+
FALSE_CONFIG = PrefabProto::ConfigValue.new(bool: false)
|
20
|
+
|
21
|
+
DEFAULT_ROW = PrefabProto::ConfigRow.new(
|
22
|
+
values: [
|
23
|
+
PrefabProto::ConditionalValue.new(value: DEFAULT_VALUE_CONFIG)
|
24
|
+
]
|
25
|
+
)
|
26
|
+
|
27
|
+
def test_get
|
28
|
+
_, err = capture_io do
|
29
|
+
client = new_client
|
30
|
+
assert_equal 'default', client.get('does.not.exist', 'default')
|
31
|
+
assert_equal 'test sample value', client.get('sample')
|
32
|
+
assert_equal 123, client.get('sample_int')
|
33
|
+
end
|
34
|
+
assert_equal '', err
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_get_with_default
|
38
|
+
client = new_client
|
39
|
+
# A `false` value is not replaced with the default
|
40
|
+
assert_equal false, client.get('false_value', 'red')
|
41
|
+
|
42
|
+
# A falsy value is not replaced with the default
|
43
|
+
assert_equal 0, client.get('zero_value', 'red')
|
44
|
+
|
45
|
+
# A missing value returns the default
|
46
|
+
assert_equal 'buckets', client.get('missing_value', 'buckets')
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_get_with_missing_default
|
50
|
+
client = new_client
|
51
|
+
# it raises by default
|
52
|
+
err = assert_raises(Prefab::Errors::MissingDefaultError) do
|
53
|
+
assert_nil client.get('missing_value')
|
54
|
+
end
|
55
|
+
|
56
|
+
assert_match(/No value found for key/, err.message)
|
57
|
+
assert_match(/on_no_default/, err.message)
|
58
|
+
|
59
|
+
# you can opt-in to return `nil` instead
|
60
|
+
client = new_client(on_no_default: Prefab::Options::ON_NO_DEFAULT::RETURN_NIL)
|
61
|
+
assert_nil client.get('missing_value')
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_enabled
|
65
|
+
client = new_client
|
66
|
+
assert_equal false, client.enabled?('does_not_exist')
|
67
|
+
assert_equal true, client.enabled?('enabled_flag')
|
68
|
+
assert_equal false, client.enabled?('disabled_flag')
|
69
|
+
assert_equal false, client.enabled?('flag_with_a_value')
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_ff_enabled_with_user_key_match
|
73
|
+
client = new_client
|
74
|
+
|
75
|
+
ctx = { user: { key: 'jimmy' } }
|
76
|
+
assert_equal false, client.enabled?('user_key_match', ctx)
|
77
|
+
assert_equal false, Prefab::Context.with_context(ctx) { client.enabled?('user_key_match') }
|
78
|
+
|
79
|
+
ctx = { user: { key: 'abc123' } }
|
80
|
+
assert_equal true, client.enabled?('user_key_match', ctx)
|
81
|
+
assert_equal true, Prefab::Context.with_context(ctx) { client.enabled?('user_key_match') }
|
82
|
+
|
83
|
+
ctx = { user: { key: 'xyz987' } }
|
84
|
+
assert_equal true, client.enabled?('user_key_match', ctx)
|
85
|
+
assert_equal true, Prefab::Context.with_context(ctx) { client.enabled?('user_key_match') }
|
86
|
+
end
|
87
|
+
|
88
|
+
# NOTE: these are all `false` because we're doing a enabled? on a FF with string variants
|
89
|
+
# see test_ff_get_with_context for the raw value tests
|
90
|
+
def test_ff_enabled_with_context
|
91
|
+
client = new_client
|
92
|
+
|
93
|
+
ctx = { user: { domain: 'gmail.com' } }
|
94
|
+
assert_equal false, client.enabled?('just_my_domain', ctx)
|
95
|
+
assert_equal false, Prefab::Context.with_context(ctx) { client.enabled?('just_my_domain') }
|
96
|
+
|
97
|
+
ctx = { user: { domain: 'prefab.cloud' } }
|
98
|
+
assert_equal false, client.enabled?('just_my_domain', ctx)
|
99
|
+
assert_equal false, Prefab::Context.with_context(ctx) { client.enabled?('just_my_domain') }
|
100
|
+
|
101
|
+
ctx = { user: { domain: 'example.com' } }
|
102
|
+
assert_equal false, client.enabled?('just_my_domain', ctx)
|
103
|
+
assert_equal false, Prefab::Context.with_context(ctx) { client.enabled?('just_my_domain') }
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_ff_get_with_context
|
107
|
+
client = new_client
|
108
|
+
|
109
|
+
ctx = { user: { domain: 'gmail.com' } }
|
110
|
+
assert_equal 'DEFAULT', client.get('just_my_domain', 'DEFAULT', ctx)
|
111
|
+
assert_equal 'DEFAULT', Prefab::Context.with_context(ctx) { client.get('just_my_domain', 'DEFAULT') }
|
112
|
+
|
113
|
+
ctx = { user: { domain: 'prefab.cloud' } }
|
114
|
+
assert_equal 'new-version', client.get('just_my_domain', 'DEFAULT', ctx)
|
115
|
+
assert_equal 'new-version', Prefab::Context.with_context(ctx) { client.get('just_my_domain', 'DEFAULT') }
|
116
|
+
|
117
|
+
ctx = { user: { domain: 'example.com' } }
|
118
|
+
assert_equal 'new-version', client.get('just_my_domain', 'DEFAULT', ctx)
|
119
|
+
assert_equal 'new-version', Prefab::Context.with_context(ctx) { client.get('just_my_domain', 'DEFAULT') }
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_deprecated_no_dot_notation_ff_enabled_with_jit_context
|
123
|
+
client = new_client
|
124
|
+
# with no lookup key
|
125
|
+
assert_equal false, client.enabled?('deprecated_no_dot_notation', { domain: 'gmail.com' })
|
126
|
+
assert_equal true, client.enabled?('deprecated_no_dot_notation', { domain: 'prefab.cloud' })
|
127
|
+
assert_equal true, client.enabled?('deprecated_no_dot_notation', { domain: 'example.com' })
|
128
|
+
|
129
|
+
assert_stderr [
|
130
|
+
"[DEPRECATION] Prefab contexts should be a hash with a key of the context name and a value of a hash.",
|
131
|
+
"[DEPRECATION] Prefab contexts should be a hash with a key of the context name and a value of a hash.",
|
132
|
+
"[DEPRECATION] Prefab contexts should be a hash with a key of the context name and a value of a hash."
|
133
|
+
]
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_getting_feature_flag_value
|
137
|
+
client = new_client
|
138
|
+
assert_equal false, client.enabled?('flag_with_a_value')
|
139
|
+
assert_equal 'all-features', client.get('flag_with_a_value')
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_initialization_with_an_options_object
|
143
|
+
options_hash = {
|
144
|
+
namespace: 'test-namespace',
|
145
|
+
prefab_datasources: LOCAL_ONLY
|
146
|
+
}
|
147
|
+
|
148
|
+
options = Prefab::Options.new(options_hash)
|
149
|
+
|
150
|
+
client = Prefab::Client.new(options)
|
151
|
+
|
152
|
+
assert_equal client.namespace, 'test-namespace'
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_initialization_with_a_hash
|
156
|
+
options_hash = {
|
157
|
+
namespace: 'test-namespace',
|
158
|
+
prefab_datasources: LOCAL_ONLY
|
159
|
+
}
|
160
|
+
|
161
|
+
client = Prefab::Client.new(options_hash)
|
162
|
+
|
163
|
+
assert_equal client.namespace, 'test-namespace'
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_evaluation_summary_aggregator
|
167
|
+
fake_api_key = '123-development-yourapikey-SDK'
|
168
|
+
|
169
|
+
# it is nil by default
|
170
|
+
assert_nil new_client(api_key: fake_api_key).evaluation_summary_aggregator
|
171
|
+
|
172
|
+
# it is nil when local_only even if collect_max_evaluation_summaries is true
|
173
|
+
assert_nil new_client(prefab_datasources: LOCAL_ONLY,
|
174
|
+
collect_evaluation_summaries: true, ).evaluation_summary_aggregator
|
175
|
+
|
176
|
+
# it is nil when collect_max_evaluation_summaries is false
|
177
|
+
assert_nil new_client(api_key: fake_api_key,
|
178
|
+
prefab_datasources: :all,
|
179
|
+
collect_evaluation_summaries: false).evaluation_summary_aggregator
|
180
|
+
|
181
|
+
# it is not nil when collect_max_evaluation_summaries is true and the datasource is not local_only
|
182
|
+
assert_equal Prefab::EvaluationSummaryAggregator,
|
183
|
+
new_client(api_key: fake_api_key,
|
184
|
+
prefab_datasources: :all,
|
185
|
+
collect_evaluation_summaries: true).evaluation_summary_aggregator.class
|
186
|
+
|
187
|
+
assert_logged [
|
188
|
+
"WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client.configclient No success loading checkpoints"
|
189
|
+
]
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_get_with_basic_value
|
193
|
+
config = basic_value_config
|
194
|
+
client = new_client(config: config, project_env_id: PROJECT_ENV_ID, collect_evaluation_summaries: true,
|
195
|
+
context_upload_mode: :periodic_example, allow_telemetry_in_local_mode: true)
|
196
|
+
|
197
|
+
assert_equal DESIRED_VALUE, client.get(config.key, IRRELEVANT, 'user' => { 'key' => 99 })
|
198
|
+
|
199
|
+
assert_summary client, {
|
200
|
+
[KEY, :CONFIG] => {
|
201
|
+
{
|
202
|
+
config_id: config.id,
|
203
|
+
config_row_index: 1,
|
204
|
+
selected_value: DESIRED_VALUE_CONFIG,
|
205
|
+
conditional_value_index: 0,
|
206
|
+
weighted_value_index: nil,
|
207
|
+
selected_index: nil
|
208
|
+
} => 1
|
209
|
+
}
|
210
|
+
}
|
211
|
+
|
212
|
+
assert_example_contexts client, [Prefab::Context.new({ user: { 'key' => 99 } })]
|
213
|
+
end
|
214
|
+
|
215
|
+
def test_get_with_basic_value_with_context
|
216
|
+
config = basic_value_config
|
217
|
+
client = new_client(config: config, project_env_id: PROJECT_ENV_ID, collect_evaluation_summaries: true,
|
218
|
+
context_upload_mode: :periodic_example, allow_telemetry_in_local_mode: true)
|
219
|
+
|
220
|
+
client.with_context('user' => { 'key' => 99 }) do
|
221
|
+
assert_equal DESIRED_VALUE, client.get(config.key)
|
222
|
+
end
|
223
|
+
|
224
|
+
assert_summary client, {
|
225
|
+
[KEY, :CONFIG] => {
|
226
|
+
{
|
227
|
+
config_id: config.id,
|
228
|
+
config_row_index: 1,
|
229
|
+
selected_value: DESIRED_VALUE_CONFIG,
|
230
|
+
conditional_value_index: 0,
|
231
|
+
weighted_value_index: nil,
|
232
|
+
selected_index: nil
|
233
|
+
} => 1
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
assert_example_contexts client, [Prefab::Context.new({ user: { 'key' => 99 } })]
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_get_with_weighted_values
|
241
|
+
config = PrefabProto::Config.new(
|
242
|
+
id: 123,
|
243
|
+
key: KEY,
|
244
|
+
config_type: PrefabProto::ConfigType::CONFIG,
|
245
|
+
rows: [
|
246
|
+
DEFAULT_ROW,
|
247
|
+
PrefabProto::ConfigRow.new(
|
248
|
+
project_env_id: PROJECT_ENV_ID,
|
249
|
+
values: [
|
250
|
+
PrefabProto::ConditionalValue.new(
|
251
|
+
criteria: [PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)],
|
252
|
+
value: PrefabProto::ConfigValue.new(weighted_values: weighted_values([['abc', 98], ['def', 1],
|
253
|
+
['ghi', 1]]))
|
254
|
+
)
|
255
|
+
]
|
256
|
+
)
|
257
|
+
]
|
258
|
+
)
|
259
|
+
|
260
|
+
client = new_client(config: config, project_env_id: PROJECT_ENV_ID, collect_evaluation_summaries: true,
|
261
|
+
context_upload_mode: :periodic_example, allow_telemetry_in_local_mode: true)
|
262
|
+
|
263
|
+
2.times do
|
264
|
+
assert_equal 'abc', client.get(config.key, IRRELEVANT, 'user' => { 'key' => '1' })
|
265
|
+
end
|
266
|
+
|
267
|
+
3.times do
|
268
|
+
assert_equal 'def', client.get(config.key, IRRELEVANT, 'user' => { 'key' => '12' })
|
269
|
+
end
|
270
|
+
|
271
|
+
assert_equal 'ghi',
|
272
|
+
client.get(config.key, IRRELEVANT, 'user' => { 'key' => '4', admin: true })
|
273
|
+
|
274
|
+
assert_summary client, {
|
275
|
+
[KEY, :CONFIG] => {
|
276
|
+
{
|
277
|
+
config_id: config.id,
|
278
|
+
config_row_index: 1,
|
279
|
+
selected_value: PrefabProto::ConfigValue.new(string: 'abc'),
|
280
|
+
conditional_value_index: 0,
|
281
|
+
weighted_value_index: 0,
|
282
|
+
selected_index: nil
|
283
|
+
} => 2,
|
284
|
+
|
285
|
+
{
|
286
|
+
config_id: config.id,
|
287
|
+
config_row_index: 1,
|
288
|
+
selected_value: PrefabProto::ConfigValue.new(string: 'def'),
|
289
|
+
conditional_value_index: 0,
|
290
|
+
weighted_value_index: 1,
|
291
|
+
selected_index: nil
|
292
|
+
} => 3,
|
293
|
+
|
294
|
+
{
|
295
|
+
config_id: config.id,
|
296
|
+
config_row_index: 1,
|
297
|
+
selected_value: PrefabProto::ConfigValue.new(string: 'ghi'),
|
298
|
+
conditional_value_index: 0,
|
299
|
+
weighted_value_index: 2,
|
300
|
+
selected_index: nil
|
301
|
+
} => 1
|
302
|
+
}
|
303
|
+
}
|
304
|
+
|
305
|
+
assert_example_contexts client, [
|
306
|
+
Prefab::Context.new(user: { 'key' => '1' }),
|
307
|
+
Prefab::Context.new(user: { 'key' => '12' }),
|
308
|
+
Prefab::Context.new(user: { 'key' => '4', admin: true })
|
309
|
+
]
|
310
|
+
end
|
311
|
+
|
312
|
+
def test_in_seg
|
313
|
+
segment_key = 'segment_key'
|
314
|
+
|
315
|
+
segment_config = PrefabProto::Config.new(
|
316
|
+
config_type: PrefabProto::ConfigType::SEGMENT,
|
317
|
+
key: segment_key,
|
318
|
+
rows: [
|
319
|
+
PrefabProto::ConfigRow.new(
|
320
|
+
values: [
|
321
|
+
PrefabProto::ConditionalValue.new(
|
322
|
+
value: TRUE_CONFIG,
|
323
|
+
criteria: [
|
324
|
+
PrefabProto::Criterion.new(
|
325
|
+
operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
|
326
|
+
value_to_match: string_list(['hotmail.com', 'gmail.com']),
|
327
|
+
property_name: 'user.email'
|
328
|
+
)
|
329
|
+
]
|
330
|
+
),
|
331
|
+
PrefabProto::ConditionalValue.new(value: FALSE_CONFIG)
|
332
|
+
]
|
333
|
+
)
|
334
|
+
]
|
335
|
+
)
|
336
|
+
|
337
|
+
config = PrefabProto::Config.new(
|
338
|
+
key: KEY,
|
339
|
+
rows: [
|
340
|
+
DEFAULT_ROW,
|
341
|
+
|
342
|
+
PrefabProto::ConfigRow.new(
|
343
|
+
project_env_id: PROJECT_ENV_ID,
|
344
|
+
values: [
|
345
|
+
PrefabProto::ConditionalValue.new(
|
346
|
+
criteria: [
|
347
|
+
PrefabProto::Criterion.new(
|
348
|
+
operator: PrefabProto::Criterion::CriterionOperator::IN_SEG,
|
349
|
+
value_to_match: PrefabProto::ConfigValue.new(string: segment_key)
|
350
|
+
)
|
351
|
+
],
|
352
|
+
value: DESIRED_VALUE_CONFIG
|
353
|
+
)
|
354
|
+
]
|
355
|
+
)
|
356
|
+
]
|
357
|
+
)
|
358
|
+
|
359
|
+
client = new_client(config: [config, segment_config], project_env_id: PROJECT_ENV_ID,
|
360
|
+
collect_evaluation_summaries: true, context_upload_mode: :periodic_example, allow_telemetry_in_local_mode: true)
|
361
|
+
|
362
|
+
assert_equal DEFAULT_VALUE, client.get(config.key)
|
363
|
+
assert_equal DEFAULT_VALUE,
|
364
|
+
client.get(config.key, IRRELEVANT, user: { key: 'abc', email: 'example@prefab.cloud' })
|
365
|
+
assert_equal DESIRED_VALUE, client.get(config.key, IRRELEVANT, user: { key: 'def', email: 'example@hotmail.com' })
|
366
|
+
|
367
|
+
assert_summary client, {
|
368
|
+
[segment_key, :SEGMENT] => {
|
369
|
+
{ config_id: 0, config_row_index: 0, conditional_value_index: 1, selected_value: FALSE_CONFIG,
|
370
|
+
weighted_value_index: nil, selected_index: nil } => 2,
|
371
|
+
{ config_id: 0, config_row_index: 0, conditional_value_index: 0, selected_value: TRUE_CONFIG,
|
372
|
+
weighted_value_index: nil, selected_index: nil } => 1
|
373
|
+
},
|
374
|
+
[KEY, :NOT_SET_CONFIG_TYPE] => {
|
375
|
+
{ config_id: 0, config_row_index: 0, conditional_value_index: 0, selected_value: DEFAULT_VALUE_CONFIG,
|
376
|
+
weighted_value_index: nil, selected_index: nil } => 2,
|
377
|
+
{ config_id: 0, config_row_index: 1, conditional_value_index: 0, selected_value: DESIRED_VALUE_CONFIG,
|
378
|
+
weighted_value_index: nil, selected_index: nil } => 1
|
379
|
+
}
|
380
|
+
}
|
381
|
+
|
382
|
+
assert_example_contexts client, [
|
383
|
+
Prefab::Context.new(user: { key: 'abc', email: 'example@prefab.cloud' }),
|
384
|
+
Prefab::Context.new(user: { key: 'def', email: 'example@hotmail.com' })
|
385
|
+
]
|
386
|
+
end
|
387
|
+
|
388
|
+
def test_get_log_level
|
389
|
+
config = PrefabProto::Config.new(
|
390
|
+
id: 999,
|
391
|
+
key: 'log-level',
|
392
|
+
config_type: PrefabProto::ConfigType::LOG_LEVEL,
|
393
|
+
rows: [
|
394
|
+
PrefabProto::ConfigRow.new(
|
395
|
+
values: [
|
396
|
+
PrefabProto::ConditionalValue.new(
|
397
|
+
criteria: [PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)],
|
398
|
+
value: PrefabProto::ConfigValue.new(log_level: PrefabProto::LogLevel::INFO)
|
399
|
+
)
|
400
|
+
]
|
401
|
+
)
|
402
|
+
]
|
403
|
+
)
|
404
|
+
|
405
|
+
client = new_client(config: config, project_env_id: PROJECT_ENV_ID,
|
406
|
+
collect_evaluation_summaries: true, allow_telemetry_in_local_mode: true)
|
407
|
+
|
408
|
+
assert_equal :INFO, client.get(config.key, IRRELEVANT)
|
409
|
+
|
410
|
+
# nothing is summarized for log levels
|
411
|
+
assert_summary client, {}
|
412
|
+
end
|
413
|
+
|
414
|
+
def test_fork_includes_logger_context_keys
|
415
|
+
client = new_client
|
416
|
+
client.log.add_context_keys "user.name"
|
417
|
+
|
418
|
+
forked = client.fork
|
419
|
+
|
420
|
+
assert forked.log.context_keys.to_a == %w(user.name)
|
421
|
+
end
|
422
|
+
|
423
|
+
private
|
424
|
+
|
425
|
+
def basic_value_config
|
426
|
+
PrefabProto::Config.new(
|
427
|
+
id: 123,
|
428
|
+
key: KEY,
|
429
|
+
config_type: PrefabProto::ConfigType::CONFIG,
|
430
|
+
rows: [
|
431
|
+
DEFAULT_ROW,
|
432
|
+
PrefabProto::ConfigRow.new(
|
433
|
+
project_env_id: PROJECT_ENV_ID,
|
434
|
+
values: [
|
435
|
+
PrefabProto::ConditionalValue.new(
|
436
|
+
criteria: [PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)],
|
437
|
+
value: DESIRED_VALUE_CONFIG
|
438
|
+
)
|
439
|
+
]
|
440
|
+
)
|
441
|
+
]
|
442
|
+
)
|
443
|
+
end
|
444
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestConfigClient < Minitest::Test
|
6
|
+
def setup
|
7
|
+
super
|
8
|
+
options = Prefab::Options.new(
|
9
|
+
prefab_config_override_dir: 'none',
|
10
|
+
prefab_config_classpath_dir: 'test',
|
11
|
+
prefab_envs: 'unit_tests',
|
12
|
+
prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY,
|
13
|
+
x_use_local_cache: true,
|
14
|
+
)
|
15
|
+
|
16
|
+
@config_client = Prefab::ConfigClient.new(MockBaseClient.new(options), 10)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_load
|
20
|
+
assert_equal 'test sample value', @config_client.get('sample')
|
21
|
+
assert_equal 123, @config_client.get('sample_int')
|
22
|
+
assert_equal 12.12, @config_client.get('sample_double')
|
23
|
+
assert_equal true, @config_client.get('sample_bool')
|
24
|
+
assert_equal :ERROR, @config_client.get('log-level.app')
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_initialization_timeout_error
|
28
|
+
options = Prefab::Options.new(
|
29
|
+
api_key: '123-ENV-KEY-SDK',
|
30
|
+
initialization_timeout_sec: 0.01,
|
31
|
+
logdev: StringIO.new
|
32
|
+
)
|
33
|
+
|
34
|
+
err = assert_raises(Prefab::Errors::InitializationTimeoutError) do
|
35
|
+
Prefab::Client.new(options).config_client.get('anything')
|
36
|
+
end
|
37
|
+
|
38
|
+
assert_match(/couldn't initialize in 0.01 second timeout/, err.message)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_prefab_envs_is_forgiving
|
42
|
+
assert_equal ['my_env'], Prefab::Options.new(
|
43
|
+
prefab_envs: 'my_env'
|
44
|
+
).prefab_envs
|
45
|
+
|
46
|
+
assert_equal %w[my_env a_second_env], Prefab::Options.new(
|
47
|
+
prefab_envs: %w[my_env a_second_env]
|
48
|
+
).prefab_envs
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_prefab_envs_env_var
|
52
|
+
ENV['PREFAB_ENVS'] = 'one,two'
|
53
|
+
assert_equal %w[one two], Prefab::Options.new.prefab_envs
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_invalid_api_key_error
|
57
|
+
options = Prefab::Options.new(
|
58
|
+
api_key: ''
|
59
|
+
)
|
60
|
+
|
61
|
+
err = assert_raises(Prefab::Errors::InvalidApiKeyError) do
|
62
|
+
Prefab::Client.new(options).config_client.get('anything')
|
63
|
+
end
|
64
|
+
|
65
|
+
assert_match(/No API key/, err.message)
|
66
|
+
|
67
|
+
options = Prefab::Options.new(
|
68
|
+
api_key: 'invalid'
|
69
|
+
)
|
70
|
+
|
71
|
+
err = assert_raises(Prefab::Errors::InvalidApiKeyError) do
|
72
|
+
Prefab::Client.new(options).config_client.get('anything')
|
73
|
+
end
|
74
|
+
|
75
|
+
assert_match(/format is invalid/, err.message)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_caching
|
79
|
+
@config_client.send(:cache_configs,
|
80
|
+
PrefabProto::Configs.new(configs:
|
81
|
+
[PrefabProto::Config.new(key: 'test', id: 1,
|
82
|
+
rows: [PrefabProto::ConfigRow.new(
|
83
|
+
values: [
|
84
|
+
PrefabProto::ConditionalValue.new(
|
85
|
+
value: PrefabProto::ConfigValue.new(string: "test value")
|
86
|
+
)
|
87
|
+
]
|
88
|
+
)])],
|
89
|
+
config_service_pointer: PrefabProto::ConfigServicePointer.new(project_id: 3, project_env_id: 5)))
|
90
|
+
@config_client.send(:load_cache)
|
91
|
+
assert_equal "test value", @config_client.get("test")
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_cache_path_respects_xdg
|
95
|
+
options = Prefab::Options.new(
|
96
|
+
prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY,
|
97
|
+
x_use_local_cache: true,
|
98
|
+
api_key: "123-ENV-KEY-SDK",)
|
99
|
+
|
100
|
+
config_client = Prefab::ConfigClient.new(MockBaseClient.new(options), 10)
|
101
|
+
assert_equal "#{Dir.home}/.cache/prefab.cache.123.json", config_client.send(:cache_path)
|
102
|
+
|
103
|
+
with_env('XDG_CACHE_HOME', '/tmp') do
|
104
|
+
config_client = Prefab::ConfigClient.new(MockBaseClient.new(options), 10)
|
105
|
+
assert_equal "/tmp/prefab.cache.123.json", config_client.send(:cache_path)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestConfigLoader < Minitest::Test
|
6
|
+
def setup
|
7
|
+
super
|
8
|
+
options = Prefab::Options.new(
|
9
|
+
prefab_config_override_dir: 'none',
|
10
|
+
prefab_config_classpath_dir: 'test',
|
11
|
+
prefab_envs: 'unit_tests'
|
12
|
+
)
|
13
|
+
@loader = Prefab::ConfigLoader.new(MockBaseClient.new(options))
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_load
|
17
|
+
should_be :int, 123, 'sample_int'
|
18
|
+
should_be :string, 'test sample value', 'sample'
|
19
|
+
should_be :bool, true, 'sample_bool'
|
20
|
+
should_be :double, 12.12, 'sample_double'
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_nested
|
24
|
+
should_be :string, 'nested value', 'nested.values.string'
|
25
|
+
should_be :string, 'top level', 'nested.values'
|
26
|
+
should_be :log_level, :ERROR, 'log-level.app'
|
27
|
+
should_be :log_level, :WARN, 'log-level.app.controller.hello'
|
28
|
+
should_be :log_level, :INFO, 'log-level.app.controller.hello.index'
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_invalid_log_level
|
32
|
+
should_be :log_level, :NOT_SET_LOG_LEVEL, 'log-level.invalid'
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_load_without_unit_test_env
|
36
|
+
options = Prefab::Options.new(
|
37
|
+
prefab_config_override_dir: 'none',
|
38
|
+
prefab_config_classpath_dir: 'test'
|
39
|
+
# no prefab_envs
|
40
|
+
)
|
41
|
+
@loader = Prefab::ConfigLoader.new(MockBaseClient.new(options))
|
42
|
+
should_be :string, 'default sample value', 'sample'
|
43
|
+
should_be :bool, true, 'sample_bool'
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_highwater
|
47
|
+
assert_equal 0, @loader.highwater_mark
|
48
|
+
@loader.set(PrefabProto::Config.new(id: 1, key: 'sample_int', rows: [config_row(PrefabProto::ConfigValue.new(int: 456))]),
|
49
|
+
'test')
|
50
|
+
assert_equal 1, @loader.highwater_mark
|
51
|
+
|
52
|
+
@loader.set(PrefabProto::Config.new(id: 5, key: 'sample_int', rows: [config_row(PrefabProto::ConfigValue.new(int: 456))]),
|
53
|
+
'test')
|
54
|
+
assert_equal 5, @loader.highwater_mark
|
55
|
+
@loader.set(PrefabProto::Config.new(id: 2, key: 'sample_int', rows: [config_row(PrefabProto::ConfigValue.new(int: 456))]),
|
56
|
+
'test')
|
57
|
+
assert_equal 5, @loader.highwater_mark
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_keeps_most_recent
|
61
|
+
assert_equal 0, @loader.highwater_mark
|
62
|
+
@loader.set(PrefabProto::Config.new(id: 1, key: 'sample_int', rows: [config_row(PrefabProto::ConfigValue.new(int: 1))]),
|
63
|
+
'test')
|
64
|
+
assert_equal 1, @loader.highwater_mark
|
65
|
+
should_be :int, 1, 'sample_int'
|
66
|
+
|
67
|
+
@loader.set(PrefabProto::Config.new(id: 4, key: 'sample_int', rows: [config_row(PrefabProto::ConfigValue.new(int: 4))]),
|
68
|
+
'test')
|
69
|
+
assert_equal 4, @loader.highwater_mark
|
70
|
+
should_be :int, 4, 'sample_int'
|
71
|
+
|
72
|
+
@loader.set(PrefabProto::Config.new(id: 2, key: 'sample_int', rows: [config_row(PrefabProto::ConfigValue.new(int: 2))]),
|
73
|
+
'test')
|
74
|
+
assert_equal 4, @loader.highwater_mark
|
75
|
+
should_be :int, 4, 'sample_int'
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_api_precedence
|
79
|
+
should_be :int, 123, 'sample_int'
|
80
|
+
|
81
|
+
@loader.set(PrefabProto::Config.new(key: 'sample_int', rows: [config_row(PrefabProto::ConfigValue.new(int: 456))]), 'test')
|
82
|
+
should_be :int, 456, 'sample_int'
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_api_deltas
|
86
|
+
val = PrefabProto::ConfigValue.new(int: 456)
|
87
|
+
config = PrefabProto::Config.new(key: 'sample_int', rows: [config_row(val)])
|
88
|
+
@loader.set(config, 'test')
|
89
|
+
|
90
|
+
configs = PrefabProto::Configs.new
|
91
|
+
configs.configs << config
|
92
|
+
assert_equal configs, @loader.get_api_deltas
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_loading_tombstones_removes_entries
|
96
|
+
val = PrefabProto::ConfigValue.new(int: 456)
|
97
|
+
config = PrefabProto::Config.new(key: 'sample_int', rows: [config_row(val)], id: 2)
|
98
|
+
@loader.set(config, 'test')
|
99
|
+
|
100
|
+
config = PrefabProto::Config.new(key: 'sample_int', rows: [], id: 3)
|
101
|
+
@loader.set(config, 'test')
|
102
|
+
|
103
|
+
configs = PrefabProto::Configs.new
|
104
|
+
assert_equal configs, @loader.get_api_deltas
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def should_be(type, value, key)
|
110
|
+
assert_equal type, @loader.calc_config[key][:config].rows[0].values[0].value.type
|
111
|
+
assert_equal value, @loader.calc_config[key][:config].rows[0].values[0].value.send(type)
|
112
|
+
end
|
113
|
+
|
114
|
+
def config_row(value)
|
115
|
+
PrefabProto::ConfigRow.new(values: [PrefabProto::ConditionalValue.new(value: value)])
|
116
|
+
end
|
117
|
+
end
|